[
  {
    "path": ".editorconfig",
    "content": "root = true\n\n[*.java]\nindent_style = tab\nindent_size = 4\n\n[*.adoc]\nindent_style = tab\nindent_size = 4\n\n[*.groovy]\nindent_style = tab\nindent_size = 4\n\n[*.xml]\nindent_style = tab\nindent_size = 4\n\n[*.yml]\nindent_style = space\nindent_size = 2\n\n[*.yaml]\nindent_style = space\nindent_size = 2\n\n[*.sh]\nindent_style = space\nindent_size = 4\nend_of_line = lf\n"
  },
  {
    "path": ".github/actions/trivy-scan/action.yml",
    "content": "name: 'Trivy Scan'\ndescription: 'Run Trivy Scan on repository'\nruns:\n  using: \"composite\"\n  steps:\n    - uses: actions/checkout@v4\n    - name: Run Trivy vulnerability scanner in repo mode\n      env:\n        TRIVY_DB_REPOSITORY: public.ecr.aws/aquasecurity/trivy-db,aquasec/trivy-db,ghcr.io/aquasecurity/trivy-db\n        TRIVY_JAVA_DB_REPOSITORY: public.ecr.aws/aquasecurity/trivy-java-db,aquasec/trivy-java-db,ghcr.io/aquasecurity/trivy-java-db\n      uses: aquasecurity/trivy-action@master\n      with:\n        scan-type: 'fs'\n        scanners: 'vuln'\n        ignore-unfixed: true\n        severity: 'CRITICAL,HIGH'\n        exit-code: 1\n        trivyignores: .trivyignore"
  },
  {
    "path": ".github/dco.yml",
    "content": "require:\n  members: false\n"
  },
  {
    "path": ".github/settings.xml",
    "content": "<settings>\n\t<profiles>\n\t\t<profile>\n\t\t\t<id>stagingmilestone</id>\n\t\t\t<repositories>\n\t\t\t\t<repository>\n\t\t\t\t\t<id>maven-central</id>\n\t\t\t\t\t<name>Maven Central</name>\n\t\t\t\t\t<url>https://repo.maven.apache.org/maven2</url>\n\t\t\t\t\t<snapshots>\n\t\t\t\t\t\t<enabled>false</enabled>\n\t\t\t\t\t</snapshots>\n\t\t\t\t</repository>\n\t\t\t\t<repository>\n\t\t\t\t\t<id>spring-staging</id>\n\t\t\t\t\t<name>Spring Staging</name>\n\t\t\t\t\t<url>https://repo.spring.io/libs-staging</url>\n\t\t\t\t\t<snapshots>\n\t\t\t\t\t\t<enabled>false</enabled>\n\t\t\t\t\t</snapshots>\n\t\t\t\t</repository>\n\t\t\t\t<repository>\n\t\t\t\t\t<id>spring-milestones</id>\n\t\t\t\t\t<name>Spring Milestones</name>\n\t\t\t\t\t<url>https://repo.spring.io/libs-milestone</url>\n\t\t\t\t\t<snapshots>\n\t\t\t\t\t\t<enabled>false</enabled>\n\t\t\t\t\t</snapshots>\n\t\t\t\t</repository>\n\t\t\t</repositories>\n\t\t\t<pluginRepositories>\n\t\t\t\t<pluginRepository>\n\t\t\t\t\t<id>maven-central</id>\n\t\t\t\t\t<name>Maven Central</name>\n\t\t\t\t\t<url>https://repo.maven.apache.org/maven2</url>\n\t\t\t\t\t<snapshots>\n\t\t\t\t\t\t<enabled>false</enabled>\n\t\t\t\t\t</snapshots>\n\t\t\t\t</pluginRepository>\n\t\t\t\t<pluginRepository>\n\t\t\t\t\t<id>spring-staging</id>\n\t\t\t\t\t<name>Spring Staging</name>\n\t\t\t\t\t<url>https://repo.spring.io/libs-staging</url>\n\t\t\t\t\t<snapshots>\n\t\t\t\t\t\t<enabled>false</enabled>\n\t\t\t\t\t</snapshots>\n\t\t\t\t</pluginRepository>\n\t\t\t\t<pluginRepository>\n\t\t\t\t\t<id>spring-milestones</id>\n\t\t\t\t\t<name>Spring Milestones</name>\n\t\t\t\t\t<url>https://repo.spring.io/libs-milestone</url>\n\t\t\t\t\t<snapshots>\n\t\t\t\t\t\t<enabled>false</enabled>\n\t\t\t\t\t</snapshots>\n\t\t\t\t</pluginRepository>\n\t\t\t</pluginRepositories>\n\t\t</profile>\n\t\t<profile>\n\t\t\t<id>stagingrelease</id>\n\t\t\t<repositories>\n\t\t\t\t<repository>\n\t\t\t\t\t<id>maven-central</id>\n\t\t\t\t\t<name>Maven Central</name>\n\t\t\t\t\t<url>https://repo.maven.apache.org/maven2</url>\n\t\t\t\t\t<snapshots>\n\t\t\t\t\t\t<enabled>false</enabled>\n\t\t\t\t\t</snapshots>\n\t\t\t\t</repository>\n\t\t\t\t<repository>\n\t\t\t\t\t<id>spring-staging</id>\n\t\t\t\t\t<name>Spring Staging</name>\n\t\t\t\t\t<url>https://repo.spring.io/libs-staging</url>\n\t\t\t\t\t<snapshots>\n\t\t\t\t\t\t<enabled>false</enabled>\n\t\t\t\t\t</snapshots>\n\t\t\t\t</repository>\n\t\t\t</repositories>\n\t\t\t<pluginRepositories>\n\t\t\t\t<pluginRepository>\n\t\t\t\t\t<id>maven-central</id>\n\t\t\t\t\t<name>Maven Central</name>\n\t\t\t\t\t<url>https://repo.maven.apache.org/maven2</url>\n\t\t\t\t\t<snapshots>\n\t\t\t\t\t\t<enabled>false</enabled>\n\t\t\t\t\t</snapshots>\n\t\t\t\t</pluginRepository>\n\t\t\t\t<pluginRepository>\n\t\t\t\t\t<id>spring-staging</id>\n\t\t\t\t\t<name>Spring Staging</name>\n\t\t\t\t\t<url>https://repo.spring.io/libs-staging</url>\n\t\t\t\t\t<snapshots>\n\t\t\t\t\t\t<enabled>false</enabled>\n\t\t\t\t\t</snapshots>\n\t\t\t\t</pluginRepository>\n\t\t\t</pluginRepositories>\n\t\t</profile>\n\t</profiles>\n</settings>\n"
  },
  {
    "path": ".github/workflows/build-snapshot-worker.yml",
    "content": "# Worker which is dispatched from build-snapshot-controller workflow.\nname: Build Snapshot Worker\n\non:\n  workflow_dispatch:\n    inputs:\n      build-zoo-handler:\n        description: 'Build Zoo Handler Payload'\n        required: true\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    steps:\n    - uses: actions/checkout@v4\n    - uses: actions/setup-java@v3\n      with:\n        java-version: '17'\n        distribution: 'liberica'\n    - uses: jfrog/setup-jfrog-cli@v3\n      env:\n        JF_URL: 'https://repo.spring.io'\n        JF_ENV_SPRING: ${{ secrets.JF_ARTIFACTORY_SPRING }}\n\n    # cache maven .m2\n    - uses: actions/cache@v3\n      with:\n        path: ~/.m2/repository\n        key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}\n        restore-keys: |\n          ${{ runner.os }}-m2-\n\n    # target deploy repos\n    - name: Configure JFrog Cli\n      run: |\n        jfrog rt mvnc --use-wrapper \\\n          --server-id-resolve=${{ vars.JF_SERVER_ID }} \\\n          --server-id-deploy=${{ vars.JF_SERVER_ID }} \\\n          --repo-resolve-releases=libs-milestone \\\n          --repo-resolve-snapshots=libs-snapshot \\\n          --repo-deploy-releases=libs-release-local \\\n          --repo-deploy-snapshots=libs-snapshot-local\n        echo JFROG_CLI_BUILD_NAME=spring-cloud-deployer-main >> $GITHUB_ENV\n        echo JFROG_CLI_BUILD_NUMBER=$GITHUB_RUN_NUMBER >> $GITHUB_ENV\n\n    # zoo extract and ensure\n    - name: Extract Zoo Context Properties\n      uses: jvalkeal/build-zoo-handler@v0.0.4\n      with:\n        dispatch-handler-extract-context-properties: true\n\n    # build and publish to configured target\n    - name: Build and Publish\n      run: |\n        jfrog rt mvn -U -B clean install -T 0.5C\n        jfrog rt build-publish\n        echo BUILD_ZOO_HANDLER_spring_cloud_deployer_version=$(./mvnw help:evaluate -Dexpression=project.version -q -DforceStdout) >> $GITHUB_ENV\n        echo BUILD_ZOO_HANDLER_spring_cloud_deployer_buildname=spring-cloud-deployer-main >> $GITHUB_ENV\n        echo BUILD_ZOO_HANDLER_spring_cloud_deployer_buildnumber=$GITHUB_RUN_NUMBER >> $GITHUB_ENV\n    - name: Test Report\n      uses: dorny/test-reporter@v1\n      if: ${{ success() || failure() }}\n      with:\n        name: Unit Test - Report\n        path: '**/surefire-reports/*.xml'\n        reporter: java-junit\n        list-tests: 'failed'\n    # zoo success\n    - name: Notify Build Success Zoo Handler Controller\n      uses: jvalkeal/build-zoo-handler@v0.0.4\n      with:\n        dispatch-handler-token: ${{ secrets.SCDF_ACCESS_TOKEN }}\n        dispatch-handler-client-payload-data: >\n          {\n            \"event\": \"build-succeed\"\n          }\n\n    # zoo failure\n    - name: Notify Build Failure Zoo Handler Controller\n      if: ${{ failure() }}\n      uses: jvalkeal/build-zoo-handler@v0.0.4\n      with:\n        dispatch-handler-token: ${{ secrets.SCDF_ACCESS_TOKEN }}\n        dispatch-handler-client-payload-data: >\n          {\n            \"event\": \"build-failed\",\n            \"message\": \"spring-cloud-deployer failed\"\n          }\n    # clean m2 cache\n    - name: Clean cache\n      run: |\n        find ~/.m2/repository -type d -name '*SNAPSHOT' | xargs rm -fr\n  scan:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - name: Run Trivy vulnerability scanner in repo mode\n        uses: aquasecurity/trivy-action@master\n        with:\n          scan-type: 'fs'\n          ignore-unfixed: true\n          format: 'sarif'\n          output: 'trivy-results.sarif'\n          severity: 'CRITICAL,HIGH'\n      - name: Upload Trivy scan results to GitHub Security tab\n        uses: github/codeql-action/upload-sarif@v2\n        with:\n          sarif_file: 'trivy-results.sarif'\n      - name: 'Scanned'\n        shell: bash\n        run: echo \"::info ::Scanned\"\n  done:\n    runs-on: ubuntu-latest\n    needs: [ scan, build ]\n    steps:\n      - name: 'Done'\n        shell: bash\n        run: echo \"::info ::Done\"\n"
  },
  {
    "path": ".github/workflows/ci-it.yml",
    "content": "name: CI IT\n\non:\n  workflow_dispatch:\n\njobs:\n  prepare:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - name: 'Load Matrix'\n        id: matrix\n        shell: bash\n        run: |\n          MATRIX=$(jq -c . .github/workflows/k8s-versions.json)\n          echo \"MATRIX=$MATRIX\" >> $GITHUB_OUTPUT\n    outputs:\n      matrix: ${{ steps.matrix.outputs.MATRIX }}\n  k8s-it:\n    runs-on: ubuntu-latest\n    needs: [ prepare ]\n    strategy:\n      fail-fast: false\n      matrix:\n        include: ${{ fromJson(needs.prepare.outputs.matrix) }}\n    steps:\n      - uses: actions/checkout@v4\n      - uses: actions/cache@v3\n        with:\n          path: ~/.m2/repository\n          key: ${{ runner.os }}-m2it-${{ hashFiles('**/pom.xml') }}\n          restore-keys: |\n            ${{ runner.os }}-m2-\n      - uses: actions/setup-java@v3\n        with:\n          java-version: '17'\n          distribution: 'liberica'\n      - name: 'Install minikube ${{ matrix.patch && matrix.patch || matrix.k8s_version }}'\n        shell: bash\n        run: |\n          MINIKUBE_VERSION=\"v1.34.0\"\n          curl -LO \"https://storage.googleapis.com/minikube/releases/$MINIKUBE_VERSION/minikube-linux-amd64\"\n          sudo install minikube-linux-amd64 /usr/local/bin/minikube\n          minikube start \"--kubernetes-version=${{ matrix.patch && matrix.patch || matrix.k8s_version }}\"\n      # build\n      - name: Build\n        run: |\n          ./mvnw -B -Pfailsafe verify -pl spring-cloud-deployer-kubernetes\n      - name: Test Report\n        uses: dorny/test-reporter@v1\n        if: ${{ success() || failure() }}\n        with:\n          name: \"Integration Test - Report for ${{ matrix.k8s_version }}\"\n          path: '**/failsafe-reports/*IT.xml'\n          reporter: java-junit\n          list-tests: 'failed'\n      # clean m2 cache\n      - name: Clean cache\n        run: |\n          find ~/.m2/repository -type d -name '*SNAPSHOT' | xargs rm -fr\n"
  },
  {
    "path": ".github/workflows/ci-pr.yml",
    "content": "name: CI PRs\n\non:\n  workflow_dispatch:\n  pull_request:\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - uses: actions/setup-java@v3\n        with:\n          java-version: '17'\n          distribution: 'liberica'\n      - name: Build\n        run: |\n          ./mvnw -U -B -s .settings.xml -Pspring clean install -T 0.5C\n      - name: Scan\n        uses: ./.github/actions/trivy-scan\n        if: always()\n"
  },
  {
    "path": ".github/workflows/ci.yml",
    "content": "name: CI\n\non:\n  workflow_dispatch:\n  push:\n    branches:\n      - 'main'\n      - '2.9.x'\n      - '2.8.x'\n      - '2.7.x'\n    paths-ignore:\n      - '.github/**'\n\njobs:\n  build:\n    uses: ./.github/workflows/common-ci.yml\n    with:\n      artifactoryServerId: ${{ vars.JF_SERVER_ID }}\n    secrets: inherit\n"
  },
  {
    "path": ".github/workflows/common-ci.yml",
    "content": "name: common-ci\n\non:\n  workflow_call:\n    inputs:\n      artifactoryServerId:\n        type: string\n        required: true\n        description: 'Artifactory Server Id (typically from vars.JF_SERVER_ID)'\n    secrets:\n      JF_ARTIFACTORY_SPRING:\n\nenv:\n  ARTIFACTORY_KEY: ${{ secrets.JF_ARTIFACTORY_SPRING }}\n  ARTIFACTORY_SERVER_ID: ${{ inputs.artifactoryServerId }}\n\njobs:\n  build_publish_scan:\n    name: Build / Publish / Scan\n    if: github.repository_owner == 'spring-cloud'\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - uses: actions/setup-java@v4\n        with:\n          java-version: '17'\n          distribution: 'liberica'\n      - uses: jfrog/setup-jfrog-cli@v4\n        env:\n          JF_URL: 'https://repo.spring.io'\n          JF_ENV_SPRING: ${{ env.ARTIFACTORY_KEY }}\n        # setup frog cli\n      - name: Configure JFrog Cli\n        run: |\n          jfrog mvnc --use-wrapper \\\n          --server-id-deploy=${{ env.ARTIFACTORY_SERVER_ID }} \\\n          --repo-deploy-releases=libs-release-local \\\n          --repo-deploy-snapshots=libs-snapshot-local\n          echo JFROG_CLI_BUILD_NAME=spring-cloud-deployer-main >> $GITHUB_ENV\n          echo JFROG_CLI_BUILD_NUMBER=$GITHUB_RUN_NUMBER >> $GITHUB_ENV\n      - name: Build and Publish\n        run: |\n          jfrog mvn -U -B -s .settings.xml clean install -T 0.5C\n          jfrog rt build-publish\n      - name: Scan\n        uses: ./.github/actions/trivy-scan\n        if: always()\n"
  },
  {
    "path": ".github/workflows/issue-handler.yml",
    "content": "name: Issue Handler\n\non:\n  workflow_dispatch:\n  issues:\n    types: [opened, labeled, unlabeled]\n  issue_comment:\n    types: [created]\n\njobs:\n  labeler:\n    runs-on: ubuntu-latest\n    steps:\n\n    - name: Handle Issues\n      uses: jvalkeal/issue-handler@v0.0.4\n      with:\n        token: ${{ secrets.GITHUB_TOKEN }}\n        config: >\n          {\n            \"data\": {\n              \"team\": [\n                \"jvalkeal\",\n                \"oodamien\",\n                \"ilayaperumalg\",\n                \"sabbyanandan\",\n                \"tzolov\",\n                \"chrisjs\",\n                \"cppwfs\",\n                \"mminella\",\n                \"dturanski\",\n                \"onobc\",\n                \"claudiahub\",\n                \"sobychacko\",\n                \"corneil\"\n              ]\n            },\n            \"recipes\": [\n              {\n                \"name\": \"Mark new issue to get triaged\",\n                \"type\": \"ifThen\",\n                \"if\": \"isAction('opened') && !dataInArray('team', actor)\",\n                \"then\": \"labelIssue(['status/need-triage'])\"\n              },\n              {\n                \"name\": \"Switch to team if user comments\",\n                \"type\": \"ifThen\",\n                \"if\": \"isEvent('issue_comment') && isAction('created') && actor == context.payload.issue.user.login && labelsContainsAny('status/need-feedback')\",\n                \"then\": \"[labelIssue('for/team-attention'), removeLabel('status/need-feedback')]\"\n              },\n              {\n                \"name\": \"Switch to user if team comments\",\n                \"type\": \"ifThen\",\n                \"if\": \"isEvent('issue_comment') && isAction('created') && dataInArray('team', actor) && labelsContainsAny('for/team-attention') \",\n                \"then\": \"[labelIssue('status/need-feedback', removeLabel('for/team-attention'))]\"\n              },\n              {\n                \"name\": \"Manage backport issues\",\n                \"type\": \"manageBackportIssues\",\n                \"whenLabeled\": \"labeledStartsWith(['branch/'])\",\n                \"whenUnlabeled\": \"labeledStartsWith(['branch/'])\",\n                \"whenLabels\": \"labelsContainsAny(['for/backport'])\",\n                \"fromLabels\": \"labeledStartsWith(['branch/'])\",\n                \"additionalLabels\": \"'type/backport'\",\n                \"body\": \"'Backport #' + number\"\n              }\n            ]\n          }\n"
  },
  {
    "path": ".github/workflows/k8s-versions.json",
    "content": "[\n  {\n    \"k8s_version\": \"v1.23\",\n    \"patch\": null\n  },\n  {\n    \"k8s_version\": \"v1.24\",\n    \"patch\": null\n  },\n  {\n    \"k8s_version\": \"v1.25\",\n    \"patch\": null\n  },\n  {\n    \"k8s_version\": \"v1.26\",\n    \"patch\": null\n  },\n  {\n    \"k8s_version\": \"v1.27\",\n    \"patch\": null\n  },\n  {\n    \"k8s_version\": \"v1.28\",\n    \"patch\": null\n  },\n  {\n    \"k8s_version\": \"v1.29\",\n    \"patch\": null\n  },\n  {\n    \"k8s_version\": \"v1.30\",\n    \"patch\": null\n  }\n]"
  },
  {
    "path": ".github/workflows/milestone-worker.yml",
    "content": "name: Milestone Worker\n\non:\n  workflow_dispatch:\n    inputs:\n      build-zoo-handler:\n        description: 'Build Zoo Handler Payload'\n        required: true\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    steps:\n\n    - uses: actions/checkout@v4\n    - uses: actions/setup-java@v3\n      with:\n        java-version: '17'\n        distribution: 'liberica'\n    - uses: jfrog/setup-jfrog-cli@v3\n      env:\n        JF_URL: 'https://repo.spring.io'\n        JF_ENV_SPRING: ${{ secrets.JF_ARTIFACTORY_SPRING }}\n    - uses: actions/cache@v3\n      with:\n        path: ~/.m2/repository\n        key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}\n        restore-keys: |\n          ${{ runner.os }}-m2-\n\n    # target deploy repos\n    - name: Configure JFrog Cli\n      run: |\n        jfrog rt mvnc --use-wrapper \\\n          --server-id-resolve=${{ vars.JF_SERVER_ID }} \\\n          --server-id-deploy=${{ vars.JF_SERVER_ID }} \\\n          --repo-resolve-releases=libs-milestone \\\n          --repo-resolve-snapshots=libs-snapshot \\\n          --repo-deploy-releases=libs-milestone-local \\\n          --repo-deploy-snapshots=libs-snapshot-local\n        echo JFROG_CLI_BUILD_NAME=spring-cloud-deployer-main-milestone >> $GITHUB_ENV\n        echo JFROG_CLI_BUILD_NUMBER=$GITHUB_RUN_NUMBER >> $GITHUB_ENV\n\n    # zoo extract and ensure\n    - name: Extract Zoo Context Properties\n      uses: jvalkeal/build-zoo-handler@v0.0.4\n      with:\n        dispatch-handler-extract-context-properties: true\n        ensure-env: |\n          BUILD_ZOO_HANDLER_milestone_version\n\n    # build and publish to configured target\n    - name: Build and Publish\n      run: |\n        jfrog rt mvn build-helper:parse-version versions:set \\\n          -gs .github/settings.xml \\\n          -Pstagingmilestone \\\n          -DprocessAllModules=true \\\n          -DgenerateBackupPoms=false \\\n          -Dartifactory.publish.artifacts=false \\\n          -DnewVersion='${parsedVersion.majorVersion}.${parsedVersion.minorVersion}.${parsedVersion.incrementalVersion}-'${BUILD_ZOO_HANDLER_milestone_version} \\\n          -B\n        echo BUILD_ZOO_HANDLER_spring_cloud_deployer_version=$(./mvnw help:evaluate -Dexpression=project.version -q -DforceStdout) >> $GITHUB_ENV\n        jfrog rt build-clean\n        jfrog rt mvn clean install \\\n          -gs .github/settings.xml \\\n          -P-spring,stagingmilestone \\\n          -DskipTests -U -B -T 0.5C\n        jfrog rt build-publish\n        echo BUILD_ZOO_HANDLER_spring_cloud_deployer_buildname=spring-cloud-deployer-main-milestone >> $GITHUB_ENV\n        echo BUILD_ZOO_HANDLER_spring_cloud_deployer_buildnumber=$GITHUB_RUN_NUMBER >> $GITHUB_ENV\n    # zoo tag\n    - name: Tag Release\n      uses: jvalkeal/build-zoo-handler@v0.0.4\n      with:\n        tag-release-branch: ${{ env.BUILD_ZOO_HANDLER_spring_cloud_deployer_version }}\n        tag-release-tag: ${{ env.BUILD_ZOO_HANDLER_spring_cloud_deployer_version }}\n        tag-release-tag-prefix: v\n\n    # zoo success\n    - name: Notify Build Success Zoo Handler Controller\n      uses: jvalkeal/build-zoo-handler@v0.0.4\n      with:\n        dispatch-handler-token: ${{ secrets.SCDF_ACCESS_TOKEN }}\n        dispatch-handler-client-payload-data: >\n          {\n            \"event\": \"build-succeed\"\n          }\n\n    # zoo failure\n    - name: Notify Build Failure Zoo Handler Controller\n      if: ${{ failure() }}\n      uses: jvalkeal/build-zoo-handler@v0.0.4\n      with:\n        dispatch-handler-token: ${{ secrets.SCDF_ACCESS_TOKEN }}\n        dispatch-handler-client-payload-data: >\n          {\n            \"event\": \"build-failed\",\n            \"message\": \"spring-cloud-deployer failed\"\n          }\n    # clean m2 cache\n    - name: Clean cache\n      run: |\n        find ~/.m2/repository -type d -name '*SNAPSHOT' | xargs rm -fr\n  scan:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - name: Run Trivy vulnerability scanner in repo mode\n        uses: aquasecurity/trivy-action@master\n        with:\n          scan-type: 'fs'\n          ignore-unfixed: true\n          format: 'sarif'\n          output: 'trivy-results.sarif'\n          severity: 'CRITICAL,HIGH'\n      - name: Upload Trivy scan results to GitHub Security tab\n        uses: github/codeql-action/upload-sarif@v2\n        with:\n          sarif_file: 'trivy-results.sarif'\n      - name: 'Scanned'\n        shell: bash\n        run: echo \"::info ::Scanned\"\n  done:\n    runs-on: ubuntu-latest\n    needs: [ scan, build ]\n    steps:\n      - name: 'Done'\n        shell: bash\n        run: echo \"::info ::Done\"\n"
  },
  {
    "path": ".github/workflows/next-dev-version-worker.yml",
    "content": "name: Next Dev Version Worker\n\non:\n  workflow_dispatch:\n    inputs:\n      build-zoo-handler:\n        description: 'Build Zoo Handler Payload'\n        required: true\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    steps:\n\n    - uses: actions/checkout@v4\n    - uses: actions/setup-java@v3\n      with:\n        java-version: '17'\n        distribution: 'liberica'\n    - uses: jfrog/setup-jfrog-cli@v3\n      env:\n        JF_URL: 'https://repo.spring.io'\n        JF_ENV_SPRING: ${{ secrets.JF_ARTIFACTORY_SPRING }}\n\n    # cache maven .m2\n    - uses: actions/cache@v3\n      with:\n        path: ~/.m2/repository\n        key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}\n        restore-keys: |\n          ${{ runner.os }}-m2-\n\n    # target deploy repos\n    - name: Configure JFrog Cli\n      run: |\n        jfrog rt mvnc --use-wrapper \\\n          --server-id-resolve=${{ vars.JF_SERVER_ID }} \\\n          --server-id-deploy=${{ vars.JF_SERVER_ID }} \\\n          --repo-resolve-releases=libs-milestone \\\n          --repo-resolve-snapshots=libs-snapshot \\\n          --repo-deploy-releases=libs-release-local \\\n          --repo-deploy-snapshots=libs-snapshot-local\n        echo JFROG_CLI_BUILD_NAME=spring-cloud-deployer-main-ndv >> $GITHUB_ENV\n        echo JFROG_CLI_BUILD_NUMBER=$GITHUB_RUN_NUMBER >> $GITHUB_ENV\n\n    # zoo extract and ensure\n    - name: Extract Zoo Context Properties\n      uses: jvalkeal/build-zoo-handler@v0.0.4\n      with:\n        dispatch-handler-extract-context-properties: true\n\n    # build and publish to configured target\n    - name: Build and Publish\n      run: |\n        jfrog rt mvn build-helper:parse-version versions:set \\\n          -DprocessAllModules=true \\\n          -DgenerateBackupPoms=false \\\n          -Dartifactory.publish.artifacts=false \\\n          -DnewVersion='${parsedVersion.majorVersion}.${parsedVersion.minorVersion}.${parsedVersion.nextIncrementalVersion}-SNAPSHOT' \\\n          -B\n        echo BUILD_ZOO_HANDLER_spring_cloud_deployer_version=$(./mvnw help:evaluate -Dexpression=project.version -q -DforceStdout) >> $GITHUB_ENV\n        jfrog rt build-clean\n        jfrog rt mvn clean install -DskipTests -B -T 0.5C\n        jfrog rt build-publish\n        echo BUILD_ZOO_HANDLER_spring_cloud_deployer_buildname=spring-cloud-deployer-main-ndv >> $GITHUB_ENV\n        echo BUILD_ZOO_HANDLER_spring_cloud_deployer_buildnumber=$GITHUB_RUN_NUMBER >> $GITHUB_ENV\n\n    # zoo commit\n    - name: Commit Next Dev Changes\n      uses: jvalkeal/build-zoo-handler@v0.0.4\n      with:\n        commit-changes-branch: main\n        commit-changes-message: Next development version\n\n    # zoo success\n    - name: Notify Build Success Zoo Handler Controller\n      uses: jvalkeal/build-zoo-handler@v0.0.4\n      with:\n        dispatch-handler-token: ${{ secrets.SCDF_ACCESS_TOKEN }}\n        dispatch-handler-client-payload-data: >\n          {\n            \"event\": \"next-dev-version-succeed\"\n          }\n\n    # zoo failure\n    - name: Notify Build Failure Zoo Handler Controller\n      if: ${{ failure() }}\n      uses: jvalkeal/build-zoo-handler@v0.0.4\n      with:\n        dispatch-handler-token: ${{ secrets.SCDF_ACCESS_TOKEN }}\n        dispatch-handler-client-payload-data: >\n          {\n            \"event\": \"next-dev-version-failed\",\n            \"message\": \"spring-cloud-dataflow-build next version failed\"\n          }\n\n    # clean m2 cache\n    - name: Clean cache\n      run: |\n        find ~/.m2/repository -type d -name '*SNAPSHOT' | xargs rm -fr\n  scan:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - name: Run Trivy vulnerability scanner in repo mode\n        uses: aquasecurity/trivy-action@master\n        with:\n          scan-type: 'fs'\n          ignore-unfixed: true\n          format: 'sarif'\n          output: 'trivy-results.sarif'\n          severity: 'CRITICAL,HIGH'\n      - name: Upload Trivy scan results to GitHub Security tab\n        uses: github/codeql-action/upload-sarif@v2\n        with:\n          sarif_file: 'trivy-results.sarif'\n      - name: 'Scanned'\n        shell: bash\n        run: echo \"::info ::Scanned\"\n  done:\n    runs-on: ubuntu-latest\n    needs: [ scan, build ]\n    steps:\n      - name: 'Done'\n        shell: bash\n        run: echo \"::info ::Done\"\n"
  },
  {
    "path": ".github/workflows/release-worker.yml",
    "content": "name: Release Worker\n\non:\n  workflow_dispatch:\n    inputs:\n      build-zoo-handler:\n        description: 'Build Zoo Handler Payload'\n        required: true\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    steps:\n\n    - uses: actions/checkout@v4\n    - uses: actions/setup-java@v3\n      with:\n        java-version: '17'\n        distribution: 'liberica'\n    - uses: jfrog/setup-jfrog-cli@v3\n      env:\n        JF_URL: 'https://repo.spring.io'\n        JF_ENV_SPRING: ${{ secrets.JF_ARTIFACTORY_SPRING }}\n    - uses: actions/cache@v3\n      with:\n        path: ~/.m2/repository\n        key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}\n        restore-keys: |\n          ${{ runner.os }}-m2-\n\n    # target deploy repos\n    - name: Configure JFrog Cli\n      run: |\n        jfrog rt mvnc --use-wrapper \\\n          --server-id-resolve=${{ vars.JF_SERVER_ID }} \\\n          --server-id-deploy=${{ vars.JF_SERVER_ID }} \\\n          --repo-resolve-releases=libs-release-staging \\\n          --repo-resolve-snapshots=libs-snapshot \\\n          --repo-deploy-releases=libs-staging-local \\\n          --repo-deploy-snapshots=libs-snapshot-local\n        echo JFROG_CLI_BUILD_NAME=spring-cloud-deployer-main-release >> $GITHUB_ENV\n        echo JFROG_CLI_BUILD_NUMBER=$GITHUB_RUN_NUMBER >> $GITHUB_ENV\n\n    # zoo extract and ensure\n    - name: Extract Zoo Context Properties\n      uses: jvalkeal/build-zoo-handler@v0.0.4\n      with:\n        dispatch-handler-extract-context-properties: true\n\n    # build and publish to configured target\n    - name: Build and Publish\n      run: |\n        jfrog rt mvn build-helper:parse-version versions:set \\\n          -gs .github/settings.xml \\\n          -Pstagingrelease \\\n          -DprocessAllModules=true \\\n          -DgenerateBackupPoms=false \\\n          -Dartifactory.publish.artifacts=false \\\n          -DnewVersion='${parsedVersion.majorVersion}.${parsedVersion.minorVersion}.${parsedVersion.incrementalVersion}' \\\n          -B\n        echo BUILD_ZOO_HANDLER_spring_cloud_deployer_version=$(./mvnw help:evaluate -Dexpression=project.version -q -DforceStdout) >> $GITHUB_ENV\n        jfrog rt build-clean\n        jfrog rt mvn clean install \\\n          -gs .github/settings.xml \\\n          -P-spring,stagingrelease \\\n          -DskipTests -U -B -T 0.5C\n        jfrog rt build-publish\n        echo BUILD_ZOO_HANDLER_spring_cloud_deployer_buildname=spring-cloud-deployer-main-release >> $GITHUB_ENV\n        echo BUILD_ZOO_HANDLER_spring_cloud_deployer_buildnumber=$GITHUB_RUN_NUMBER >> $GITHUB_ENV\n    # zoo tag\n    - name: Tag Release\n      uses: jvalkeal/build-zoo-handler@v0.0.4\n      with:\n        tag-release-branch: ${{ env.BUILD_ZOO_HANDLER_spring_cloud_deployer_version }}\n        tag-release-tag: ${{ env.BUILD_ZOO_HANDLER_spring_cloud_deployer_version }}\n        tag-release-tag-prefix: v\n\n    # zoo success\n    - name: Notify Build Success Zoo Handler Controller\n      uses: jvalkeal/build-zoo-handler@v0.0.4\n      with:\n        dispatch-handler-token: ${{ secrets.SCDF_ACCESS_TOKEN }}\n        dispatch-handler-client-payload-data: >\n          {\n            \"event\": \"build-succeed\"\n          }\n\n    # zoo failure\n    - name: Notify Build Failure Zoo Handler Controller\n      if: ${{ failure() }}\n      uses: jvalkeal/build-zoo-handler@v0.0.4\n      with:\n        dispatch-handler-token: ${{ secrets.SCDF_ACCESS_TOKEN }}\n        dispatch-handler-client-payload-data: >\n          {\n            \"event\": \"build-failed\",\n            \"message\": \"spring-cloud-deployer failed\"\n          }\n    # clean m2 cache\n    - name: Clean cache\n      run: |\n        find ~/.m2/repository -type d -name '*SNAPSHOT' | xargs rm -fr\n  scan:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - name: Run Trivy vulnerability scanner in repo mode\n        uses: aquasecurity/trivy-action@master\n        with:\n          scan-type: 'fs'\n          ignore-unfixed: true\n          format: 'sarif'\n          output: 'trivy-results.sarif'\n          severity: 'CRITICAL,HIGH'\n      - name: Upload Trivy scan results to GitHub Security tab\n        uses: github/codeql-action/upload-sarif@v2\n        with:\n          sarif_file: 'trivy-results.sarif'\n      - name: 'Scanned'\n        shell: bash\n        run: echo \"::info ::Scanned\"\n  done:\n    runs-on: ubuntu-latest\n    needs: [ scan, build ]\n    steps:\n      - name: 'Done'\n        shell: bash\n        run: echo \"::info ::Done\"\n"
  },
  {
    "path": ".gitignore",
    "content": "*~\n.#*\n*#\n*.sw*\n_site/\n.factorypath\n.gradletasknamecache\n.DS_Store\n.checkstyle\n/application.yml\n/application.properties\nasciidoctor.css\natlassian-ide-plugin.xml\nbin/\nbuild/\ndump.rdb\nout\nspring-shell.log\ntarget/\ntest-output\n.jfrog/\n\n# Eclipse artifacts, including WTP generated manifests\n.classpath\n.project\n.settings/\n.springBeans\nspring-*/src/main/java/META-INF/MANIFEST.MF\n\n# IDEA artifacts and output dirs\n*.iml\n*.ipr\n*.iws\n.idea/\nrebel.xml\n\n# Visual Studio Code\n.vscode/*\n!.vscode/settings.json\n!.vscode/tasks.json\n!.vscode/launch.json\n!.vscode/extensions.json\n"
  },
  {
    "path": ".mvn/jvm.config",
    "content": "-Xmx1024m -XX:CICompilerCount=1 -XX:TieredStopAtLevel=1 -Djava.security.egd=file:/dev/./urandom"
  },
  {
    "path": ".mvn/maven.config",
    "content": "-DaltSnapshotDeploymentRepository=repo.spring.io::default::https://repo.spring.io/libs-snapshot-local -P spring\n"
  },
  {
    "path": ".mvn/wrapper/maven-wrapper.properties",
    "content": "distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.8/apache-maven-3.8.8-bin.zip\n"
  },
  {
    "path": ".settings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<settings>\n  <servers>\n\t<server>\n\t  <id>repo.spring.io</id>\n\t  <username>${env.CI_DEPLOY_USERNAME}</username>\n\t  <password>${env.CI_DEPLOY_PASSWORD}</password>\n\t</server>\n  </servers>\n  <profiles>\n\t<profile>\n\t  <id>spring</id>\n\t  <activation><activeByDefault>true</activeByDefault></activation>\n\t  <repositories>\n\t\t  <repository>\n\t\t\t  <id>maven-central</id>\n\t\t\t  <name>Maven Central</name>\n\t\t\t  <url>https://repo.maven.apache.org/maven2</url>\n\t\t\t  <snapshots>\n\t\t\t\t  <enabled>false</enabled>\n\t\t\t  </snapshots>\n\t\t  </repository>\n\t\t  <repository>\n\t\t\t<id>spring-snapshots</id>\n\t\t\t<name>Spring Snapshots</name>\n\t\t\t<url>https://repo.spring.io/libs-snapshot</url>\n\t\t\t<snapshots>\n\t\t\t\t<enabled>true</enabled>\n\t\t\t</snapshots>\n\t\t</repository>\n\t\t  <repository>\n\t\t\t<id>spring-milestones</id>\n\t\t\t<name>Spring Milestones</name>\n\t\t\t<url>https://repo.spring.io/libs-milestone</url>\n\t\t\t<snapshots>\n\t\t\t\t<enabled>false</enabled>\n\t\t\t</snapshots>\n\t\t</repository>\n\t  </repositories>\n\t  <pluginRepositories>\n\t\t  <pluginRepository>\n\t\t\t  <id>maven-central</id>\n\t\t\t  <name>Maven Central</name>\n\t\t\t  <url>https://repo.maven.apache.org/maven2</url>\n\t\t\t  <snapshots>\n\t\t\t\t  <enabled>false</enabled>\n\t\t\t  </snapshots>\n\t\t  </pluginRepository>\n\t\t  <pluginRepository>\n\t\t\t<id>spring-snapshots</id>\n\t\t\t<name>Spring Snapshots</name>\n\t\t\t<url>https://repo.spring.io/libs-snapshot</url>\n\t\t\t<snapshots>\n\t\t\t\t<enabled>true</enabled>\n\t\t\t</snapshots>\n\t\t</pluginRepository>\n\t\t  <pluginRepository>\n\t\t\t<id>spring-milestones</id>\n\t\t\t<name>Spring Milestones</name>\n\t\t\t<url>https://repo.spring.io/libs-milestone</url>\n\t\t\t<snapshots>\n\t\t\t\t<enabled>false</enabled>\n\t\t\t</snapshots>\n\t\t</pluginRepository>\n\t  </pluginRepositories>\n    </profile>\n  </profiles>\n</settings>\n"
  },
  {
    "path": ".trivyignore",
    "content": "CVE-2022-1471\nCVE-2016-1000027"
  },
  {
    "path": "CONTRIBUTING.adoc",
    "content": "= Contributing to Spring Cloud Deployer\n\n:github: https://github.com/spring-cloud/spring-cloud-deployer\n\nSpring Cloud Deployer is released under the Apache 2.0 license. If you would like to contribute something, or want to hack on the code this document should help you get started.\n\n\n== Using GitHub Issues\nWe use GitHub issues to track bugs and enhancements.\nIf you have a general usage question please ask on https://stackoverflow.com[Stack Overflow].\nThe Spring Cloud Deployer team and the broader community monitor the https://stackoverflow.com/tags/spring-cloud-deployer[`spring-cloud-deployer`] tag.\n\nIf you are reporting a bug, please help to speed up problem diagnosis by providing as much information as possible.\nIdeally, that would include a small sample project that reproduces the problem.\n\n\n\n== Reporting Security Vulnerabilities\nIf you think you have found a security vulnerability in Spring Cloud Deployer please *DO NOT* disclose it publicly until we've had a chance to fix it.\nPlease don't report security vulnerabilities using GitHub issues, instead head over to https://spring.io/security-policy and learn how to disclose them responsibly.\n\n\n\n== Developer Certificate of Origin\nAll commits must include a **Signed-off-by** trailer at the end of each commit message to indicate that the contributor agrees to the Developer Certificate of Origin.\nFor additional details, please refer to the blog post https://spring.io/blog/2025/01/06/hello-dco-goodbye-cla-simplifying-contributions-to-spring[Hello DCO, Goodbye CLA: Simplifying Contributions to Spring].\n\n\n=== Code Conventions and Housekeeping\n\nNone of the following guidelines is essential for a pull request, but they all help your fellow developers understand and work with your code.\nThey can also be added after the original pull request but before a merge.\n\n* Use the Spring Framework code format conventions. If you use Eclipse, you can import formatter settings by using the `eclipse-code-formatter.xml` file from the https://github.com/spring-cloud/spring-cloud-build/blob/master/spring-cloud-dependencies-parent/eclipse-code-formatter.xml[Spring Cloud Build] project.\nIf you use IntelliJ, you can use the https://plugins.jetbrains.com/plugin/6546[Eclipse Code Formatter Plugin] to import the same file.\n* Make sure all new `.java` files have a simple Javadoc class comment with at least an  `@author` tag identifying you, and preferably at least a paragraph describing the class's purpose.\n* Add the ASF license header comment to all new `.java` files (to do so, copy it from existing files in the project).\n* Add yourself as an `@author` to the .java files that you modify substantially (more than cosmetic changes).\n* Add some Javadocs and, if you change the namespace, some XSD doc elements.\n* A few unit tests would help a lot as well. Someone has to do it, and your fellow developers appreciate the effort.\n* If no one else uses your branch, rebase it against the current master (or other target branch in the main project).\n* When writing a commit message, follow https://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html[these conventions].\nIf you fix an existing issue, add `Fixes gh-XXXX` (where XXXX is the issue number) at the end of the commit message.\n"
  },
  {
    "path": "LICENSE",
    "content": "                                 Apache License\n                           Version 2.0, January 2004\n                        https://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"{}\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright {yyyy} {name of copyright owner}\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       https://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n\n"
  },
  {
    "path": "NOTICE",
    "content": "spring-cloud-deployer\n\nCopyright (c) 2016-Present Pivotal Software, Inc. All Rights Reserved.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n  https://www.apache.org/licenses/LICENSE-2.0\n \nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License."
  },
  {
    "path": "README.adoc",
    "content": "# Spring Cloud Data Flow is no longer maintained as an open-source project by Broadcom, Inc.\n\n## For information about extended support or enterprise options for Spring Cloud Data Flow, please read the official blog post [here](https://spring.io/blog/2025/04/21/spring-cloud-data-flow-commercial).\n\n== Spring Cloud Deployer\n\n[frame=none, grid=none, caption=, width=\"75%\", cols=\"^2,^2\"]\n.Build Status by Branches\n|===\n| *Main* | *2.9.x*\n|===\n\nThe Spring Cloud Deployer project defines a Service Provider Interface (SPI) for deploying long lived applications and short lived tasks.\n\n== Components\n\nThe https://github.com/spring-cloud/spring-cloud-deployer/tree/master/spring-cloud-deployer-spi[SPI] project\ndefines the core interfaces, including https://github.com/spring-cloud/spring-cloud-deployer/blob/master/spring-cloud-deployer-spi/src/main/java/org/springframework/cloud/deployer/spi/app/AppDeployer.java[AppDeployer]\nand https://github.com/spring-cloud/spring-cloud-deployer/blob/master/spring-cloud-deployer-spi/src/main/java/org/springframework/cloud/deployer/spi/task/TaskLauncher.java[TaskLauncher]\nas well as the core domain model.\n\nThe https://github.com/spring-cloud/spring-cloud-deployer/tree/master/spring-cloud-deployer-spi-test[SPI Test] project provides\nthe basic test framework that any SPI implementation should use to verify its functionality.\n\nThe https://github.com/spring-cloud/spring-cloud-deployer/tree/master/spring-cloud-deployer-resource-maven[spring-cloud-deployer-resource-maven]\nproject provides support for referencing Maven artifacts via Spring's `Resource` abstraction.\n\nThe https://github.com/spring-cloud/spring-cloud-deployer/tree/master/spring-cloud-deployer-resource-docker[spring-cloud-deployer-resource-docker]\nproject provides support for referencing Docker artifacts via Spring's `Resource` abstraction.\n\nThe https://github.com/spring-cloud/spring-cloud-deployer/tree/master/spring-cloud-deployer-resource-support[spring-cloud-deployer-resource-support]\nproject provides various common support classes for working with `Resources`, such as the\nhttps://github.com/spring-cloud/spring-cloud-deployer/blob/master/spring-cloud-deployer-resource-support/src/main/java/org/springframework/cloud/deployer/resource/registry/UriRegistry.java[UriRegistry]\nfor maintaining the locations of app artifacts, and the\nhttps://github.com/spring-cloud/spring-cloud-deployer/blob/master/spring-cloud-deployer-resource-support/src/main/java/org/springframework/cloud/deployer/resource/support/DelegatingResourceLoader.java[DelegatingResourceLoader]\nfor working with multiple `ResourceLoader` implementations in a map with URI schemes as keys.\n\nThere is also an implementation of the SPI for running apps locally. That link is provided below along with other SPI implementations.\n\n== Implementations\n\nThis deployer SPI has been implemented for several runtime environments. Here are the github locations:\n\n* https://github.com/spring-cloud/spring-cloud-deployer/blob/master/spring-cloud-deployer-local[Local]\n* https://github.com/spring-cloud/spring-cloud-deployer/blob/master/spring-cloud-deployer-cloudfoundry[Cloud Foundry]\n* https://github.com/spring-cloud/spring-cloud-deployer/blob/master/spring-cloud-deployer-kubernetes[Kubernetes]\n\n=== Building\n\nClone the repo and type \n\n----\n$ ./mvnw clean install \n----\n\n## Contributing\n\nWe welcome contributions! See the link:./CONTRIBUTING.adoc[CONTRIBUTING] guide for details.\n"
  },
  {
    "path": "SECURITY.md",
    "content": "# Security Policy\n## Reporting a Vulnerability\n\nIf you think you have found a security vulnerability, please **DO NOT** disclose it publicly until we’ve had a chance to fix it.\nPlease don’t report security vulnerabilities using GitHub issues, instead head over to https://spring.io/security-policy and learn how to disclose them responsibly.\n"
  },
  {
    "path": "mvnw",
    "content": "#!/bin/sh\n# ----------------------------------------------------------------------------\n# Licensed to the Apache Software Foundation (ASF) under one\n# or more contributor license agreements.  See the NOTICE file\n# distributed with this work for additional information\n# regarding copyright ownership.  The ASF licenses this file\n# to you under the Apache License, Version 2.0 (the\n# \"License\"); you may not use this file except in compliance\n# with the License.  You may obtain a copy of the License at\n#\n#    https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing,\n# software distributed under the License is distributed on an\n# \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n# KIND, either express or implied.  See the License for the\n# specific language governing permissions and limitations\n# under the License.\n# ----------------------------------------------------------------------------\n\n# ----------------------------------------------------------------------------\n# Maven2 Start Up Batch script\n#\n# Required ENV vars:\n# ------------------\n#   JAVA_HOME - location of a JDK home dir\n#\n# Optional ENV vars\n# -----------------\n#   M2_HOME - location of maven2's installed home dir\n#   MAVEN_OPTS - parameters passed to the Java VM when running Maven\n#     e.g. to debug Maven itself, use\n#       set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000\n#   MAVEN_SKIP_RC - flag to disable loading of mavenrc files\n# ----------------------------------------------------------------------------\n\nif [ -z \"$MAVEN_SKIP_RC\" ] ; then\n\n  if [ -f /etc/mavenrc ] ; then\n    . /etc/mavenrc\n  fi\n\n  if [ -f \"$HOME/.mavenrc\" ] ; then\n    . \"$HOME/.mavenrc\"\n  fi\n\nfi\n\n# OS specific support.  $var _must_ be set to either true or false.\ncygwin=false;\ndarwin=false;\nmingw=false\ncase \"`uname`\" in\n  CYGWIN*) cygwin=true ;;\n  MINGW*) mingw=true;;\n  Darwin*) darwin=true\n           #\n           # Look for the Apple JDKs first to preserve the existing behaviour, and then look\n           # for the new JDKs provided by Oracle.\n           # \n           if [ -z \"$JAVA_HOME\" ] && [ -L /System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK ] ; then\n             #\n             # Apple JDKs\n             #\n             export JAVA_HOME=/System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK/Home\n           fi\n           \n           if [ -z \"$JAVA_HOME\" ] && [ -L /System/Library/Java/JavaVirtualMachines/CurrentJDK ] ; then\n             #\n             # Apple JDKs\n             #\n             export JAVA_HOME=/System/Library/Java/JavaVirtualMachines/CurrentJDK/Contents/Home\n           fi\n             \n           if [ -z \"$JAVA_HOME\" ] && [ -L \"/Library/Java/JavaVirtualMachines/CurrentJDK\" ] ; then\n             #\n             # Oracle JDKs\n             #\n             export JAVA_HOME=/Library/Java/JavaVirtualMachines/CurrentJDK/Contents/Home\n           fi           \n\n           if [ -z \"$JAVA_HOME\" ] && [ -x \"/usr/libexec/java_home\" ]; then\n             #\n             # Apple JDKs\n             #\n             export JAVA_HOME=`/usr/libexec/java_home`\n           fi\n           ;;\nesac\n\nif [ -z \"$JAVA_HOME\" ] ; then\n  if [ -r /etc/gentoo-release ] ; then\n    JAVA_HOME=`java-config --jre-home`\n  fi\nfi\n\nif [ -z \"$M2_HOME\" ] ; then\n  ## resolve links - $0 may be a link to maven's home\n  PRG=\"$0\"\n\n  # need this for relative symlinks\n  while [ -h \"$PRG\" ] ; do\n    ls=`ls -ld \"$PRG\"`\n    link=`expr \"$ls\" : '.*-> \\(.*\\)$'`\n    if expr \"$link\" : '/.*' > /dev/null; then\n      PRG=\"$link\"\n    else\n      PRG=\"`dirname \"$PRG\"`/$link\"\n    fi\n  done\n\n  saveddir=`pwd`\n\n  M2_HOME=`dirname \"$PRG\"`/..\n\n  # make it fully qualified\n  M2_HOME=`cd \"$M2_HOME\" && pwd`\n\n  cd \"$saveddir\"\n  # echo Using m2 at $M2_HOME\nfi\n\n# For Cygwin, ensure paths are in UNIX format before anything is touched\nif $cygwin ; then\n  [ -n \"$M2_HOME\" ] &&\n    M2_HOME=`cygpath --unix \"$M2_HOME\"`\n  [ -n \"$JAVA_HOME\" ] &&\n    JAVA_HOME=`cygpath --unix \"$JAVA_HOME\"`\n  [ -n \"$CLASSPATH\" ] &&\n    CLASSPATH=`cygpath --path --unix \"$CLASSPATH\"`\nfi\n\n# For Migwn, ensure paths are in UNIX format before anything is touched\nif $mingw ; then\n  [ -n \"$M2_HOME\" ] &&\n    M2_HOME=\"`(cd \"$M2_HOME\"; pwd)`\"\n  [ -n \"$JAVA_HOME\" ] &&\n    JAVA_HOME=\"`(cd \"$JAVA_HOME\"; pwd)`\"\n  # TODO classpath?\nfi\n\nif [ -z \"$JAVA_HOME\" ]; then\n  javaExecutable=\"`which javac`\"\n  if [ -n \"$javaExecutable\" ] && ! [ \"`expr \\\"$javaExecutable\\\" : '\\([^ ]*\\)'`\" = \"no\" ]; then\n    # readlink(1) is not available as standard on Solaris 10.\n    readLink=`which readlink`\n    if [ ! `expr \"$readLink\" : '\\([^ ]*\\)'` = \"no\" ]; then\n      if $darwin ; then\n        javaHome=\"`dirname \\\"$javaExecutable\\\"`\"\n        javaExecutable=\"`cd \\\"$javaHome\\\" && pwd -P`/javac\"\n      else\n        javaExecutable=\"`readlink -f \\\"$javaExecutable\\\"`\"\n      fi\n      javaHome=\"`dirname \\\"$javaExecutable\\\"`\"\n      javaHome=`expr \"$javaHome\" : '\\(.*\\)/bin'`\n      JAVA_HOME=\"$javaHome\"\n      export JAVA_HOME\n    fi\n  fi\nfi\n\nif [ -z \"$JAVACMD\" ] ; then\n  if [ -n \"$JAVA_HOME\"  ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n      # IBM's JDK on AIX uses strange locations for the executables\n      JAVACMD=\"$JAVA_HOME/jre/sh/java\"\n    else\n      JAVACMD=\"$JAVA_HOME/bin/java\"\n    fi\n  else\n    JAVACMD=\"`which java`\"\n  fi\nfi\n\nif [ ! -x \"$JAVACMD\" ] ; then\n  echo \"Error: JAVA_HOME is not defined correctly.\" >&2\n  echo \"  We cannot execute $JAVACMD\" >&2\n  exit 1\nfi\n\nif [ -z \"$JAVA_HOME\" ] ; then\n  echo \"Warning: JAVA_HOME environment variable is not set.\"\nfi\n\nCLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher\n\n# For Cygwin, switch paths to Windows format before running java\nif $cygwin; then\n  [ -n \"$M2_HOME\" ] &&\n    M2_HOME=`cygpath --path --windows \"$M2_HOME\"`\n  [ -n \"$JAVA_HOME\" ] &&\n    JAVA_HOME=`cygpath --path --windows \"$JAVA_HOME\"`\n  [ -n \"$CLASSPATH\" ] &&\n    CLASSPATH=`cygpath --path --windows \"$CLASSPATH\"`\nfi\n\n# traverses directory structure from process work directory to filesystem root\n# first directory with .mvn subdirectory is considered project base directory\nfind_maven_basedir() {\n  local basedir=$(pwd)\n  local wdir=$(pwd)\n  while [ \"$wdir\" != '/' ] ; do\n    if [ -d \"$wdir\"/.mvn ] ; then\n      basedir=$wdir\n      break\n    fi\n    wdir=$(cd \"$wdir/..\"; pwd)\n  done\n  echo \"${basedir}\"\n}\n\n# concatenates all lines of a file\nconcat_lines() {\n  if [ -f \"$1\" ]; then\n    echo \"$(tr -s '\\n' ' ' < \"$1\")\"\n  fi\n}\n\nexport MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-$(find_maven_basedir)}\nMAVEN_OPTS=\"$(concat_lines \"$MAVEN_PROJECTBASEDIR/.mvn/jvm.config\") $MAVEN_OPTS\"\n\n# Provide a \"standardized\" way to retrieve the CLI args that will \n# work with both Windows and non-Windows executions.\nMAVEN_CMD_LINE_ARGS=\"$MAVEN_CONFIG $@\"\nexport MAVEN_CMD_LINE_ARGS\n\nWRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain\n\nexec \"$JAVACMD\" \\\n  $MAVEN_OPTS \\\n  -classpath \"$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar\" \\\n  \"-Dmaven.home=${M2_HOME}\" \"-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}\" \\\n  ${WRAPPER_LAUNCHER} \"$@\"\n\n"
  },
  {
    "path": "mvnw.cmd",
    "content": "@REM ----------------------------------------------------------------------------\n@REM Licensed to the Apache Software Foundation (ASF) under one\n@REM or more contributor license agreements.  See the NOTICE file\n@REM distributed with this work for additional information\n@REM regarding copyright ownership.  The ASF licenses this file\n@REM to you under the Apache License, Version 2.0 (the\n@REM \"License\"); you may not use this file except in compliance\n@REM with the License.  You may obtain a copy of the License at\n@REM\n@REM    https://www.apache.org/licenses/LICENSE-2.0\n@REM\n@REM Unless required by applicable law or agreed to in writing,\n@REM software distributed under the License is distributed on an\n@REM \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n@REM KIND, either express or implied.  See the License for the\n@REM specific language governing permissions and limitations\n@REM under the License.\n@REM ----------------------------------------------------------------------------\n\n@REM ----------------------------------------------------------------------------\n@REM Maven2 Start Up Batch script\n@REM\n@REM Required ENV vars:\n@REM JAVA_HOME - location of a JDK home dir\n@REM\n@REM Optional ENV vars\n@REM M2_HOME - location of maven2's installed home dir\n@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands\n@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending\n@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven\n@REM     e.g. to debug Maven itself, use\n@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000\n@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files\n@REM ----------------------------------------------------------------------------\n\n@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'\n@echo off\n@REM enable echoing my setting MAVEN_BATCH_ECHO to 'on'\n@if \"%MAVEN_BATCH_ECHO%\" == \"on\"  echo %MAVEN_BATCH_ECHO%\n\n@REM set %HOME% to equivalent of $HOME\nif \"%HOME%\" == \"\" (set \"HOME=%HOMEDRIVE%%HOMEPATH%\")\n\n@REM Execute a user defined script before this one\nif not \"%MAVEN_SKIP_RC%\" == \"\" goto skipRcPre\n@REM check for pre script, once with legacy .bat ending and once with .cmd ending\nif exist \"%HOME%\\mavenrc_pre.bat\" call \"%HOME%\\mavenrc_pre.bat\"\nif exist \"%HOME%\\mavenrc_pre.cmd\" call \"%HOME%\\mavenrc_pre.cmd\"\n:skipRcPre\n\n@setlocal\n\nset ERROR_CODE=0\n\n@REM To isolate internal variables from possible post scripts, we use another setlocal\n@setlocal\n\n@REM ==== START VALIDATION ====\nif not \"%JAVA_HOME%\" == \"\" goto OkJHome\n\necho.\necho Error: JAVA_HOME not found in your environment. >&2\necho Please set the JAVA_HOME variable in your environment to match the >&2\necho location of your Java installation. >&2\necho.\ngoto error\n\n:OkJHome\nif exist \"%JAVA_HOME%\\bin\\java.exe\" goto init\n\necho.\necho Error: JAVA_HOME is set to an invalid directory. >&2\necho JAVA_HOME = \"%JAVA_HOME%\" >&2\necho Please set the JAVA_HOME variable in your environment to match the >&2\necho location of your Java installation. >&2\necho.\ngoto error\n\n@REM ==== END VALIDATION ====\n\n:init\n\nset MAVEN_CMD_LINE_ARGS=%*\n\n@REM Find the project base dir, i.e. the directory that contains the folder \".mvn\".\n@REM Fallback to current working directory if not found.\n\nset MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%\nIF NOT \"%MAVEN_PROJECTBASEDIR%\"==\"\" goto endDetectBaseDir\n\nset EXEC_DIR=%CD%\nset WDIR=%EXEC_DIR%\n:findBaseDir\nIF EXIST \"%WDIR%\"\\.mvn goto baseDirFound\ncd ..\nIF \"%WDIR%\"==\"%CD%\" goto baseDirNotFound\nset WDIR=%CD%\ngoto findBaseDir\n\n:baseDirFound\nset MAVEN_PROJECTBASEDIR=%WDIR%\ncd \"%EXEC_DIR%\"\ngoto endDetectBaseDir\n\n:baseDirNotFound\nset MAVEN_PROJECTBASEDIR=%EXEC_DIR%\ncd \"%EXEC_DIR%\"\n\n:endDetectBaseDir\n\nIF NOT EXIST \"%MAVEN_PROJECTBASEDIR%\\.mvn\\jvm.config\" goto endReadAdditionalConfig\n\n@setlocal EnableExtensions EnableDelayedExpansion\nfor /F \"usebackq delims=\" %%a in (\"%MAVEN_PROJECTBASEDIR%\\.mvn\\jvm.config\") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a\n@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%\n\n:endReadAdditionalConfig\n\nSET MAVEN_JAVA_EXE=\"%JAVA_HOME%\\bin\\java.exe\"\n\nset WRAPPER_JAR=\"\".\\.mvn\\wrapper\\maven-wrapper.jar\"\"\nset WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain\n\n%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% \"-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%\" %WRAPPER_LAUNCHER% %MAVEN_CMD_LINE_ARGS%\nif ERRORLEVEL 1 goto error\ngoto end\n\n:error\nset ERROR_CODE=1\n\n:end\n@endlocal & set ERROR_CODE=%ERROR_CODE%\n\nif not \"%MAVEN_SKIP_RC%\" == \"\" goto skipRcPost\n@REM check for post script, once with legacy .bat ending and once with .cmd ending\nif exist \"%HOME%\\mavenrc_post.bat\" call \"%HOME%\\mavenrc_post.bat\"\nif exist \"%HOME%\\mavenrc_post.cmd\" call \"%HOME%\\mavenrc_post.cmd\"\n:skipRcPost\n\n@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'\nif \"%MAVEN_BATCH_PAUSE%\" == \"on\" pause\n\nif \"%MAVEN_TERMINATE_CMD%\" == \"on\" exit %ERROR_CODE%\n\nexit /B %ERROR_CODE%\n"
  },
  {
    "path": "pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd\">\n\t<modelVersion>4.0.0</modelVersion>\n\n\t<artifactId>spring-cloud-deployer-parent</artifactId>\n\t<version>3.0.0-SNAPSHOT</version>\n\t<groupId>org.springframework.cloud</groupId>\n\t<packaging>pom</packaging>\n\n\t<name>Spring Cloud Deployer</name>\n\t<description>Spring Cloud Deployer</description>\n\t<organization>\n\t\t<name>Pivotal Software, Inc.</name>\n\t\t<url>https://www.spring.io</url>\n\t</organization>\n\t<url>https://github.com/spring-cloud/spring-cloud-deployer</url>\n\t<licenses>\n\t\t<license>\n\t\t\t<name>Apache License, Version 2.0</name>\n\t\t\t<url>https://www.apache.org/licenses/LICENSE-2.0</url>\n\t\t\t<comments>\n\t\t\t\tCopyright 2014-2021 the original author or authors.\n\n\t\t\t\tLicensed under the Apache License, Version 2.0 (the \"License\");\n\t\t\t\tyou may not use this file except in compliance with the License.\n\t\t\t\tYou may obtain a copy of the License at\n\n\t\t\t\thttps://www.apache.org/licenses/LICENSE-2.0\n\n\t\t\t\tUnless required by applicable law or agreed to in writing, software\n\t\t\t\tdistributed under the License is distributed on an \"AS IS\" BASIS,\n\t\t\t\tWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n\t\t\t\timplied.\n\n\t\t\t\tSee the License for the specific language governing permissions and\n\t\t\t\tlimitations under the License.\n\t\t\t</comments>\n\t\t</license>\n\t</licenses>\n\t<scm>\n\t\t<url>https://github.com/spring-cloud/spring-cloud-deployer</url>\n\t\t<connection>scm:git:git://github.com/spring-cloud/spring-cloud-deployer.git</connection>\n\t\t<developerConnection>scm:git:ssh://git@github.com/spring-cloud/spring-cloud-deployer.git</developerConnection>\n\t\t<tag>HEAD</tag>\n\t</scm>\n\t<developers>\n\t\t<developer>\n\t\t\t<id>scdf-team</id>\n\t\t\t<name>Data Flow Team</name>\n\t\t\t<organizationUrl>https://github.com/spring-cloud/spring-cloud-deployer/graphs/contributors</organizationUrl>\n\t\t</developer>\n\t</developers>\n\t<properties>\n\t\t<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>\n\t\t<java.version>17</java.version>\n\n\t\t<!-- ==================== -->\n\t\t<!-- Dependency Versions  -->\n\t\t<!-- ==================== -->\n\t\t<spring-boot.version>3.4.5</spring-boot.version>\n\t\t<commons-compress.version>1.26.2</commons-compress.version>\n\t\t<commons-io.version>2.16.1</commons-io.version>\n\n\t\t<!-- Kubernetes Deployer -->\n\t\t<kubernetes-fabric8-client.version>6.13.5</kubernetes-fabric8-client.version>\n\t\t<hashids.version>1.0.1</hashids.version>\n\n\t\t<!-- Cloudfoundry Deployer -->\n\t\t<!-- @TODO boot3 updates need for jakarta ee -->\n\t\t<cloudfoundry-java-lib.version>5.12.1.RELEASE</cloudfoundry-java-lib.version>\n\t\t<pivotal-cf-client-reactor.version>2.2.0.RELEASE</pivotal-cf-client-reactor.version>\n\n\t\t<!-- ==================== -->\n\t\t<!-- Plugin Versions      -->\n\t\t<!-- ==================== -->\n\t\t<!-- @TODO boot3 waiting for 4.x to support jakarta ee -->\n\t\t<maven.version>3.9.6</maven.version>\n\t\t<!-- @TODO boot3 waiting for 4.x to support jakarta ee -->\n\t\t<maven-resolver.version>1.9.18</maven-resolver.version>\n\t\t<maven-wagon.version>3.5.3</maven-wagon.version>\n\t</properties>\n\n\t<modules>\n\t\t<module>spring-cloud-deployer-spi</module>\n\t\t<module>spring-cloud-deployer-resource-docker</module>\n\t\t<module>spring-cloud-deployer-resource-maven</module>\n\t\t<module>spring-cloud-deployer-spi-test</module>\n\t\t<module>spring-cloud-deployer-resource-support</module>\n\t\t<module>spring-cloud-deployer-spi-test-app</module>\n\t\t<module>spring-cloud-deployer-spi-scheduler-test-app</module>\n\t\t<module>spring-cloud-deployer-autoconfigure</module>\n\t\t<module>spring-cloud-deployer-dependencies</module>\n\t\t<module>spring-cloud-deployer-local</module>\n\t\t<module>spring-cloud-deployer-kubernetes</module>\n\t\t<module>spring-cloud-deployer-cloudfoundry</module>\n\t</modules>\n\n\t<dependencyManagement>\n\t\t<dependencies>\n\t\t\t<dependency>\n\t\t\t\t<groupId>io.fabric8</groupId>\n\t\t\t\t<artifactId>kubernetes-client-bom</artifactId>\n\t\t\t\t<version>${kubernetes-fabric8-client.version}</version>\n\t\t\t\t<type>pom</type>\n\t\t\t\t<scope>import</scope>\n\t\t\t</dependency>\n\t\t\t<dependency>\n\t\t\t\t<groupId>org.apache.commons</groupId>\n\t\t\t\t<artifactId>commons-compress</artifactId>\n\t\t\t\t<version>${commons-compress.version}</version>\n\t\t\t</dependency>\n\t\t\t<dependency>\n\t\t\t\t<groupId>commons-io</groupId>\n\t\t\t\t<artifactId>commons-io</artifactId>\n\t\t\t\t<version>${commons-io.version}</version>\n\t\t\t</dependency>\n\t\t\t<dependency>\n\t\t\t\t<groupId>org.springframework.boot</groupId>\n\t\t\t\t<artifactId>spring-boot-dependencies</artifactId>\n\t\t\t\t<version>${spring-boot.version}</version>\n\t\t\t\t<type>pom</type>\n\t\t\t\t<scope>import</scope>\n\t\t\t</dependency>\n\t\t\t<dependency>\n\t\t\t\t<groupId>org.springframework.cloud</groupId>\n\t\t\t\t<artifactId>spring-cloud-deployer-dependencies</artifactId>\n\t\t\t\t<version>${project.version}</version>\n\t\t\t\t<type>pom</type>\n\t\t\t\t<scope>import</scope>\n\t\t\t</dependency>\n\t\t\t<dependency>\n\t\t\t\t<groupId>org.apache.maven.wagon</groupId>\n\t\t\t\t<artifactId>wagon-http</artifactId>\n\t\t\t\t<version>${maven-wagon.version}</version>\n\t\t\t</dependency>\n\t\t\t<dependency>\n\t\t\t\t<groupId>org.cloudfoundry</groupId>\n\t\t\t\t<artifactId>cloudfoundry-client-reactor</artifactId>\n\t\t\t\t<version>${cloudfoundry-java-lib.version}</version>\n\t\t\t</dependency>\n\t\t\t<dependency>\n\t\t\t\t<groupId>org.cloudfoundry</groupId>\n\t\t\t\t<artifactId>cloudfoundry-operations</artifactId>\n\t\t\t\t<version>${cloudfoundry-java-lib.version}</version>\n\t\t\t</dependency>\n\t\t\t<dependency>\n\t\t\t\t<groupId>io.pivotal</groupId>\n\t\t\t\t<artifactId>pivotal-cloudfoundry-client-reactor</artifactId>\n\t\t\t\t<version>${pivotal-cf-client-reactor.version}</version>\n\t\t\t</dependency>\n\t\t</dependencies>\n\t</dependencyManagement>\n\n\t<profiles>\n\t\t<profile>\n\t\t\t<id>spring</id>\n\t\t\t<activation><activeByDefault>true</activeByDefault></activation>\n\t\t\t<repositories>\n\t\t\t\t<repository>\n\t\t\t\t\t<id>maven-central</id>\n\t\t\t\t\t<name>Maven Central</name>\n\t\t\t\t\t<url>https://repo.maven.apache.org/maven2</url>\n\t\t\t\t\t<snapshots>\n\t\t\t\t\t\t<enabled>false</enabled>\n\t\t\t\t\t</snapshots>\n\t\t\t\t</repository>\n\t\t\t\t<repository>\n\t\t\t\t\t<id>spring-snapshots</id>\n\t\t\t\t\t<name>Spring Snapshots</name>\n\t\t\t\t\t<url>https://repo.spring.io/libs-snapshot</url>\n\t\t\t\t\t<snapshots>\n\t\t\t\t\t\t<enabled>true</enabled>\n\t\t\t\t\t</snapshots>\n\t\t\t\t</repository>\n\t\t\t\t<repository>\n\t\t\t\t\t<id>spring-milestones</id>\n\t\t\t\t\t<name>Spring Milestones</name>\n\t\t\t\t\t<url>https://repo.spring.io/libs-milestone</url>\n\t\t\t\t\t<snapshots>\n\t\t\t\t\t\t<enabled>false</enabled>\n\t\t\t\t\t</snapshots>\n\t\t\t\t</repository>\n\t\t\t</repositories>\n\t\t\t<pluginRepositories>\n\t\t\t\t<pluginRepository>\n\t\t\t\t\t<id>maven-central</id>\n\t\t\t\t\t<name>Maven Central</name>\n\t\t\t\t\t<url>https://repo.maven.apache.org/maven2</url>\n\t\t\t\t\t<snapshots>\n\t\t\t\t\t\t<enabled>false</enabled>\n\t\t\t\t\t</snapshots>\n\t\t\t\t</pluginRepository>\n\t\t\t\t<pluginRepository>\n\t\t\t\t\t<id>spring-snapshots</id>\n\t\t\t\t\t<name>Spring Snapshots</name>\n\t\t\t\t\t<url>https://repo.spring.io/libs-snapshot</url>\n\t\t\t\t\t<snapshots>\n\t\t\t\t\t\t<enabled>true</enabled>\n\t\t\t\t\t</snapshots>\n\t\t\t\t</pluginRepository>\n\t\t\t\t<pluginRepository>\n\t\t\t\t\t<id>spring-milestones</id>\n\t\t\t\t\t<name>Spring Milestones</name>\n\t\t\t\t\t<url>https://repo.spring.io/libs-milestone</url>\n\t\t\t\t\t<snapshots>\n\t\t\t\t\t\t<enabled>false</enabled>\n\t\t\t\t\t</snapshots>\n\t\t\t\t</pluginRepository>\n\t\t\t</pluginRepositories>\n\t\t</profile>\n\t\t<profile>\n\t\t\t<id>coverage</id>\n\t\t\t<activation>\n\t\t\t\t<property>\n\t\t\t\t\t<name>env.TRAVIS</name>\n\t\t\t\t\t<value>true</value>\n\t\t\t\t</property>\n\t\t\t</activation>\n\t\t\t<build>\n\t\t\t\t<plugins>\n\t\t\t\t\t<plugin>\n\t\t\t\t\t\t<groupId>org.jacoco</groupId>\n\t\t\t\t\t\t<artifactId>jacoco-maven-plugin</artifactId>\n\t\t\t\t\t\t<version>0.7.9</version>\n\t\t\t\t\t\t<executions>\n\t\t\t\t\t\t\t<execution>\n\t\t\t\t\t\t\t\t<id>agent</id>\n\t\t\t\t\t\t\t\t<goals>\n\t\t\t\t\t\t\t\t\t<goal>prepare-agent</goal>\n\t\t\t\t\t\t\t\t</goals>\n\t\t\t\t\t\t\t</execution>\n\t\t\t\t\t\t\t<execution>\n\t\t\t\t\t\t\t\t<id>report</id>\n\t\t\t\t\t\t\t\t<phase>test</phase>\n\t\t\t\t\t\t\t\t<goals>\n\t\t\t\t\t\t\t\t\t<goal>report</goal>\n\t\t\t\t\t\t\t\t</goals>\n\t\t\t\t\t\t\t</execution>\n\t\t\t\t\t\t</executions>\n\t\t\t\t\t</plugin>\n\t\t\t\t</plugins>\n\t\t\t</build>\n\t\t</profile>\n\t</profiles>\n\n\t<build>\n\t\t<plugins>\n\t\t\t<plugin>\n\t\t\t\t<groupId>org.apache.maven.plugins</groupId>\n\t\t\t\t<artifactId>maven-checkstyle-plugin</artifactId>\n\t\t\t\t<version>3.2.1</version>\n\t\t\t</plugin>\n\t\t\t<plugin>\n\t\t\t\t<groupId>org.apache.maven.plugins</groupId>\n\t\t\t\t<artifactId>maven-compiler-plugin</artifactId>\n\t\t\t\t<version>3.8.0</version>\n\t\t\t\t<configuration>\n\t\t\t\t\t<source>${java.version}</source>\n\t\t\t\t\t<target>${java.version}</target>\n\t\t\t\t</configuration>\n\t\t\t</plugin>\n\t\t\t<plugin>\n\t\t\t\t<groupId>org.apache.maven.plugins</groupId>\n\t\t\t\t<artifactId>maven-surefire-plugin</artifactId>\n\t\t\t\t<version>3.1.2</version>\n\t\t\t</plugin>\n\t\t\t<plugin>\n\t\t\t\t<groupId>org.apache.maven.plugins</groupId>\n\t\t\t\t<artifactId>maven-javadoc-plugin</artifactId>\n\t\t\t\t<version>3.4.1</version>\n\t\t\t\t<executions>\n\t\t\t\t\t<execution>\n\t\t\t\t\t\t<id>javadoc</id>\n\t\t\t\t\t\t<goals>\n\t\t\t\t\t\t\t<goal>jar</goal>\n\t\t\t\t\t\t</goals>\n\t\t\t\t\t\t<phase>package</phase>\n\t\t\t\t\t</execution>\n\t\t\t\t</executions>\n\t\t\t</plugin>\n\t\t\t<plugin>\n\t\t\t\t<groupId>org.apache.maven.plugins</groupId>\n\t\t\t\t<artifactId>maven-source-plugin</artifactId>\n\t\t\t\t<version>3.3.0</version>\n\t\t\t\t<executions>\n\t\t\t\t\t<execution>\n\t\t\t\t\t\t<id>source</id>\n\t\t\t\t\t\t<goals>\n\t\t\t\t\t\t\t<goal>jar</goal>\n\t\t\t\t\t\t</goals>\n\t\t\t\t\t\t<phase>package</phase>\n\t\t\t\t\t</execution>\n\t\t\t\t</executions>\n\t\t\t</plugin>\n\t\t</plugins>\n\t</build>\n\n</project>\n"
  },
  {
    "path": "spring-cloud-deployer-autoconfigure/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd\">\n\t<modelVersion>4.0.0</modelVersion>\n\t<artifactId>spring-cloud-deployer-autoconfigure</artifactId>\n\t<packaging>jar</packaging>\n\t<name>Spring Cloud Deployer Autoconfigure</name>\n\t<parent>\n\t\t<groupId>org.springframework.cloud</groupId>\n\t\t<artifactId>spring-cloud-deployer-parent</artifactId>\n\t\t<version>3.0.0-SNAPSHOT</version>\n\t\t<relativePath>..</relativePath>\n\t</parent>\n\n\t<dependencies>\n\t\t<dependency>\n\t\t\t<groupId>org.springframework.boot</groupId>\n\t\t\t<artifactId>spring-boot-autoconfigure</artifactId>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.springframework.boot</groupId>\n\t\t\t<artifactId>spring-boot-configuration-processor</artifactId>\n\t\t\t<optional>true</optional>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.springframework.boot</groupId>\n\t\t\t<artifactId>spring-boot-starter-test</artifactId>\n\t\t\t<scope>test</scope>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.springframework.cloud</groupId>\n\t\t\t<artifactId>spring-cloud-deployer-resource-maven</artifactId>\n\t\t\t<optional>true</optional>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.springframework.cloud</groupId>\n\t\t\t<artifactId>spring-cloud-deployer-resource-support</artifactId>\n\t\t\t<optional>true</optional>\n\t\t</dependency>\n\t</dependencies>\n\n\t<build>\n\t\t<resources>\n\t\t\t<resource>\n\t\t\t\t<directory>src/main/resources</directory>\n\t\t\t\t<filtering>true</filtering>\n\t\t\t\t<includes>\n\t\t\t\t\t<include>META-INF/spring.factories</include>\n\t\t\t\t\t<include>META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports</include>\n\t\t\t\t</includes>\n\t\t\t</resource>\n\t\t</resources>\n\t</build>\n\n</project>\n"
  },
  {
    "path": "spring-cloud-deployer-autoconfigure/src/main/java/org/springframework/cloud/deployer/autoconfigure/DelegatingResourceLoaderBuilder.java",
    "content": "/*\n * Copyright 2018 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage org.springframework.cloud.deployer.autoconfigure;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport org.springframework.cloud.deployer.resource.support.DelegatingResourceLoader;\nimport org.springframework.core.io.ResourceLoader;\n\n/**\n * Builder implementation for a {@link DelegatingResourceLoader} which allows to\n * register resource loaders with schemes before actual instance of a\n * {@link DelegatingResourceLoader} is created.\n *\n * @author Janne Valkealahti\n *\n */\npublic class DelegatingResourceLoaderBuilder {\n\n\tprivate final Map<String, ResourceLoader> loaders = new HashMap<>();\n\n\t/**\n\t * Register a map of resource loaders.\n\t *\n\t * @param loaders the resource loaders to register\n\t * @return builder instance for chaining\n\t */\n\tpublic DelegatingResourceLoaderBuilder loaders(Map<String, ResourceLoader> loaders) {\n\t\tthis.loaders.putAll(loaders);\n\t\treturn this;\n\t}\n\n\t/**\n\t * register a loader with a scheme.\n\t *\n\t * @param scheme the scheme\n\t * @param loader the resource loader\n\t * @return builder instance for chaining\n\t */\n\tpublic DelegatingResourceLoaderBuilder loader(String scheme, ResourceLoader loader) {\n\t\tthis.loaders.put(scheme, loader);\n\t\treturn this;\n\t}\n\n\t/**\n\t * Builds a {@link DelegatingResourceLoader}.\n\t *\n\t * @return the built delegating resource loader\n\t */\n\tpublic DelegatingResourceLoader build() {\n\t\treturn new DelegatingResourceLoader(this.loaders);\n\t}\n}\n"
  },
  {
    "path": "spring-cloud-deployer-autoconfigure/src/main/java/org/springframework/cloud/deployer/autoconfigure/DelegatingResourceLoaderBuilderCustomizer.java",
    "content": "/*\n * Copyright 2018 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage org.springframework.cloud.deployer.autoconfigure;\n\nimport org.springframework.cloud.deployer.resource.support.DelegatingResourceLoader;\n\n/**\n * Callback interface that can be used to customize resource loaders for a\n * {@link DelegatingResourceLoader}.\n *\n * @author Janne Valkealahti\n *\n */\n@FunctionalInterface\npublic interface DelegatingResourceLoaderBuilderCustomizer {\n\n\t/**\n\t * Callback to customize a {@link DelegatingResourceLoaderBuilder} instance.\n\t *\n\t * @param builder the loader builder\n\t */\n\tvoid customize(DelegatingResourceLoaderBuilder builder);\n}\n"
  },
  {
    "path": "spring-cloud-deployer-autoconfigure/src/main/java/org/springframework/cloud/deployer/autoconfigure/MavenConfigurationProperties.java",
    "content": "/*\n * Copyright 2018 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage org.springframework.cloud.deployer.autoconfigure;\n\nimport org.springframework.boot.context.properties.ConfigurationProperties;\nimport org.springframework.cloud.deployer.resource.maven.MavenProperties;\n\n/**\n * Configuration properties for a {@link MavenProperties}.\n *\n * @author Janne Valkealahti\n *\n */\n@ConfigurationProperties(prefix = \"maven\")\npublic class MavenConfigurationProperties extends MavenProperties {\n}\n"
  },
  {
    "path": "spring-cloud-deployer-autoconfigure/src/main/java/org/springframework/cloud/deployer/autoconfigure/ResourceLoadingAutoConfiguration.java",
    "content": "/*\n * Copyright 2018 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage org.springframework.cloud.deployer.autoconfigure;\n\nimport org.springframework.beans.factory.ObjectProvider;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnClass;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;\nimport org.springframework.boot.context.properties.EnableConfigurationProperties;\nimport org.springframework.cloud.deployer.resource.maven.MavenProperties;\nimport org.springframework.cloud.deployer.resource.maven.MavenResourceLoader;\nimport org.springframework.cloud.deployer.resource.support.DelegatingResourceLoader;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.core.annotation.Order;\nimport org.springframework.core.io.ResourceLoader;\n\n/**\n * Autoconfiguration of a file or Maven based {@link ResourceLoader}.\n *\n * @author Michael Minella\n * @author Janne Valkealahti\n */\n@Configuration\npublic class ResourceLoadingAutoConfiguration {\n\n\t@Configuration\n\t@ConditionalOnClass(MavenResourceLoader.class)\n\t@EnableConfigurationProperties(MavenConfigurationProperties.class)\n\tpublic static class MavenResourceLoaderConfig {\n\n\t\t@Bean\n\t\t@Order(0)\n\t\tpublic DelegatingResourceLoaderBuilderCustomizer mavenDelegatingResourceLoaderBuilderCustomizer(MavenProperties mavenProperties) {\n\t\t\treturn customizer -> customizer.loader(\"maven\", new MavenResourceLoader(mavenProperties));\n\t\t}\n\t}\n\n\t@Configuration\n\t@ConditionalOnMissingBean(DelegatingResourceLoader.class)\n\tpublic static class DelegatingResourceLoaderConfig {\n\n\t\tprivate final ObjectProvider<DelegatingResourceLoaderBuilderCustomizer> loaderBuilderCustomizers;\n\n\t\tpublic DelegatingResourceLoaderConfig(\n\t\t\t\tObjectProvider<DelegatingResourceLoaderBuilderCustomizer> loaderBuilderCustomizers) {\n\t\t\tthis.loaderBuilderCustomizers = loaderBuilderCustomizers;\n\t\t}\n\n\t\t@Bean\n\t\tpublic DelegatingResourceLoader delegatingResourceLoader() {\n\t\t\tDelegatingResourceLoaderBuilder builder = new DelegatingResourceLoaderBuilder();\n\t\t\tthis.loaderBuilderCustomizers.orderedStream().forEach(customizer -> customizer.customize(builder));\n\t\t\treturn builder.build();\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "spring-cloud-deployer-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports",
    "content": "org.springframework.cloud.deployer.autoconfigure.ResourceLoadingAutoConfiguration\n"
  },
  {
    "path": "spring-cloud-deployer-autoconfigure/src/main/resources/META-INF/spring.factories",
    "content": "org.springframework.boot.autoconfigure.EnableAutoConfiguration=\\\n  org.springframework.cloud.deployer.autoconfigure.ResourceLoadingAutoConfiguration\n"
  },
  {
    "path": "spring-cloud-deployer-autoconfigure/src/test/java/org/springframework/cloud/deployer/autoconfigure/ResourceLoadingAutoConfigurationTests.java",
    "content": "/*\n * Copyright 2018 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage org.springframework.cloud.deployer.autoconfigure;\n\nimport org.assertj.core.api.Condition;\nimport org.junit.jupiter.api.Test;\n\nimport org.springframework.boot.autoconfigure.AutoConfigurations;\nimport org.springframework.boot.test.context.runner.ApplicationContextRunner;\nimport org.springframework.cloud.deployer.resource.maven.MavenProperties;\nimport org.springframework.cloud.deployer.resource.support.DelegatingResourceLoader;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.core.Ordered;\nimport org.springframework.core.annotation.Order;\nimport org.springframework.core.io.Resource;\nimport org.springframework.core.io.ResourceLoader;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * Tests for {@link ResourceLoadingAutoConfiguration}.\n *\n * @author Janne Valkealahti\n *\n */\npublic class ResourceLoadingAutoConfigurationTests {\n\n\tprivate final static ResourceLoader mockResourceLoader = new ResourceLoader() {\n\n\t\t@Override\n\t\tpublic Resource getResource(String location) {\n\t\t\treturn null;\n\t\t}\n\n\t\t@Override\n\t\tpublic ClassLoader getClassLoader() {\n\t\t\treturn null;\n\t\t}\n\t};\n\n\tprivate final static Condition<DelegatingResourceLoader> mavenCondition = new Condition<>(\n\t\t\tl -> l.getLoaders().containsKey(\"maven\"), \"maven loader\");\n\n\tprivate final static Condition<DelegatingResourceLoader> mavenReplacedCondition = new Condition<>(\n\t\t\tl -> l.getLoaders().get(\"maven\").equals(mockResourceLoader), \"maven loader replaced\");\n\n\tprivate final static Condition<DelegatingResourceLoader> foobarCondition = new Condition<>(\n\t\t\tl -> l.getLoaders().containsKey(\"foobar\"), \"foobar mock loader\");\n\n\tprivate final static Condition<MavenProperties> offlineCondition = new Condition<>(\n\t\t\tp -> p.isOffline(), \"offline\");\n\n\tprivate final ApplicationContextRunner contextRunner = new ApplicationContextRunner()\n\t\t\t.withConfiguration(AutoConfigurations.of(ResourceLoadingAutoConfiguration.class));\n\n\n\t@Test\n\tpublic void testAutoConfigNoProperties() {\n\t\tthis.contextRunner\n\t\t\t\t.run((context) -> {\n\t\t\t\t\tassertThat(context).hasSingleBean(DelegatingResourceLoader.class);\n\t\t\t\t\tassertThat(context).getBean(DelegatingResourceLoader.class).has(mavenCondition);\n\t\t\t\t});\n\t}\n\n\t@Test\n\tpublic void testMavenProperties() {\n\t\tthis.contextRunner\n\t\t\t\t.withPropertyValues(\"maven.offline=true\")\n\t\t\t\t.run((context) ->\n\t\t\t\t\tassertThat(context).getBean(MavenProperties.class).has(offlineCondition));\n\t}\n\n\t@Test\n\tpublic void testBuilderRegistration() {\n\t\tthis.contextRunner\n\t\t\t\t.withUserConfiguration(CustomBuilderCustomizerConfig.class)\n\t\t\t\t.run((context) ->\n\t\t\t\t\tassertThat(context).getBean(DelegatingResourceLoader.class).has(foobarCondition));\n\t}\n\n\t@Test\n\tpublic void testBuilderOrderRegistration() {\n\t\tthis.contextRunner\n\t\t\t\t.withUserConfiguration(MavenReplacingBuilderCustomizerConfig.class)\n\t\t\t\t.run((context) ->\n\t\t\t\t\tassertThat(context).getBean(DelegatingResourceLoader.class).has(mavenReplacedCondition));\n\t}\n\n\t@Configuration\n\tstatic class CustomBuilderCustomizerConfig {\n\n\t\t@Bean\n\t\tpublic DelegatingResourceLoaderBuilderCustomizer foobarDelegatingResourceLoaderBuilderCustomizer() {\n\t\t\treturn customizer -> customizer.loader(\"foobar\", mockResourceLoader);\n\t\t}\n\t}\n\n\t@Configuration\n\tstatic class MavenReplacingBuilderCustomizerConfig {\n\n\t\t@Bean\n\t\t@Order(Ordered.LOWEST_PRECEDENCE)\n\t\tpublic DelegatingResourceLoaderBuilderCustomizer foobarDelegatingResourceLoaderBuilderCustomizer() {\n\t\t\treturn customizer -> customizer.loader(\"maven\", mockResourceLoader);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "spring-cloud-deployer-cloudfoundry/README.adoc",
    "content": "= Spring Cloud Deployer for Cloud Foundry\n\nThis project provides a common means to deploy applications to Cloud Foundry based on the Spring Cloud Deployer SPI.\n\n== Building\n\nClone the repo and type\n\n----\n$ ./mvnw clean install\n----\n\nThe project includes a set of integration tests that can be run against a Cloud Foundry installation, provided that\nconnection information is correctly set. If credentials are not set correctly, those tests will be silently skipped.\nBelow is a short list of common Spring Boot `@ConfigurationProperties` (in environment variable format) that you will\nneed to set in order to deploy applications to Cloud Foundry:\n\n----\n# url of the CF API (used when using cf login -a for example)\nSPRING_CLOUD_DEPLOYER_CLOUDFOUNDRY_URL\n\n# name of the space into which modules will be deployed\nSPRING_CLOUD_DEPLOYER_CLOUDFOUNDRY_SPACE\n\n# name of the organization that owns the space above\nSPRING_CLOUD_DEPLOYER_CLOUDFOUNDRY_ORG\n\n# the root domain to use when mapping routes\nSPRING_CLOUD_DEPLOYER_CLOUDFOUNDRY_DOMAIN\n\n# Comma separated set of service instance names to bind to the deployed app\nSPRING_CLOUD_DEPLOYER_CLOUDFOUNDRY_SERVICES\n\n# username and password of the user to use to create apps\nSPRING_CLOUD_DEPLOYER_CLOUDFOUNDRY_USERNAME\nSPRING_CLOUD_DEPLOYER_CLOUDFOUNDRY_PASSWORD\n\n# the identity provider to be used when accessing the Cloud Foundry API (optional)\n# the passed string has to be a URL-Encoded JSON Object, containing the field origin with value as origin_key of an identity provider.\nSPRING_CLOUD_DEPLOYER_CLOUDFOUNDRY_LOGIN_HINT\n\n# whether to allow self-signed certificates during SSL validation\nSPRING_CLOUD_DEPLOYER_CLOUDFOUNDRY_SKIP_SSL_VALIDATION\n----\n\nRefer to `CloudFoundryDeploymentProperties.java` and `CloudFoundryConnectionProperties.java` for a complete listing.\n\n== Services, Disk and Memory Settings for Applications\n\nThe deployer also supports setting the properties `spring.cloud.deployer.cloudfoundry.services`,\n`spring.cloud.deployer.cloudfoundry.memory`, and `spring.cloud.deployer.cloudfoundry.disk` as part of an individual\ndeployment request.\n\n== Application Name Settings and Deployments\n\nTo help avoid clashes with routes across spaces on Cloud Foundry, a naming strategy to provide a random prefix to a\ndeployed application is available and is enabled by default.  There are two configuration properties,\n`enableRandomAppNamePrefix` and `appNamePrefix`.  The `appNamePrefix` property defaults to `spring.application.name`\nif present, otherwise defaults to an empty string.\n\nAn application can have four possible \"name\" combinations. For instance, the `time` application can have name\ncombinations as shown in the table below. The name of the deployed application is defined via deployer's high level API,\n in this case it is 'time'.\n\n.Application Name\n|===\n|appNamePrefix | enableRandomAppNamePrefix | application name\n\n|server\n|true\n|server-u7r9fhm-time\n\n|<empty string>\n|true\n|u7r9fhm-time\n\n|server\n|false\n|server-time\n\n|<emtpy string>\n|false\n|time\n|===\n\n== Disable Push Task Applications\n\nIf an application does not exist when the `TaskLauncher` `launch` method is invoked, the TaskLauncher by default pushes a task application using the `AppDeploymentRequest` `resource` property.\n\nThe default behavior is disabled by setting `spring.cloud.deployer.cloudfoundry.push-task-apps-enabled` to `false`.\nIn this case, the application must have already been pushed to CloudFoundry via an external process and the TaskLauncher simply starts it, providing any command line arguments defined in the deployment request.\n\nThis is useful in cases in which the Cloud Foundry foundation is isolated from external code repositories.\n\n== Set Additional Environment Variables\n\nBy default, the deployer adds global and application configuration properties to a single `SPRING_APPLICATION_JSON` environment variable entry in the application manifest.\nYou can configure additional top-level environment variables in the manifest by setting `spring.cloud.deployer.cloudfoundry.env.<key>=<value>`.\nThis is useful for adding https://github.com/cloudfoundry/java-buildpack[Java build pack configuration properties] to the application manifest since the Java build pack does not recognize `SPRING_APPLICATION_JSON`.\n\n\n\n\n\n"
  },
  {
    "path": "spring-cloud-deployer-cloudfoundry/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd\">\n\t<modelVersion>4.0.0</modelVersion>\n\n\t<artifactId>spring-cloud-deployer-cloudfoundry</artifactId>\n\t<version>3.0.0-SNAPSHOT</version>\n\t<packaging>jar</packaging>\n\t<name>Spring Cloud Deployer CloudFoundry</name>\n\t<parent>\n\t\t<groupId>org.springframework.cloud</groupId>\n\t\t<artifactId>spring-cloud-deployer-parent</artifactId>\n\t\t<version>3.0.0-SNAPSHOT</version>\n\t\t<relativePath>..</relativePath>\n\t</parent>\n\n\t<dependencies>\n\t\t<dependency>\n\t\t\t<groupId>org.springframework.cloud</groupId>\n\t\t\t<artifactId>spring-cloud-deployer-spi</artifactId>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.springframework.boot</groupId>\n\t\t\t<artifactId>spring-boot-loader-classic</artifactId>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.springframework.boot</groupId>\n\t\t\t<artifactId>spring-boot-starter-validation</artifactId>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.springframework.boot</groupId>\n\t\t\t<artifactId>spring-boot-configuration-processor</artifactId>\n\t\t\t<optional>true</optional>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.cloudfoundry</groupId>\n\t\t\t<artifactId>cloudfoundry-client-reactor</artifactId>\n\t\t\t<!-- @TODO boot3 remove when updated -->\n\t\t\t<exclusions>\n\t\t\t\t<exclusion>\n\t\t\t\t\t<groupId>javax.annotation</groupId>\n\t\t\t\t\t<artifactId>javax.annotation-api</artifactId>\n\t\t\t\t</exclusion>\n\t\t\t</exclusions>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.cloudfoundry</groupId>\n\t\t\t<artifactId>cloudfoundry-operations</artifactId>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>io.pivotal</groupId>\n\t\t\t<artifactId>pivotal-cloudfoundry-client-reactor</artifactId>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.apache.commons</groupId>\n\t\t\t<artifactId>commons-compress</artifactId>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>com.github.ben-manes.caffeine</groupId>\n\t\t\t<artifactId>caffeine</artifactId>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>io.projectreactor.addons</groupId>\n\t\t\t<artifactId>reactor-extra</artifactId>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.yaml</groupId>\n\t\t\t<artifactId>snakeyaml</artifactId>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.springframework.retry</groupId>\n\t\t\t<artifactId>spring-retry</artifactId>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.springframework.boot</groupId>\n\t\t\t<artifactId>spring-boot-starter-test</artifactId>\n\t\t\t<scope>test</scope>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.springframework.cloud</groupId>\n\t\t\t<artifactId>spring-cloud-deployer-spi-test</artifactId>\n\t\t\t<version>${project.version}</version>\n\t\t\t<scope>test</scope>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.springframework.cloud</groupId>\n\t\t\t<artifactId>spring-cloud-deployer-resource-docker</artifactId>\n\t\t\t<scope>test</scope>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.springframework.cloud</groupId>\n\t\t\t<artifactId>spring-cloud-deployer-resource-support</artifactId>\n\t\t\t<scope>test</scope>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.springframework.cloud</groupId>\n\t\t\t<artifactId>spring-cloud-deployer-autoconfigure</artifactId>\n\t\t\t<scope>test</scope>\n\t\t</dependency>\n\t</dependencies>\n\n\t<profiles>\n\t\t<profile>\n\t\t\t<id>spring</id>\n\t\t\t<activation><activeByDefault>true</activeByDefault></activation>\n\t\t\t<repositories>\n\t\t\t\t<repository>\n\t\t\t\t\t<id>spring-snapshots</id>\n\t\t\t\t\t<name>Spring Snapshots</name>\n\t\t\t\t\t<url>https://repo.spring.io/snapshot</url>\n\t\t\t\t\t<snapshots>\n\t\t\t\t\t\t<enabled>true</enabled>\n\t\t\t\t\t</snapshots>\n\t\t\t\t</repository>\n\t\t\t\t<repository>\n\t\t\t\t\t<id>spring-milestones</id>\n\t\t\t\t\t<name>Spring Milestones</name>\n\t\t\t\t\t<url>https://repo.spring.io/milestone</url>\n\t\t\t\t\t<snapshots>\n\t\t\t\t\t\t<enabled>false</enabled>\n\t\t\t\t\t</snapshots>\n\t\t\t\t</repository>\n\t\t\t\t<repository>\n\t\t\t\t\t<id>maven-central</id>\n\t\t\t\t\t<name>Maven Central</name>\n\t\t\t\t\t<url>https://repo.maven.apache.org/maven2</url>\n\t\t\t\t\t<snapshots>\n\t\t\t\t\t\t<enabled>false</enabled>\n\t\t\t\t\t</snapshots>\n\t\t\t\t</repository>\n\t\t\t</repositories>\n\t\t\t<pluginRepositories>\n\t\t\t\t<pluginRepository>\n\t\t\t\t\t<id>spring-snapshots</id>\n\t\t\t\t\t<name>Spring Snapshots</name>\n\t\t\t\t\t<url>https://repo.spring.io/snapshot</url>\n\t\t\t\t\t<snapshots>\n\t\t\t\t\t\t<enabled>true</enabled>\n\t\t\t\t\t</snapshots>\n\t\t\t\t</pluginRepository>\n\t\t\t\t<pluginRepository>\n\t\t\t\t\t<id>spring-milestones</id>\n\t\t\t\t\t<name>Spring Milestones</name>\n\t\t\t\t\t<url>https://repo.spring.io/milestone</url>\n\t\t\t\t\t<snapshots>\n\t\t\t\t\t\t<enabled>false</enabled>\n\t\t\t\t\t</snapshots>\n\t\t\t\t</pluginRepository>\n\t\t\t\t<pluginRepository>\n\t\t\t\t\t<id>maven-central</id>\n\t\t\t\t\t<name>Maven Central</name>\n\t\t\t\t\t<url>https://repo.maven.apache.org/maven2</url>\n\t\t\t\t\t<snapshots>\n\t\t\t\t\t\t<enabled>false</enabled>\n\t\t\t\t\t</snapshots>\n\t\t\t\t</pluginRepository>\n\t\t\t</pluginRepositories>\n\t\t</profile>\n\t</profiles>\n\n</project>\n"
  },
  {
    "path": "spring-cloud-deployer-cloudfoundry/src/main/java/org/springframework/cloud/deployer/spi/cloudfoundry/AbstractCloudFoundryDeployer.java",
    "content": "/*\n * Copyright 2016-2020 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.cloudfoundry;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.nio.file.Path;\nimport java.time.Duration;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.LinkedHashMap;\nimport java.util.Locale;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.Set;\nimport java.util.concurrent.TimeoutException;\nimport java.util.function.Consumer;\nimport java.util.function.Function;\nimport java.util.function.Predicate;\nimport java.util.stream.Collectors;\nimport java.util.stream.Stream;\n\nimport com.fasterxml.jackson.core.JsonProcessingException;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport org.cloudfoundry.AbstractCloudFoundryException;\nimport org.cloudfoundry.UnknownCloudFoundryException;\nimport org.cloudfoundry.operations.services.BindServiceInstanceRequest;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport reactor.core.Exceptions;\nimport reactor.core.publisher.Mono;\nimport reactor.retry.Retry;\n\nimport org.springframework.cloud.deployer.spi.app.AppDeployer;\nimport org.springframework.cloud.deployer.spi.app.AppScaleRequest;\nimport org.springframework.cloud.deployer.spi.core.AppDeploymentRequest;\nimport org.springframework.cloud.deployer.spi.core.RuntimeEnvironmentInfo;\nimport org.springframework.cloud.deployer.spi.util.ByteSizeUtils;\nimport org.springframework.core.io.Resource;\nimport org.springframework.http.HttpStatus;\nimport org.springframework.util.FileSystemUtils;\nimport org.springframework.util.ObjectUtils;\nimport org.springframework.util.StringUtils;\n\n/**\n * Base class dealing with configuration overrides on a per-deployment basis, as well as common code for apps and tasks.\n *\n * @author Eric Bottard\n * @author Ilayaperumal Gopinathan\n * @author David Turanski\n */\nclass AbstractCloudFoundryDeployer {\n\n\tprotected static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();\n\n\tprotected final RuntimeEnvironmentInfo runtimeEnvironmentInfo;\n\n\tfinal CloudFoundryDeploymentProperties deploymentProperties;\n\n\tprivate final Logger logger = LoggerFactory.getLogger(AbstractCloudFoundryDeployer.class);\n\n\n\tAbstractCloudFoundryDeployer(CloudFoundryDeploymentProperties deploymentProperties, RuntimeEnvironmentInfo runtimeEnvironmentInfo) {\n\t\tthis.deploymentProperties = deploymentProperties;\n\t\tthis.runtimeEnvironmentInfo = runtimeEnvironmentInfo;\n\t}\n\n\tint memory(AppDeploymentRequest request) {\n\t\tString withUnit = request.getDeploymentProperties()\n\t\t\t.getOrDefault(AppDeployer.MEMORY_PROPERTY_KEY, this.deploymentProperties.getMemory());\n\t\treturn (int) ByteSizeUtils.parseToMebibytes(withUnit);\n\t}\n\n\tint memory(AppScaleRequest request) {\n\t\tif (request.getProperties().isPresent() && request.getProperties().get() != null) {\n\t\t\treturn (int) ByteSizeUtils.parseToMebibytes(request.getProperties().get().getOrDefault(AppDeployer.MEMORY_PROPERTY_KEY,\n\t\t\t\t\tthis.deploymentProperties.getMemory()));\n\t\t}\n\t\treturn (int) ByteSizeUtils.parseToMebibytes(this.deploymentProperties.getMemory());\n\t}\n\n\tint diskQuota(AppScaleRequest request) {\n\t\tif (request.getProperties().isPresent() && request.getProperties().get() != null) {\n\t\t\treturn (int) ByteSizeUtils.parseToMebibytes(request.getProperties().get().getOrDefault(AppDeployer.DISK_PROPERTY_KEY,\n\t\t\t\t\tthis.deploymentProperties.getDisk()));\n\t\t}\n\t\treturn (int) ByteSizeUtils.parseToMebibytes(this.deploymentProperties.getDisk());\n\t}\n\n\tSet<String> servicesToBind(AppDeploymentRequest request) {\n\n\t\tSet<String> services = this.deploymentProperties.getServices().stream().filter(s->!ServiceParser\n\t\t\t.getServiceParameters(s).isPresent())\n\t\t\t.collect(Collectors.toSet());\n\n\t\tSet<String> requestServices = ServiceParser.splitServiceProperties(request.getDeploymentProperties().get\n\t\t\t(CloudFoundryDeploymentProperties.SERVICES_PROPERTY_KEY))\n\t\t\t.stream()\n\t\t\t.filter(s-> !ServiceParser.getServiceParameters(s).isPresent())\n\t\t\t.collect(Collectors.toSet());\n\n\t\tservices.addAll(requestServices);\n\t\treturn services;\n\t}\n\n\tboolean includesServiceParameters(AppDeploymentRequest request) {\n\t\treturn this.deploymentProperties.getServices().stream()\n\t\t\t\t.anyMatch(s -> ServiceParser.getServiceParameters(s).isPresent())\n\t\t\t\t|| ServiceParser\n\t\t\t\t\t\t.splitServiceProperties(request.getDeploymentProperties()\n\t\t\t\t\t\t\t\t.get(CloudFoundryDeploymentProperties.SERVICES_PROPERTY_KEY))\n\t\t\t\t\t\t.stream().anyMatch(s -> ServiceParser.getServiceParameters(s).isPresent());\n\t}\n\n\tStream<BindServiceInstanceRequest> bindParameterizedServiceInstanceRequests(AppDeploymentRequest request,\n\t\tString deploymentId) {\n\t\treturn ServiceParser.splitServiceProperties(request.getDeploymentProperties().get\n\t\t\t(CloudFoundryDeploymentProperties.SERVICES_PROPERTY_KEY)).stream()\n\t\t\t.filter(s-> ServiceParser.getServiceParameters(s).isPresent())\n\t\t\t.map(s->\n\t\t\t\tBindServiceInstanceRequest.builder()\n\t\t\t\t\t.applicationName(deploymentId)\n\t\t\t\t\t.serviceInstanceName(ServiceParser.getServiceInstanceName(s))\n\t\t\t\t\t.parameters(ServiceParser.getServiceParameters(s).get())\n\t\t\t\t\t.build()\n\t\t\t);\n\t}\n\n\tint diskQuota(AppDeploymentRequest request) {\n\t\tString withUnit = request.getDeploymentProperties()\n\t\t\t.getOrDefault(AppDeployer.DISK_PROPERTY_KEY, this.deploymentProperties.getDisk());\n\t\treturn (int) ByteSizeUtils.parseToMebibytes(withUnit);\n\t}\n\n\tSet<String> buildpacks(AppDeploymentRequest request) {\n\t\t// TODO: When 'buildpack' setting gets removed due to deprecation,\n\t\t//       change this logic not ot fallback to it if 'buildpacks'\n\t\t//       is used.\n\t\tString buidpacksValue = request.getDeploymentProperties()\n\t\t\t\t.get(CloudFoundryDeploymentProperties.BUILDPACKS_PROPERTY_KEY);\n\t\tString buidpackValue = request.getDeploymentProperties()\n\t\t\t\t.get(CloudFoundryDeploymentProperties.BUILDPACK_PROPERTY_KEY);\n\t\tif (buidpacksValue != null) {\n\t\t\treturn StringUtils.commaDelimitedListToSet(buidpacksValue);\n\t\t}\n\t\telse if (buidpackValue != null) {\n\t\t\treturn new HashSet<>(Arrays.asList(buidpackValue));\n\t\t}\n\t\telse if (!ObjectUtils.isEmpty((this.deploymentProperties.getBuildpacks()))) {\n\t\t\treturn this.deploymentProperties.getBuildpacks();\n\t\t}\n\t\telse {\n\t\t\treturn new HashSet<>(Arrays.asList(this.deploymentProperties.getBuildpack()));\n\t\t}\n\t}\n\n\tString javaOpts(AppDeploymentRequest request) {\n\t\treturn Optional\n\t\t\t\t.ofNullable(\n\t\t\t\t\t\trequest.getDeploymentProperties().get(CloudFoundryDeploymentProperties.JAVA_OPTS_PROPERTY_KEY))\n\t\t\t\t.orElse(this.deploymentProperties.getJavaOpts());\n\t}\n\n\tPredicate<Throwable> isNotFoundError() {\n\t\treturn t -> t instanceof AbstractCloudFoundryException && ((AbstractCloudFoundryException) t).getStatusCode() == HttpStatus.NOT_FOUND.value();\n\t}\n\n\t/**\n\t * Return a Docker image identifier if the application Resource is for a Docker image, or {@literal null} otherwise.\n\t *\n\t * @see #getApplication(AppDeploymentRequest)\n\t */\n\tString getDockerImage(AppDeploymentRequest request) {\n\t\ttry {\n\t\t\tString uri = request.getResource().getURI().toString();\n\t\t\tif (uri.startsWith(\"docker:\")) {\n\t\t\t\treturn uri.substring(\"docker:\".length());\n\t\t\t} else {\n\t\t\t\treturn null;\n\t\t\t}\n\t\t} catch (IOException e) {\n\t\t\tthrow Exceptions.propagate(e);\n\t\t}\n\t}\n\n\t/**\n\t * Return a Path to the application Resource or {@literal null} if the request is for a Docker image.\n\t *\n\t * @see #getDockerImage(AppDeploymentRequest)\n\t */\n\tPath getApplication(AppDeploymentRequest request) {\n\t\ttry {\n\t\t\tlogger.info(\n\t\t\t\t\"Preparing to push an application from {}\" +\n\t\t\t\t\t\". This may take some time if the artifact must be downloaded from a remote host.\",\n\t\t\t\trequest.getResource());\n\t\t\tif (!request.getResource().getURI().toString().startsWith(\"docker:\")) {\n\t\t\t\treturn request.getResource().getFile().toPath();\n\t\t\t} else {\n\t\t\t\treturn null;\n\t\t\t}\n\t\t} catch (IOException e) {\n\t\t\tthrow Exceptions.propagate(e);\n\t\t}\n\t}\n\n\t/**\n\t * Return a function usable in {@literal doOnError} constructs that will unwrap unrecognized Cloud Foundry Exceptions\n\t * and log the text payload.\n\t */\n\tprotected Consumer<Throwable> logError(String msg) {\n\t\treturn e -> {\n\t\t\tif (e instanceof UnknownCloudFoundryException) {\n\t\t\t\tlogger.error(msg + \"\\nUnknownCloudFoundryException encountered, whose payload follows:\\n\" + ((UnknownCloudFoundryException)e).getPayload(), e);\n\t\t\t} else {\n\t\t\t\tlogger.error(msg, e);\n\t\t\t}\n\t\t};\n\t}\n\n\t/**\n\t * To be used in order to retry the status operation for an application or task.\n\t * @param id The application id or the task id\n\t * @param <T> The type of status object being queried for, usually AppStatus or TaskStatus\n\t * @return The function that executes the retry logic around for determining App or Task Status\n\t */\n\t<T> Function<Mono<T>, Mono<T>> statusRetry(String id) {\n\t\tlong statusTimeout = this.deploymentProperties.getStatusTimeout();\n\t\tlong requestTimeout = Math.round(statusTimeout * 0.40); // wait 500ms with default status timeout of 2000ms\n\t\tlong initialRetryDelay = Math.round(statusTimeout * 0.10); // wait 200ms with status timeout of 2000ms\n\n\t\tif (requestTimeout < 500L) {\n\t\t\tlogger.info(\"Computed statusRetry Request timeout = {} ms is below 500ms minimum value.  Setting to 500ms\", requestTimeout);\n\t\t\trequestTimeout = 500L;\n\t\t}\n\t\tfinal long requestTimeoutToUse = requestTimeout;\n\t\treturn m -> m.timeout(Duration.ofMillis(requestTimeoutToUse))\n\t\t\t.doOnError(e -> {\n\t\t\t\t// show real exception if it wasn't timeout\n\t\t\t\tif (e instanceof TimeoutException) {\n\t\t\t\t\tlogger.warn(\"Error getting status for {} within {}ms, Retrying operation.\", id, requestTimeoutToUse);\n\t\t\t\t}\n\t\t\t\telse if (e instanceof UnknownCloudFoundryException) {\n\t\t\t\t\tlogger.warn(\"Received UnknownCloudFoundryException from cf with payload={}\",\n\t\t\t\t\t\t\t((UnknownCloudFoundryException) e).getPayload());\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tlogger.warn(\"Received error from cf\", e);\n\t\t\t\t}\n\t\t\t})\n\t\t\t// let all other than timeout exception to propagate back to caller\n\t\t\t.retryWhen(reactor.util.retry.Retry.withThrowable(Retry.onlyIf(c -> {\n\t\t\t\t\tlogger.debug(\"RetryContext for id {} iteration {} backoff {}\", id,  c.iteration(), c.backoff());\n\t\t\t\t\tif (c.iteration() > 5) {\n\t\t\t\t\t\tlogger.info(\"Stopping retry for id {} after {} iterations\", id, c.iteration());\n\t\t\t\t\t\treturn false;\n\t\t\t\t\t}\n\t\t\t\t\tif (c.exception().getClass().getName().contains(\"org.cloudfoundry.client\")) {\n\t\t\t\t\t\t// most likely real error which is not worth to retry\n\t\t\t\t\t\treturn false;\n\t\t\t\t\t}\n\t\t\t\t\t// might be some netty error for not connected client, etc, retry\n\t\t\t\t\treturn true;\n\t\t\t\t})\n\t\t\t\t.exponentialBackoff(Duration.ofMillis(initialRetryDelay), Duration.ofMillis(statusTimeout))\n\t\t\t\t.doOnRetry(c -> logger.debug(\"Retrying cf call for {}\", id))))\n\t\t\t.doOnError(TimeoutException.class, e -> {\n\t\t\t\tlogger.error(\"Retry operation on getStatus failed for {}. Max retry time {}ms\", id, statusTimeout);\n\t\t\t});\n\t}\n\n\t/**\n\t * Always delete downloaded files for static http resources. Conditionally delete maven resources.\n\t * @param appDeploymentRequest\n\t */\n\tprotected void deleteLocalApplicationResourceFile(AppDeploymentRequest appDeploymentRequest) {\n\n\t\ttry {\n\n\t\t\tOptional<File> fileToDelete = fileToDelete(appDeploymentRequest.getResource());\n\t\t\tif (fileToDelete.isPresent()) {\n\t\t\t\tFile applicationFile = fileToDelete.get();\n\n\t\t\t\tlogger.info(\"Free Disk Space = {} bytes, Total Disk Space = {} bytes\",\n\t\t\t\t\t\tapplicationFile.getFreeSpace(),\n\t\t\t\t\t\tapplicationFile.getTotalSpace());\n\n\n\t\t\t\tboolean deleted = deleteFileOrDirectory(applicationFile);\n\t\t\t\tlogger.info((deleted) ? \"Successfully deleted the application resource: \" + applicationFile.getCanonicalPath() :\n\t\t\t\t\t\t\"Could not delete the application resource: \" + applicationFile.getCanonicalPath());\n\t\t\t}\n\n\t\t} catch(IOException e){\n\t\t\tlogger.warn(\"Exception deleting the application resource after successful CF push request.\"\n\t\t\t\t\t+ \" This could cause increase in disk space usage. Exception message: \" + e.getMessage());\n\t\t}\n\t}\n\n\t/*\n\t * Always delete files downloaded from http/s url.\n\t * Delete maven resources if property is set.\n\t */\n\tprivate Optional<File> fileToDelete(Resource resource) throws IOException {\n\t\tString scheme = resource.getURI().getScheme().toLowerCase(Locale.ROOT);\n\t\tif (scheme.startsWith(\"http\")) {\n\t\t\treturn Optional.of(resource.getFile());\n\t\t}\n\t\tif (scheme.equals(\"maven\") && deploymentProperties.isAutoDeleteMavenArtifacts()) {\n\t\t\treturn Optional.of(resource.getFile().getParentFile());\n\t\t}\n\t\treturn Optional.empty();\n\t}\n\n\tprivate boolean deleteFileOrDirectory(File fileToDelete) {\n\t\tboolean deleted;\n\t\tif (fileToDelete.isDirectory())\n\t\t\tdeleted  = FileSystemUtils.deleteRecursively(fileToDelete);\n\t\telse {\n\t\t\tdeleted = fileToDelete.delete();\n\t\t}\n\t\treturn deleted;\n\t}\n\n\t/*\n\t * Merge environment variables from global DeploymentProperties, application properties, and\n\t * environment variables declared in ApplicationDeploymentRequest\n\t */\n\tprotected Map<String, String> mergeEnvironmentVariables(String deploymentId, AppDeploymentRequest request) {\n\t\tMap<String, String> envVariables = new HashMap<>(deploymentProperties.getEnv());\n\t\tenvVariables.putAll(getApplicationProperties(deploymentId, request));\n\t\tenvVariables.putAll(getDeclaredEnvironmentVariables(request));\n\t\tString javaOpts = javaOpts(request);\n\t\tif (StringUtils.hasText(javaOpts)) {\n\t\t\tenvVariables.put(\"JAVA_OPTS\", javaOpts(request));\n\t\t}\n\n\t\tif (hasCfEnv(request.getResource())) {\n\t\t\tMap<String, String> env =\n\t\t\t\t\tCfEnvConfigurer.disableJavaBuildPackAutoReconfiguration(envVariables);\n\t\t\t//Only append to existing spring profiles active\n\t\t\tenv.putAll(CfEnvConfigurer.activateCloudProfile(env, null));\n\t\t\tenvVariables.putAll(env);\n\t\t}\n\t\treturn envVariables;\n\t}\n\n\tprivate Map<? extends String,? extends String> getDeclaredEnvironmentVariables(AppDeploymentRequest request) {\n\t\tMap<String, String> env = new LinkedHashMap<>();\n\t\trequest.getDeploymentProperties().entrySet().stream()\n\t\t\t\t.filter(e -> e.getKey().startsWith(CloudFoundryDeploymentProperties.ENV_KEY + \".\"))\n\t\t\t\t.forEach(e -> env.put(e.getKey().substring(CloudFoundryDeploymentProperties.ENV_KEY.length() + 1),\n\t\t\t\t\t\te.getValue()));\n\t\treturn env;\n\t}\n\n\tprotected boolean hasCfEnv(Resource resource) {\n\t\tif (resource instanceof CfEnvAwareResource) {\n\t\t\treturn ((CfEnvAwareResource)resource).hasCfEnv();\n\t\t}\n\t\treturn CfEnvAwareResource.of(resource).hasCfEnv();\n\t}\n\n\tprivate Map<String, String> getApplicationProperties(String deploymentId, AppDeploymentRequest request) {\n\t\tMap<String, String> applicationProperties = getSanitizedApplicationProperties(deploymentId, request);\n\n\t\tif (!useSpringApplicationJson(request)) {\n\t\t\treturn applicationProperties;\n\t\t}\n\n\t\ttry {\n\t\t\treturn Collections.singletonMap(\"SPRING_APPLICATION_JSON\", OBJECT_MAPPER.writeValueAsString(applicationProperties));\n\t\t} catch (JsonProcessingException e) {\n\t\t\tthrow new RuntimeException(e);\n\t\t}\n\t}\n\n\tprivate Map<String, String> getSanitizedApplicationProperties(String deploymentId, AppDeploymentRequest request) {\n\t\tMap<String, String> applicationProperties = new HashMap<>(request.getDefinition().getProperties());\n\n\t\t// Remove server.port as CF assigns a port for us, and we don't want to override that\n\t\tOptional.ofNullable(applicationProperties.remove(\"server.port\"))\n\t\t\t\t.ifPresent(port -> logger.warn(\"Ignoring 'server.port={}' for app {}, as Cloud Foundry will assign a local dynamic port. Route to the app will use port 80.\", port, deploymentId));\n\n\t\t// Update active Spring Profiles given in application properties. Create a new entry with the given key if necessary\n\t\tif (hasCfEnv(request.getResource())) {\n\t\t\tapplicationProperties = CfEnvConfigurer\n\t\t\t\t\t.activateCloudProfile(applicationProperties, CfEnvConfigurer.SPRING_PROFILES_ACTIVE_FQN);\n\t\t}\n\t\treturn applicationProperties;\n\t}\n\n\tprivate boolean useSpringApplicationJson(AppDeploymentRequest request) {\n\t\treturn Optional\n\t\t\t\t.ofNullable(request.getDeploymentProperties()\n\t\t\t\t\t\t.get(CloudFoundryDeploymentProperties.USE_SPRING_APPLICATION_JSON_KEY))\n\t\t\t\t.map(Boolean::valueOf).orElse(this.deploymentProperties.isUseSpringApplicationJson());\n\t}\n\n\n\n\tpublic RuntimeEnvironmentInfo environmentInfo() {\n\t\treturn runtimeEnvironmentInfo;\n\t}\n}\n"
  },
  {
    "path": "spring-cloud-deployer-cloudfoundry/src/main/java/org/springframework/cloud/deployer/spi/cloudfoundry/AbstractCloudFoundryTaskLauncher.java",
    "content": "/*\n * Copyright 2016-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.cloudfoundry;\n\nimport java.time.Duration;\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport io.jsonwebtoken.lang.Assert;\nimport org.cloudfoundry.client.CloudFoundryClient;\nimport org.cloudfoundry.client.v2.organizations.ListOrganizationsRequest;\nimport org.cloudfoundry.client.v2.spaces.ListSpacesRequest;\nimport org.cloudfoundry.client.v3.tasks.CancelTaskRequest;\nimport org.cloudfoundry.client.v3.tasks.CancelTaskResponse;\nimport org.cloudfoundry.client.v3.tasks.GetTaskRequest;\nimport org.cloudfoundry.client.v3.tasks.GetTaskResponse;\nimport org.cloudfoundry.client.v3.tasks.ListTasksRequest;\nimport org.cloudfoundry.client.v3.tasks.TaskState;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport reactor.core.publisher.Mono;\nimport reactor.util.function.Tuple2;\n\nimport org.springframework.cloud.deployer.spi.core.AppDeploymentRequest;\nimport org.springframework.cloud.deployer.spi.core.RuntimeEnvironmentInfo;\nimport org.springframework.cloud.deployer.spi.task.LaunchState;\nimport org.springframework.cloud.deployer.spi.task.TaskLauncher;\nimport org.springframework.cloud.deployer.spi.task.TaskStatus;\n\n/**\n * Abstract class to provide base functionality for launching Tasks on Cloud Foundry. This\n * class provides the base SPI for the {@link CloudFoundryTaskLauncher}.\n *\n * Does not override the default no-op implementation for\n * {@link TaskLauncher#cleanup(String)} and {@link TaskLauncher#destroy(String)}.\n */\nabstract class AbstractCloudFoundryTaskLauncher extends AbstractCloudFoundryDeployer implements TaskLauncher {\n\n\tprivate static final Logger logger = LoggerFactory.getLogger(AbstractCloudFoundryTaskLauncher.class);\n\n\tprivate final CloudFoundryClient client;\n\n\tprivate final Mono<String> organizationId;\n\n\tprivate final Mono<String> spaceId;\n\n\tAbstractCloudFoundryTaskLauncher(CloudFoundryClient client,\n\t\t\tCloudFoundryDeploymentProperties deploymentProperties,\n\t\t\tRuntimeEnvironmentInfo runtimeEnvironmentInfo) {\n\t\tsuper(deploymentProperties, runtimeEnvironmentInfo);\n\t\tthis.client = client;\n\t\torganizationId = organizationId();\n\t\tspaceId = spaceId();\n\t}\n\n\t/**\n\t * Setup a reactor flow to cancel a running task. This implementation opts to be\n\t * asynchronous.\n\t *\n\t * @param id the task's id to be canceled as returned from the\n\t *     {@link TaskLauncher#launch(AppDeploymentRequest)}\n\t */\n\t@Override\n\tpublic void cancel(String id) {\n\t\trequestCancelTask(id)\n\t\t\t\t.timeout(Duration.ofSeconds(this.deploymentProperties.getApiTimeout()))\n\t\t\t\t.doOnSuccess(r -> logger.info(\"Task {} cancellation successful\", id))\n\t\t\t\t.doOnError(logError(String.format(\"Task %s cancellation failed\", id)))\n\t\t\t\t.subscribe();\n\t}\n\n\t/**\n\t * Lookup the current status based on task id.\n\t *\n\t * @param id taskId as returned from the {@link TaskLauncher#launch(AppDeploymentRequest)}\n\t * @return the current task status\n\t */\n\t@Override\n\tpublic TaskStatus status(String id) {\n\t\ttry {\n\t\t\treturn getStatus(id)\n\t\t\t\t\t.doOnSuccess(v -> logger.info(\"Successfully computed status [{}] for id={}\", v, id))\n\t\t\t\t\t.doOnError(logError(String.format(\"Failed to compute status for %s\", id)))\n\t\t\t\t\t.block(Duration.ofMillis(this.deploymentProperties.getStatusTimeout()));\n\t\t}\n\t\tcatch (Exception timeoutDueToBlock) {\n\t\t\tlogger.error(\"Caught exception while querying for status of id={}\", id, timeoutDueToBlock);\n\t\t\treturn createErrorTaskStatus(id);\n\t\t}\n\t}\n\n\t@Override\n\tpublic int getRunningTaskExecutionCount() {\n\n\t\tMono<Tuple2<String,String>> orgAndSpace = Mono.zip(organizationId, spaceId);\n\n\t\tMono<ListTasksRequest> listTasksRequest = orgAndSpace.map(tuple->\n\t\t\t\tListTasksRequest.builder()\n\t\t\t\t.state(TaskState.RUNNING)\n\t\t\t\t.organizationId(tuple.getT1())\n\t\t\t\t.spaceId(tuple.getT2())\n\t\t\t\t.build());\n\n\t\treturn listTasksRequest.flatMap(request-> this.client.tasks().list(request))\n\t\t\t\t.map(listTasksResponse -> listTasksResponse.getPagination().getTotalResults())\n\t\t\t\t.doOnError(logError(\"Failed to list running tasks\"))\n\t\t\t\t.doOnSuccess(count -> logger.info(String.format(\"There are %d running tasks\", count)))\n\t\t\t\t.block(Duration.ofMillis(this.deploymentProperties.getStatusTimeout()));\n\t}\n\n\t@Override\n\tpublic int getMaximumConcurrentTasks() {\n\t\treturn this.deploymentProperties.getMaximumConcurrentTasks();\n\t}\n\n\tprotected boolean maxConcurrentExecutionsReached() {\n\t\treturn this.getRunningTaskExecutionCount() >= this.getMaximumConcurrentTasks();\n\t}\n\n\tprivate Mono<TaskStatus> getStatus(String id) {\n\t\treturn requestGetTask(id)\n\t\t\t\t.map(this::toTaskStatus)\n\t\t\t\t.onErrorResume(isNotFoundError(), t -> {\n\t\t\t\t\tlogger.debug(\"Task for id={} does not exist\", id);\n\t\t\t\t\treturn Mono.just(new TaskStatus(id, LaunchState.unknown, null));\n\t\t\t\t})\n\t\t\t\t.transform(statusRetry(id))\n\t\t\t\t.onErrorReturn(createErrorTaskStatus(id));\n\t}\n\n\tprivate TaskStatus createErrorTaskStatus(String id) {\n\t\treturn new TaskStatus(id, LaunchState.error, null);\n\t}\n\n\tprotected TaskStatus toTaskStatus(GetTaskResponse response) {\n\t\tMap<String, String> attributes = new HashMap<>();\n\t\t//retrieve CF-GUID for retrieving logs\n\t\tAssert.notNull(response.getTaskRelationships(), \"response must contain task relationships.\");\n\t\tAssert.notNull(response.getTaskRelationships().getApp(), \"app in the taskRelationships of the response must not be null\");\n\t\tAssert.notNull(response.getTaskRelationships().getApp().getData(), \"data in the app of the task relationships within the response must not be null\");\n\t\tattributes.put(\"app-cf-guid\", response.getTaskRelationships().getApp().getData().getId());\n\t\tswitch (response.getState()) {\n\t\tcase SUCCEEDED:\n\t\t\treturn new TaskStatus(response.getId(), LaunchState.complete, attributes);\n\t\tcase RUNNING:\n\t\t\treturn new TaskStatus(response.getId(), LaunchState.running, attributes);\n\t\tcase PENDING:\n\t\t\treturn new TaskStatus(response.getId(), LaunchState.launching, attributes);\n\t\tcase CANCELING:\n\t\t\treturn new TaskStatus(response.getId(), LaunchState.cancelled, attributes);\n\t\tcase FAILED:\n\t\t\treturn new TaskStatus(response.getId(), LaunchState.failed, attributes);\n\t\tdefault:\n\t\t\tthrow new IllegalStateException(String.format(\"Unsupported CF task state %s\", response.getState()));\n\t\t}\n\t}\n\n\tprivate Mono<CancelTaskResponse> requestCancelTask(String taskId) {\n\t\treturn this.client.tasks()\n\t\t\t\t.cancel(CancelTaskRequest.builder()\n\t\t\t\t\t\t.taskId(taskId)\n\t\t\t\t\t\t.build());\n\t}\n\n\tprivate Mono<GetTaskResponse> requestGetTask(String taskId) {\n\t\treturn this.client.tasks()\n\t\t\t\t.get(GetTaskRequest.builder()\n\t\t\t\t\t\t.taskId(taskId)\n\t\t\t\t\t\t.build());\n\t}\n\n\tprivate Mono<String> organizationId() {\n\t\tString org = this.runtimeEnvironmentInfo.getPlatformSpecificInfo().get(CloudFoundryPlatformSpecificInfo.ORG);\n\t\tAssert.hasText(org,\"Missing runtimeEnvironmentInfo : 'org' required.\");\n\t\tListOrganizationsRequest listOrganizationsRequest =  ListOrganizationsRequest.builder()\n\t\t\t\t.name(org).build();\n\t\treturn this.client.organizations().list(listOrganizationsRequest)\n\t\t\t\t.doOnError(logError(\"Failed to list organizations\"))\n\t\t\t\t.map(listOrganizationsResponse -> listOrganizationsResponse.getResources().get(0).getMetadata().getId())\n\t\t\t\t.cache(aValue -> Duration.ofMillis(Long.MAX_VALUE), aValue -> Duration.ZERO, () -> Duration.ZERO);\n\t}\n\n\tprivate Mono<String> spaceId() {\n\t\tString space = this.runtimeEnvironmentInfo.getPlatformSpecificInfo().get(CloudFoundryPlatformSpecificInfo.SPACE);\n\t\tAssert.hasText(space,\"Missing runtimeEnvironmentInfo : 'space' required.\");\n\t\tListSpacesRequest listSpacesRequest = ListSpacesRequest.builder()\n\t\t\t\t.name(space).build();\n\t\treturn this.client.spaces().list(listSpacesRequest)\n\t\t\t\t.doOnError(logError(\"Failed to list spaces\"))\n\t\t\t\t.map(listSpacesResponse -> listSpacesResponse.getResources().get(0).getMetadata().getId())\n\t\t\t\t.cache(aValue -> Duration.ofMillis(Long.MAX_VALUE), aValue -> Duration.ZERO, () -> Duration.ZERO);\n\t}\n\n\t@Override\n\tpublic void cleanup(String id) {\n\n\t}\n\n\t@Override\n\tpublic void destroy(String appName) {\n\n\t}\n}\n"
  },
  {
    "path": "spring-cloud-deployer-cloudfoundry/src/main/java/org/springframework/cloud/deployer/spi/cloudfoundry/AppNameGenerator.java",
    "content": "/*\n * Copyright 2016 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.cloudfoundry;\n\n/**\n * Strategy interface for generating the names of deployed applications. *\n *\n * @author Soby Chacko\n */\npublic interface AppNameGenerator {\n\n\t/**\n\t * Generate an application name given a base name as the starting point.\n\t *\n\t * @param appName base application name\n\t * @return the generated app name\n\t */\n\tString generateAppName(String appName);\n\n}\n"
  },
  {
    "path": "spring-cloud-deployer-cloudfoundry/src/main/java/org/springframework/cloud/deployer/spi/cloudfoundry/ApplicationLogAccessor.java",
    "content": "/*\n * Copyright 2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.cloudfoundry;\n\n\nimport org.cloudfoundry.logcache.v1.Envelope;\nimport org.cloudfoundry.logcache.v1.Log;\nimport org.cloudfoundry.logcache.v1.LogCacheClient;\nimport org.cloudfoundry.logcache.v1.ReadRequest;\nimport org.cloudfoundry.logcache.v1.ReadResponse;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.util.Assert;\nimport reactor.core.publisher.Flux;\n\nimport java.time.Duration;\nimport java.util.List;\n\n/**\n * Provide api access to retrieve logs for applications.\n *\n * @author Glenn Renfro\n * @author Chris Bono\n *\n * @since 2.9.3\n */\npublic class ApplicationLogAccessor {\n\n    private final static int MAX_LOG_LIMIT = 1000;\n\n    private final static Logger logger = LoggerFactory.getLogger(ApplicationLogAccessor.class);\n    private final LogCacheClient logCacheClient;\n\n    public ApplicationLogAccessor(LogCacheClient logCacheClient) {\n        Assert.notNull(logCacheClient, \"logCacheClient must not be null\");\n        this.logCacheClient = logCacheClient;\n    }\n\n    /**\n     * Retrieve logs for specified deployment id.\n     * @param deploymentId the deployment id of the application.\n     * @param apiTimeout specify duration of the timeout for the api.\n     * @return String containing the log information or empty string if no entries are available.\n     */\n    public String getLog(String deploymentId, Duration apiTimeout) {\n        logger.debug(\"Retrieving log for deploymentId:{} with apiTimeout:{}\", deploymentId, apiTimeout);\n        Assert.hasText(deploymentId, \"id must have text and not null\");\n        Assert.notNull(apiTimeout, \"apiTimeout must not be null\");\n        StringBuilder stringBuilder = new StringBuilder();\n        ReadRequest request = ReadRequest.builder().sourceId(deploymentId).limit(MAX_LOG_LIMIT).descending(true).build();\n        List<Log> logs = this.logCacheClient\n                .read(request)\n                .flatMapMany(this::responseToEnvelope)\n                .collectList()\n                .block(apiTimeout);\n        // if no log exists the result set is null.\n        if(logs == null) {\n            return \"\";\n        }\n        logs.forEach((log) -> {\n            stringBuilder.append(log.getPayloadAsText());\n            stringBuilder.append(System.lineSeparator());\n        });\n        String [] lines = stringBuilder.toString().split(\"\\n\");\n        StringBuilder stringBuilderReconstruct = new StringBuilder();\n        for(int i = lines.length -1  ; i >= 0  ; i--) {\n            stringBuilderReconstruct.append(lines[i]);\n            if ( i > 0 ) {\n                stringBuilderReconstruct.append(\"\\n\");\n            }\n        }\n        return stringBuilderReconstruct.toString();\n    }\n\n    private Flux<Log> responseToEnvelope(ReadResponse response) {\n        return Flux.fromIterable(response.getEnvelopes().getBatch())\n                .filter(envelope -> envelope.getLog() != null)\n                .map(Envelope::getLog);\n    }\n}\n"
  },
  {
    "path": "spring-cloud-deployer-cloudfoundry/src/main/java/org/springframework/cloud/deployer/spi/cloudfoundry/CfEnvAwareAppDeploymentRequest.java",
    "content": "/*\n * Copyright 2020-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.cloudfoundry;\n\nimport org.springframework.cloud.deployer.spi.core.AppDeploymentRequest;\n\n/**\n * Copies an {@link AppDeploymentRequest} using a {@link CfEnvAwareResource}.\n *\n * @author David Turanski\n * @since 2.4\n */\nclass CfEnvAwareAppDeploymentRequest extends AppDeploymentRequest {\n\n\tstatic CfEnvAwareAppDeploymentRequest of(AppDeploymentRequest appDeploymentRequest) {\n\t\treturn new CfEnvAwareAppDeploymentRequest(appDeploymentRequest);\n\t}\n\n\tprivate CfEnvAwareAppDeploymentRequest(AppDeploymentRequest appDeploymentRequest) {\n\t\tsuper(appDeploymentRequest.getDefinition(),\n\t\t\t\tCfEnvAwareResource.of(appDeploymentRequest.getResource()),\n\t\t\t\tappDeploymentRequest.getDeploymentProperties(),\n\t\t\t\tappDeploymentRequest.getCommandlineArguments());\n\t}\n}\n"
  },
  {
    "path": "spring-cloud-deployer-cloudfoundry/src/main/java/org/springframework/cloud/deployer/spi/cloudfoundry/CfEnvAwareResource.java",
    "content": "/*\n * Copyright 2020-2024 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.cloudfoundry;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.net.MalformedURLException;\nimport java.net.URI;\nimport java.net.URL;\nimport java.net.URLClassLoader;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Locale;\nimport java.util.Optional;\n\nimport org.apache.commons.logging.Log;\nimport org.apache.commons.logging.LogFactory;\n\nimport org.springframework.boot.loader.archive.JarFileArchive;\nimport org.springframework.core.io.Resource;\n\n/**\n * A {@link Resource} implementation that delegates to a resource and keeps the state of a CfEnv dependency\n * as an {@link Optional} which may be empty, true, or false.\n *\n * @author David Turanski\n * @since 2.4\n */\nclass CfEnvAwareResource implements Resource {\n\tprivate final Resource resource;\n\n\tprivate final boolean hasCfEnv;\n\n\tstatic CfEnvAwareResource of(Resource resource) {\n\t\treturn new CfEnvAwareResource(resource);\n\t}\n\tprivate CfEnvAwareResource(Resource resource) {\n\t\tthis.resource = resource;\n\t\tthis.hasCfEnv = CfEnvResolver.hasCfEnv(this);\n\t}\n\n\t@Override\n\tpublic boolean exists() {\n\t\treturn resource.exists();\n\t}\n\n\t@Override\n\tpublic URL getURL() throws IOException {\n\t\treturn resource.getURL();\n\t}\n\n\t@Override\n\tpublic URI getURI() throws IOException {\n\t\treturn resource.getURI();\n\t}\n\n\t@Override\n\tpublic File getFile() throws IOException {\n\t\treturn resource.getFile();\n\t}\n\n\t@Override\n\tpublic long contentLength() throws IOException {\n\t\treturn resource.contentLength();\n\t}\n\n\t@Override\n\tpublic long lastModified() throws IOException {\n\t\treturn resource.lastModified();\n\t}\n\n\t@Override\n\tpublic Resource createRelative(String s) throws IOException {\n\t\treturn resource.createRelative(s);\n\t}\n\n\t@Override\n\tpublic String getFilename() {\n\t\treturn resource.getFilename();\n\t}\n\n\t@Override\n\tpublic String getDescription() {\n\t\treturn resource.getDescription();\n\t}\n\n\t@Override\n\tpublic InputStream getInputStream() throws IOException {\n\t\treturn resource.getInputStream();\n\t}\n\n\tboolean hasCfEnv() {\n\t\treturn this.hasCfEnv;\n\t}\n\n\t/**\n\t * Inspect the {@link CfEnvAwareResource} to determine if it contains a dependency on <i>io.pivotal.cfenv.core.CfEnv</i>.\n\t * Cache the result in the resource.\n\t */\n\tstatic class CfEnvResolver {\n\n\t\tprivate static Log logger = LogFactory.getLog(CfEnvResolver.class);\n\n\t\tprivate static final String CF_ENV = \"io.pivotal.cfenv.core.CfEnv\";\n\n\t\tstatic boolean hasCfEnv(CfEnvAwareResource app\n\t\t) {\n\t\t\ttry {\n\t\t\t\tString scheme = app.getURI().getScheme().toLowerCase(Locale.ROOT);\n\t\t\t\tif (scheme.equals(\"docker\")) {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t}\n\t\t\tcatch (IOException e) {\n\t\t\t\tthrow new IllegalArgumentException(e.getMessage(), e);\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\tJarFileArchive archive = new JarFileArchive(app.getFile());\n\t\t\t\tList<URL> urls = new ArrayList<>();\n\t\t\t\tarchive.getNestedArchives(entry -> entry.getName().endsWith(\".jar\"), null).forEachRemaining(a -> {\n\t\t\t\t\ttry {\n\t\t\t\t\t\turls.add(a.getUrl());\n\t\t\t\t\t}\n\t\t\t\t\tcatch (MalformedURLException e) {\n\t\t\t\t\t\tlogger.error(\"Unable to process nested archive \" +  e.getMessage());\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t\tURLClassLoader classLoader = new URLClassLoader(urls.toArray(new URL[urls.size()]), null);\n\t\t\t\ttry {\n\t\t\t\t\tlogger.info(\"Attempting to load class CFEnv\");\n\t\t\t\t\tClass.forName(CF_ENV, false, classLoader);\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t\tcatch (UnsupportedClassVersionError err) {\n\t\t\t\t\tlogger.debug(app.getFilename() + \" contains \" + CF_ENV);\n\t\t\t\t\t// class found but can't load it i.e. because it's newer class version\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t\tcatch (ClassNotFoundException e) {\n\t\t\t\t\tlogger.debug(app.getFilename() + \" doesn't contain \" + CF_ENV);\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t}\n\t\t\tcatch (Exception e) {\n\t\t\t\tlogger.warn(\"Unable to determine dependencies for file \" + app.getFilename());\n\t\t\t}\n\t\t\treturn false;\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "spring-cloud-deployer-cloudfoundry/src/main/java/org/springframework/cloud/deployer/spi/cloudfoundry/CfEnvConfigurer.java",
    "content": "/*\n * Copyright 2020-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.cloudfoundry;\n\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.stream.Stream;\n\nimport org.apache.commons.logging.Log;\nimport org.apache.commons.logging.LogFactory;\n\nimport org.springframework.util.StringUtils;\n\n/**\n * Provides methods to configure environment, application properties, and command line\n * args if the deployed artifact uses java-cfenv.\n *\n * @author David Turanski\n * @since 2.4\n */\nclass CfEnvConfigurer {\n\n\tprivate static final Log log = LogFactory.getLog(CfEnvConfigurer.class);\n\n\tstatic final String SPRING_PROFILES_ACTIVE = \"SPRING_PROFILES_ACTIVE\";\n\n\tstatic final String SPRING_PROFILES_ACTIVE_FQN = \"spring.profiles.active\";\n\n\tstatic final String SPRING_PROFILES_ACTIVE_HYPHENATED = \"spring-profiles-active\";\n\n\tstatic final String CLOUD_PROFILE_NAME = \"cloud\";\n\n\tstatic final String JBP_CONFIG_SPRING_AUTO_RECONFIGURATION = \"JBP_CONFIG_SPRING_AUTO_RECONFIGURATION\";\n\n\tstatic final String ENABLED_FALSE = \"{ enabled: false }\";\n\n\t/**\n\t * Disable Java Buildpack Spring Auto-reconfiguration.\n\t *\n\t * @param environment a map containing environment variables\n\t * @return an copy of the map setting the environment variable needed to disable\n\t * auto-reconfiguration\n\t */\n\tstatic Map<String, String> disableJavaBuildPackAutoReconfiguration(Map<String, String> environment) {\n\t\tlog.debug(\"Disabling 'JBP_CONFIG_SPRING_AUTO_RECONFIGURATION'\");\n\t\tMap<String, String> updatedEnvironment = new HashMap<>(environment);\n\t\tupdatedEnvironment.putIfAbsent(JBP_CONFIG_SPRING_AUTO_RECONFIGURATION, ENABLED_FALSE);\n\t\treturn updatedEnvironment;\n\t}\n\n\t/**\n\t * Activate the <i>cloud</i> profile. Add to a key that binds to\n\t * <i>spring.profiles.active</i> if one exists.\n\t * @param environment a map containing environment variables or application properties\n\t * @param keyToCreate create a new entry using a preferred key if none exists.\n\t * @return the updated map\n\t */\n\tstatic Map<String, String> activateCloudProfile(Map<String, String> environment, String keyToCreate) {\n\t\tlog.debug(\"Activating cloud profile\");\n\t\tMap<String, String> updatedEnvironment = new HashMap<>(environment);\n\n\t\tif (appendToExistingEntry(updatedEnvironment, SPRING_PROFILES_ACTIVE, CLOUD_PROFILE_NAME)) {\n\t\t\treturn updatedEnvironment;\n\t\t}\n\t\telse if (appendToExistingEntry(updatedEnvironment, SPRING_PROFILES_ACTIVE_FQN, CLOUD_PROFILE_NAME)) {\n\t\t\treturn updatedEnvironment;\n\t\t}\n\t\telse if (appendToExistingEntry(updatedEnvironment, SPRING_PROFILES_ACTIVE_HYPHENATED, CLOUD_PROFILE_NAME)) {\n\t\t\treturn updatedEnvironment;\n\t\t}\n\t\t// If Key provided, create new spring profiles active entry.\n\t\tif (StringUtils.hasText(keyToCreate)) {\n\t\t\tupdatedEnvironment.put(keyToCreate, CLOUD_PROFILE_NAME);\n\t\t}\n\n\t\treturn updatedEnvironment;\n\t}\n\n\t/**\n\t * Process a command line argument to append the <i>cloud</i> profile if it binds to\n\t * <i>spring.profiles.active</i>.\n\t *\n\t * @param arg the current value\n\t * @return the updated value\n\t */\n\tstatic String appendCloudProfileToSpringProfilesActiveArg(String arg) {\n\t\tif ((arg.contains(SPRING_PROFILES_ACTIVE_FQN) ||\n\t\t\t\targ.contains(SPRING_PROFILES_ACTIVE_HYPHENATED) ||\n\t\t\t\targ.contains(SPRING_PROFILES_ACTIVE)) && arg.contains(\"=\")) {\n\t\t\tString[] tokens = arg.split(\"=\");\n\t\t\targ = String.join(\"=\", tokens[0], appendToValueIfPresent(tokens[1], CLOUD_PROFILE_NAME));\n\t\t}\n\t\treturn arg;\n\t}\n\n\tprivate static boolean appendToExistingEntry(Map<String, String> environment, String key, String value) {\n\t\tif (environment.containsKey(key)) {\n\t\t\tString current = environment.get(key);\n\t\t\tenvironment.put(key, appendToValueIfPresent(current, value));\n\t\t\treturn true;\n\t\t}\n\t\treturn false;\n\t}\n\n\tprivate static String appendToValueIfPresent(String current, String value) {\n\t\tif (StringUtils.hasText(current)) {\n\t\t\tif (!Stream.of(current.split(\",\")).filter(s -> s.trim().equals(value)).findFirst().isPresent()) {\n\t\t\t\treturn current.join(\",\", current, value);\n\t\t\t}\n\t\t\treturn current;\n\t\t}\n\t\treturn value;\n\t}\n}\n"
  },
  {
    "path": "spring-cloud-deployer-cloudfoundry/src/main/java/org/springframework/cloud/deployer/spi/cloudfoundry/CloudFoundryActuatorTemplate.java",
    "content": "/*\n * Copyright 2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.cloudfoundry;\n\nimport java.util.Optional;\n\nimport org.springframework.cloud.deployer.spi.app.AbstractActuatorTemplate;\nimport org.springframework.cloud.deployer.spi.app.AppAdmin;\nimport org.springframework.cloud.deployer.spi.app.AppDeployer;\nimport org.springframework.cloud.deployer.spi.app.AppInstanceStatus;\nimport org.springframework.http.HttpHeaders;\nimport org.springframework.web.client.RestTemplate;\nimport org.springframework.web.util.UriComponentsBuilder;\n\n/**\n * Access the actuator endpoint for an app instance deployed to Cloud Foundry.\n *\n * @author David Turanski\n */\npublic class CloudFoundryActuatorTemplate extends AbstractActuatorTemplate {\n\n\tpublic CloudFoundryActuatorTemplate(RestTemplate restTemplate, AppDeployer appDeployer, AppAdmin appAdmin) {\n\t\tsuper(restTemplate, appDeployer, appAdmin);\n\t}\n\n\t@Override\n\tprotected String actuatorUrlForInstance(AppInstanceStatus appInstanceStatus) {\n\t\treturn UriComponentsBuilder.fromHttpUrl(appInstanceStatus.getAttributes().get(\"url\"))\n\t\t\t\t.path(\"/actuator\").toUriString();\n\t}\n\n\t@Override\n\tpublic Optional<HttpHeaders> httpHeadersForInstance(AppInstanceStatus appInstanceStatus) {\n\t\tHttpHeaders headers = new HttpHeaders();\n\t\theaders.add(\"X-Cf-App-Instance\", String.format(\"%s:%d\", appInstanceStatus.getAttributes()\n\t\t\t\t.get(CloudFoundryAppInstanceStatus.CF_GUID),\n\t\t\t\tInteger.valueOf(appInstanceStatus.getAttributes().get(CloudFoundryAppInstanceStatus.INDEX))));\n\t\treturn Optional.of(headers);\n\t}\n}\n"
  },
  {
    "path": "spring-cloud-deployer-cloudfoundry/src/main/java/org/springframework/cloud/deployer/spi/cloudfoundry/CloudFoundryAppDeployer.java",
    "content": "/*\n * Copyright 2016-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.cloudfoundry;\n\nimport java.time.Duration;\nimport java.util.Collections;\nimport java.util.HashSet;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.Optional;\nimport java.util.Set;\nimport java.util.concurrent.TimeUnit;\nimport java.util.stream.Collectors;\n\nimport com.github.benmanes.caffeine.cache.Cache;\nimport com.github.benmanes.caffeine.cache.Caffeine;\nimport org.cloudfoundry.client.v2.ClientV2Exception;\nimport org.cloudfoundry.operations.CloudFoundryOperations;\nimport org.cloudfoundry.operations.applications.ApplicationDetail;\nimport org.cloudfoundry.operations.applications.ApplicationHealthCheck;\nimport org.cloudfoundry.operations.applications.ApplicationManifest;\nimport org.cloudfoundry.operations.applications.ApplicationSummary;\nimport org.cloudfoundry.operations.applications.DeleteApplicationRequest;\nimport org.cloudfoundry.operations.applications.Docker;\nimport org.cloudfoundry.operations.applications.GetApplicationRequest;\nimport org.cloudfoundry.operations.applications.InstanceDetail;\nimport org.cloudfoundry.operations.applications.PushApplicationManifestRequest;\nimport org.cloudfoundry.operations.applications.Route;\nimport org.cloudfoundry.operations.applications.ScaleApplicationRequest;\nimport org.cloudfoundry.operations.applications.StartApplicationRequest;\nimport org.cloudfoundry.operations.services.BindServiceInstanceRequest;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.yaml.snakeyaml.Yaml;\nimport reactor.cache.CacheMono;\nimport reactor.core.publisher.Flux;\nimport reactor.core.publisher.Mono;\nimport reactor.core.publisher.Signal;\nimport reactor.util.retry.Retry;\n\nimport org.springframework.cloud.deployer.spi.app.AppDeployer;\nimport org.springframework.cloud.deployer.spi.app.AppScaleRequest;\nimport org.springframework.cloud.deployer.spi.app.AppStatus;\nimport org.springframework.cloud.deployer.spi.app.DeploymentState;\nimport org.springframework.cloud.deployer.spi.app.MultiStateAppDeployer;\nimport org.springframework.cloud.deployer.spi.core.AppDeploymentRequest;\nimport org.springframework.cloud.deployer.spi.core.RuntimeEnvironmentInfo;\nimport org.springframework.util.StringUtils;\n\n/**\n * A deployer that targets Cloud Foundry using the public API.\n *\n * @author Eric Bottard\n * @author Greg Turnquist\n * @author Ben Hale\n * @author Ilayaperumal Gopinathan\n * @author David Turanski\n */\npublic class CloudFoundryAppDeployer extends AbstractCloudFoundryDeployer implements MultiStateAppDeployer {\n\n\tprivate static final Logger logger = LoggerFactory.getLogger(CloudFoundryAppDeployer.class);\n\n\tprivate static final String CF_GUID_ID = \"cf-guid\";\n\n\tprivate final AppNameGenerator applicationNameGenerator;\n\n\tprivate final CloudFoundryOperations operations;\n\n\tprivate final Cache<String, ApplicationDetail> cache = Caffeine.newBuilder().expireAfterWrite(5, TimeUnit.SECONDS)\n\t\t.build();\n\n\tprivate final ApplicationLogAccessor applicationLogAccessor;\n\n\tpublic CloudFoundryAppDeployer(\n\t\tAppNameGenerator applicationNameGenerator,\n\t\tCloudFoundryDeploymentProperties deploymentProperties,\n\t\tCloudFoundryOperations operations,\n\t\tRuntimeEnvironmentInfo runtimeEnvironmentInfo,\n\t\tApplicationLogAccessor applicationLogAccessor\n\t) {\n\t\tsuper(deploymentProperties, runtimeEnvironmentInfo);\n\t\tthis.operations = operations;\n\t\tthis.applicationNameGenerator = applicationNameGenerator;\n\t\tthis.applicationLogAccessor = applicationLogAccessor;\n\t}\n\n\t@Override\n\tpublic String deploy(AppDeploymentRequest appDeploymentRequest) {\n\t\tfinal AppDeploymentRequest request = CfEnvAwareAppDeploymentRequest.of(appDeploymentRequest);\n\t\tlogger.trace(\"Entered deploy: Deploying AppDeploymentRequest: AppDefinition = {}, Resource = {}, Deployment Properties = {}\",\n\t\t\trequest.getDefinition(), request.getResource(), request.getDeploymentProperties());\n\t\tString deploymentId = deploymentId(request);\n\n\t\tlogger.trace(\"deploy: Getting Status for Deployment Id = {}\", deploymentId);\n\t\tgetStatus(deploymentId)\n\t\t\t.doOnNext(status -> assertApplicationDoesNotExist(deploymentId, status))\n\t\t\t// Need to block here to be able to throw exception early\n\t\t\t.block(Duration.ofSeconds(this.deploymentProperties.getApiTimeout()));\n\n\t\tlogger.trace(\"deploy: Pushing application\");\n\t\tpushApplication(deploymentId, request)\n\t\t\t.timeout(Duration.ofSeconds(this.deploymentProperties.getApiTimeout()))\n\t\t\t.doOnSuccess(item -> {\n\t\t\t\tlogger.info(\"Successfully deployed {}\", deploymentId);\n\t\t\t})\n\t\t\t.doOnError(error -> {\n\t\t\t\tif (isNotFoundError().test(error)) {\n\t\t\t\t\tlogger.warn(\"Unable to deploy application. It may have been destroyed before start completed: \" + error.getMessage());\n\t\t\t\t} else {\n\t\t\t\t\tlogError(String.format(\"Failed to deploy %s\", deploymentId)).accept(error);\n\t\t\t\t}\n\t\t\t})\n\t\t\t\t.doOnSuccess((c) -> {\n\t\t\t\t\tdeleteLocalApplicationResourceFile(request);\n\t\t\t\t}).doOnError((c) -> {\n\t\t\t\t\tdeleteLocalApplicationResourceFile(request);\n\t\t\t\t})\n\t\t\t.subscribe();\n\n\t\tlogger.trace(\"Exiting deploy().  Deployment Id = {}\", deploymentId);\n\t\treturn deploymentId;\n\t}\n\n\t@Override\n\tpublic Map<String, DeploymentState> states(String... ids) {\n\t\treturn requestSummary()\n\t\t\t.collect(Collectors.toMap(ApplicationSummary::getName, this::mapShallowAppState))\n\t\t\t.block();\n\t}\n\n\t@Override\n\tpublic Mono<Map<String, DeploymentState>> statesReactive(String... ids) {\n\t\treturn requestSummary()\n\t\t\t.collect(Collectors.toMap(ApplicationSummary::getName, this::mapShallowAppState));\n\t}\n\n\tprivate DeploymentState mapShallowAppState(ApplicationSummary applicationSummary) {\n\t\tif (applicationSummary.getRunningInstances().equals(applicationSummary.getInstances())) {\n\t\t\treturn DeploymentState.deployed;\n\t\t} else if (applicationSummary.getInstances() > 0) {\n\t\t\treturn DeploymentState.partial;\n\t\t} else {\n\t\t\treturn DeploymentState.undeployed;\n\t\t}\n\t}\n\n\t@Override\n\tprotected Map<String, String> mergeEnvironmentVariables(String deploymentId, AppDeploymentRequest request) {\n\t\tMap<String, String> envVariables = super.mergeEnvironmentVariables(deploymentId, request);\n\t\tenvVariables.putAll(getCommandLineArguments(request));\n\t\tString group = request.getDeploymentProperties().get(AppDeployer.GROUP_PROPERTY_KEY);\n\t\tif (StringUtils.hasText(group)) {\n\t\t\tenvVariables.put(\"SPRING_CLOUD_APPLICATION_GROUP\", group);\n\t\t}\n\t\tenvVariables.put(\"SPRING_CLOUD_APPLICATION_GUID\", \"${vcap.application.name}:${vcap.application.instance_index}\");\n\t\tenvVariables.put(\"SPRING_APPLICATION_INDEX\", \"${vcap.application.instance_index}\");\n\t\tthis.deploymentProperties.getAppAdmin().addCredentialsToAppEnvironment(envVariables);\n\t\treturn envVariables;\n\t}\n\n\tprivate Map<String, String> getCommandLineArguments(AppDeploymentRequest request) {\n\t\tif (request.getCommandlineArguments().isEmpty()) {\n\t\t\treturn Collections.emptyMap();\n\t\t}\n\n\t\tString argumentsAsString = request.getCommandlineArguments().stream()\n\t\t\t.collect(Collectors.joining(\" \"));\n\t\tString yaml = new Yaml().dump(Collections.singletonMap(\"arguments\", argumentsAsString));\n\n\t\treturn Collections.singletonMap(\"JBP_CONFIG_JAVA_MAIN\", yaml);\n\t}\n\n\t@Override\n\tpublic AppStatus status(String id) {\n\t\ttry {\n\t\t\treturn getStatus(id)\n\t\t\t\t.doOnSuccess(v -> logger.info(\"Successfully computed status [{}] for {}\", v, id))\n\t\t\t\t.doOnError(logError(String.format(\"Failed to compute status for %s\", id)))\n\t\t\t\t.block(Duration.ofMillis(this.deploymentProperties.getStatusTimeout()));\n\t\t} catch (Exception timeoutDueToBlock) {\n\t\t\tlogger.error(\"Caught exception while querying for status of {}\", id, timeoutDueToBlock);\n\t\t\treturn createErrorAppStatus(id);\n\t\t}\n\t}\n\n\t@Override\n\tpublic Mono<AppStatus> statusReactive(String id) {\n\t\treturn getStatus(id);\n\t}\n\n\t@Override\n\tpublic void undeploy(String id) {\n\t\tgetStatus(id)\n\t\t\t.doOnNext(status -> assertApplicationExists(id, status))\n\t\t\t// Need to block here to be able to throw exception early\n\t\t\t.block(Duration.ofSeconds(this.deploymentProperties.getApiTimeout()));\n\t\trequestDeleteApplication(id)\n\t\t\t.timeout(Duration.ofSeconds(this.deploymentProperties.getApiTimeout()))\n\t\t\t.doOnSuccess(v -> logger.info(\"Successfully undeployed app {}\", id))\n\t\t\t.doOnError(logError(String.format(\"Failed to undeploy app %s\", id)))\n\t\t\t.subscribe();\n\t}\n\n\t@Override\n\tpublic String getLog(String id) {\n\t\tAppStatus status = status(id);\n\t\tString cfGuid = status.getInstances().values().stream()\n\t\t\t\t.map((appInstanceStatus) -> appInstanceStatus.getAttributes().get(CF_GUID_ID))\n\t\t\t\t.filter(Objects::nonNull)\n\t\t\t\t.findFirst()\n\t\t\t\t.orElseThrow(() -> new IllegalArgumentException(\"Unable to find \" + CF_GUID_ID));\n\t\treturn applicationLogAccessor.getLog(cfGuid, Duration.ofSeconds(this.deploymentProperties.getApiTimeout()));\n\t}\n\n\t@Override\n\tpublic void scale(AppScaleRequest appScaleRequest) {\n\t\tlogger.info(\"Scaling the application instance using {}\", appScaleRequest);\n\t\tScaleApplicationRequest scaleApplicationRequest = ScaleApplicationRequest.builder()\n\t\t\t.name(appScaleRequest.getDeploymentId())\n\t\t\t.instances(appScaleRequest.getCount())\n\t\t\t.memoryLimit(memory(appScaleRequest))\n\t\t\t.diskLimit(diskQuota(appScaleRequest))\n\t\t\t.stagingTimeout(this.deploymentProperties.getStagingTimeout())\n\t\t\t.startupTimeout(this.deploymentProperties.getStartupTimeout())\n\t\t\t.build();\n\t\tthis.operations.applications().scale(scaleApplicationRequest)\n\t\t\t.timeout(Duration.ofSeconds(this.deploymentProperties.getApiTimeout()))\n\t\t\t.doOnSuccess(v -> logger.info(\"Scaled the application with deploymentId = {}\",\n\t\t\t\tappScaleRequest.getDeploymentId()))\n\t\t\t.doOnError(e -> logger.error(\"Error: {} scaling the app instance {}\", e.getMessage(),\n\t\t\t\tappScaleRequest.getDeploymentId()))\n\t\t\t.subscribe();\n\t}\n\n\n\tprivate void assertApplicationDoesNotExist(String deploymentId, AppStatus status) {\n\t\tDeploymentState state = status.getState();\n\t\tif (state != DeploymentState.unknown && state != DeploymentState.error) {\n\t\t\tthrow new IllegalStateException(String.format(\"App %s is already deployed with state %s\", deploymentId, state));\n\t\t}\n\t}\n\n\tprivate void assertApplicationExists(String deploymentId, AppStatus status) {\n\t\tDeploymentState state = status.getState();\n\t\tif (state == DeploymentState.unknown) {\n\t\t\tthrow new IllegalStateException(String.format(\"App %s is not in a deployed state\", deploymentId));\n\t\t}\n\t}\n\n\tprivate AppStatus createAppStatus(ApplicationDetail applicationDetail, String deploymentId) {\n\t\tlogger.trace(\"Gathering instances for \" + applicationDetail);\n\t\tlogger.trace(\"InstanceDetails: \" + applicationDetail.getInstanceDetails());\n\n\t\tAppStatus.Builder builder = AppStatus.of(deploymentId);\n\n\t\tint i = 0;\n\t\tfor (InstanceDetail instanceDetail : applicationDetail.getInstanceDetails()) {\n\t\t\tbuilder.with(new CloudFoundryAppInstanceStatus(applicationDetail, instanceDetail, i++));\n\t\t}\n\t\tfor (; i < applicationDetail.getInstances(); i++) {\n\t\t\tbuilder.with(new CloudFoundryAppInstanceStatus(applicationDetail, null, i));\n\t\t}\n\n\t\treturn builder.build();\n\t}\n\n\tprivate AppStatus createEmptyAppStatus(String deploymentId) {\n\t\treturn AppStatus.of(deploymentId)\n\t\t\t.build();\n\t}\n\n\tprivate AppStatus createErrorAppStatus(String deploymentId) {\n\t\treturn AppStatus.of(deploymentId)\n\t\t\t.generalState(DeploymentState.error)\n\t\t\t.build();\n\t}\n\n\tprivate String deploymentId(AppDeploymentRequest request) {\n\t\tString prefix = Optional.ofNullable(request.getDeploymentProperties().get(GROUP_PROPERTY_KEY))\n\t\t\t.map(group -> String.format(\"%s-\", group))\n\t\t\t.orElse(\"\");\n\n\t\tString appName = String.format(\"%s%s\", prefix, request.getDefinition().getName());\n\n\t\treturn this.applicationNameGenerator.generateAppName(appName);\n\t}\n\n\tprivate String domain(AppDeploymentRequest request) {\n\t\treturn Optional\n\t\t\t.ofNullable(request.getDeploymentProperties().get(CloudFoundryDeploymentProperties.DOMAIN_PROPERTY))\n\t\t\t.orElse(this.deploymentProperties.getDomain());\n\t}\n\n\n\tprivate Mono<AppStatus> getStatus(String deploymentId) {\n\t\treturn requestGetApplication(deploymentId)\n\t\t\t.map(applicationDetail -> createAppStatus(applicationDetail, deploymentId))\n\t\t\t.onErrorResume(IllegalArgumentException.class, t -> {\n\t\t\t\tlogger.debug(\"Application for {} does not exist.\", deploymentId);\n\t\t\t\treturn Mono.just(createEmptyAppStatus(deploymentId));\n\t\t\t})\n\t\t\t.transform(statusRetry(deploymentId))\n\t\t\t.onErrorReturn(createErrorAppStatus(deploymentId));\n\t}\n\n\tprivate ApplicationHealthCheck healthCheck(AppDeploymentRequest request) {\n\t\treturn Optional\n\t\t\t.ofNullable(request.getDeploymentProperties()\n\t\t\t\t.get(CloudFoundryDeploymentProperties.HEALTHCHECK_PROPERTY_KEY))\n\t\t\t.map(this::toApplicationHealthCheck).orElse(this.deploymentProperties.getHealthCheck());\n\t}\n\n\tprivate String healthCheckEndpoint(AppDeploymentRequest request) {\n\t\treturn Optional\n\t\t\t.ofNullable(request.getDeploymentProperties()\n\t\t\t\t.get(CloudFoundryDeploymentProperties.HEALTHCHECK_HTTP_ENDPOINT_PROPERTY_KEY))\n\t\t\t.orElse(this.deploymentProperties.getHealthCheckHttpEndpoint());\n\t}\n\n\tprivate Integer healthCheckTimeout(AppDeploymentRequest request) {\n\t\tString timeoutString = request.getDeploymentProperties().getOrDefault(\n\t\t\tCloudFoundryDeploymentProperties.HEALTHCHECK_TIMEOUT_PROPERTY_KEY,\n\t\t\tthis.deploymentProperties.getHealthCheckTimeout());\n\t\treturn Integer.parseInt(timeoutString);\n\t}\n\n\tprivate String host(AppDeploymentRequest request) {\n\t\treturn Optional\n\t\t\t.ofNullable(request.getDeploymentProperties().get(CloudFoundryDeploymentProperties.HOST_PROPERTY))\n\t\t\t.orElse(this.deploymentProperties.getHost());\n\t}\n\n\tprivate int instances(AppDeploymentRequest request) {\n\t\treturn Optional.ofNullable(request.getDeploymentProperties().get(AppDeployer.COUNT_PROPERTY_KEY))\n\t\t\t.map(Integer::parseInt)\n\t\t\t.orElse(this.deploymentProperties.getInstances());\n\t}\n\n\tprivate Mono<Void> pushApplication(String deploymentId, AppDeploymentRequest request) {\n\t\tApplicationManifest.Builder manifest = ApplicationManifest.builder()\n\t\t\t.path(getApplication(request)) // Only one of the two is non-null\n\t\t\t.disk(diskQuota(request))\n\t\t\t.environmentVariables(mergeEnvironmentVariables(deploymentId, request))\n\t\t\t.healthCheckType(healthCheck(request))\n\t\t\t.healthCheckHttpEndpoint(healthCheckEndpoint(request))\n\t\t\t.timeout(healthCheckTimeout(request))\n\t\t\t.instances(instances(request))\n\t\t\t.memory(memory(request))\n\t\t\t.name(deploymentId)\n\t\t\t.noRoute(toggleNoRoute(request))\n\t\t\t.services(servicesToBind(request));\n\n\t\tOptional.ofNullable(host(request)).ifPresent(manifest::host);\n\t\tOptional.ofNullable(domain(request)).ifPresent(manifest::domain);\n\t\tOptional.ofNullable(routePath(request)).ifPresent(manifest::routePath);\n\t\tif (route(request) != null) {\n\t\t\tmanifest.route(Route.builder().route(route(request)).build());\n\t\t}\n\t\tif (!routes(request).isEmpty()) {\n\t\t\tSet<Route> routes = routes(request).stream()\n\t\t\t\t.map(r -> Route.builder().route(r).build())\n\t\t\t\t.collect(Collectors.toSet());\n\t\t\tmanifest.routes(routes);\n\t\t}\n\t\tif (getDockerImage(request) != null) {\n\t\t\tlogger.info(\"Preparing to run a container from {}. This may take some time if the image must be downloaded from a remote container registry.\", request.getResource());\n\t\t\tmanifest.docker(Docker.builder().image(getDockerImage(request)).build());\n\t\t} else {\n\t\t\tmanifest.buildpacks(buildpacks(request));\n\t\t}\n\n\t\tif (!includesServiceParameters(request)) {\n\t\t\treturn pushApplicationWithNoServiceParameters(manifest.build(), deploymentId);\n\t\t} else {\n\t\t\treturn pushApplicationWithServiceParameters(manifest.build(), request, deploymentId);\n\t\t}\n\t}\n\n\tprivate Mono<Void> pushApplicationWithNoServiceParameters(ApplicationManifest manifest, String deploymentId) {\n\t\tlogger.debug(\"Pushing application manifest\");\n\t\treturn requestPushApplication(PushApplicationManifestRequest.builder()\n\t\t\t.manifest(manifest)\n\t\t\t.stagingTimeout(this.deploymentProperties.getStagingTimeout())\n\t\t\t.startupTimeout(this.deploymentProperties.getStartupTimeout())\n\t\t\t.build())\n\t\t\t.doOnSuccess(v -> logger.info(\"Done uploading bits for {}\", deploymentId))\n\t\t\t.doOnError(e -> logger.error(\"Error: {} creating app {}\", e.getMessage(), deploymentId));\n\t}\n\n\tprivate Mono<Void> requestBind(BindServiceInstanceRequest bindRequest) {\n\t\t// defer so that new reques gets created when retry does re-sub\n\t\treturn Mono.defer(() -> this.operations.services().bind(bindRequest))\n\t\t\t.doOnError(e -> {\n\t\t\t\tif (e instanceof ClientV2Exception) {\n\t\t\t\t\tClientV2Exception ce = (ClientV2Exception) e;\n\t\t\t\t\tif (ce.getCode() == 10001) {\n\t\t\t\t\t\tlogger.warn(\"Retry service bind due to concurrency error\");\n\t\t\t\t\t} else {\n\t\t\t\t\t\tlogger.warn(\"Received ClientV2Exception error from cf\", ce);\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tlogger.warn(\"Received error from cf\", e);\n\t\t\t\t}\n\t\t\t})\n\t\t\t// check expected code indicating concurrency error, aka\n\t\t\t// CF-ConcurrencyError(10001): The service broker could not perform this operation in parallel with other running operations\n\t\t\t// and retry those and let other errors to pass and fail fast\n\t\t\t.retryWhen(Retry.withThrowable(reactor.retry.Retry.onlyIf(c -> {\n\t\t\t\t\t\tif (c.exception() instanceof ClientV2Exception) {\n\t\t\t\t\t\t\tClientV2Exception e = (ClientV2Exception) c.exception();\n\t\t\t\t\t\t\tif (e.getCode() == 10001) {\n\t\t\t\t\t\t\t\treturn true;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn false;\n\t\t\t\t\t})\n\t\t\t\t\t// for now try 30 seconds and do some jitter to limit concurrency issues\n\t\t\t\t\t.timeout(Duration.ofSeconds(30))\n\t\t\t\t\t.randomBackoff(Duration.ofSeconds(1), Duration.ofSeconds(5))\n\t\t\t\t\t.doOnRetry(c -> logger.debug(\"Retrying cf call for {}\", bindRequest))\n\t\t\t));\n\t}\n\n\tprivate Mono<Void> pushApplicationWithServiceParameters(\n\t\tApplicationManifest manifest,\n\t\tAppDeploymentRequest request, String deploymentId\n\t) {\n\n\t\tlogger.debug(\"Pushing application manifest with no start\");\n\n\t\treturn requestPushApplication(PushApplicationManifestRequest.builder()\n\t\t\t.manifest(manifest)\n\t\t\t.noStart(true)\n\t\t\t.build())\n\t\t\t.doOnSuccess(v -> logger.info(\"Done uploading bits for {}\", deploymentId))\n\t\t\t.doOnError(e -> logger.error(String.format(\"Error creating app %s.  Exception Message %s\", deploymentId, e.getMessage())))\n\n\t\t\t.thenMany(Flux.fromStream(bindParameterizedServiceInstanceRequests(request, deploymentId)))\n\t\t\t.flatMap(bindRequest -> this.requestBind(bindRequest)\n\t\t\t\t.doOnSuccess(bv -> logger.info(\"Done binding service {} for {}\", bindRequest.getServiceInstanceName(), deploymentId))\n\t\t\t\t.doOnError(e -> logger.error(\"Error: {} binding service {}\", e.getMessage(), bindRequest.getServiceInstanceName())))\n\n\t\t\t.then(this.operations.applications()\n\t\t\t\t.start(StartApplicationRequest.builder()\n\t\t\t\t\t.name(deploymentId)\n\t\t\t\t\t.stagingTimeout(this.deploymentProperties.getStagingTimeout())\n\t\t\t\t\t.startupTimeout(this.deploymentProperties.getStartupTimeout())\n\t\t\t\t\t.build())\n\t\t\t\t.doOnSuccess(sv -> logger.info(\"Started app for {} \", deploymentId))\n\t\t\t\t.doOnError(e -> logger.error(\"Error: {} starting app for {}.\", e.getMessage(), deploymentId)))\n\n\t\t\t.doOnError(e -> logger.error(String.format(\"Error: %s creating app %s\", e.getMessage(), deploymentId), e));\n\t}\n\n\tprivate Mono<Void> requestDeleteApplication(String id) {\n\t\treturn this.operations.applications()\n\t\t\t.delete(DeleteApplicationRequest.builder()\n\t\t\t\t.deleteRoutes(deploymentProperties.isDeleteRoutes())\n\t\t\t\t.name(id)\n\t\t\t\t.build());\n\t}\n\n\tprivate Mono<ApplicationDetail> requestGetApplication(String id) {\n\t\treturn CacheMono\n\t\t\t.lookup(k -> Mono.defer(() -> {\n\t\t\t\tApplicationDetail ifPresent = cache.getIfPresent(id);\n\t\t\t\tlogger.debug(\"Cache get {}\", ifPresent);\n\t\t\t\treturn Mono.justOrEmpty(ifPresent).map(Signal::next);\n\t\t\t}), id)\n\t\t\t.onCacheMissResume(Mono.defer(() -> {\n\t\t\t\tlogger.debug(\"Cache miss {}\", id);\n\t\t\t\treturn getApplicationDetail(id);\n\t\t\t}))\n\t\t\t.andWriteWith((k, sig) -> Mono.fromRunnable(() -> {\n\t\t\t\tApplicationDetail ap = sig.get();\n\t\t\t\tif (ap != null) {\n\t\t\t\t\tlogger.debug(\"Cache put {} {}\", k, ap);\n\t\t\t\t\tcache.put(k, ap);\n\t\t\t\t}\n\t\t\t}));\n\t}\n\n\tprivate Mono<ApplicationDetail> getApplicationDetail(String id) {\n\t\treturn this.operations.applications()\n\t\t\t.get(GetApplicationRequest.builder()\n\t\t\t\t.name(id)\n\t\t\t\t.build());\n\t}\n\n\tprivate Mono<Void> requestPushApplication(PushApplicationManifestRequest request) {\n\t\treturn this.operations.applications()\n\t\t\t.pushManifest(request);\n\t}\n\n\tprivate Flux<ApplicationSummary> requestSummary() {\n\t\treturn this.operations.applications().list();\n\t}\n\n\tprivate String routePath(AppDeploymentRequest request) {\n\t\tString routePath = request.getDeploymentProperties().get(CloudFoundryDeploymentProperties.ROUTE_PATH_PROPERTY);\n\t\tif (StringUtils.hasText(routePath) && !routePath.startsWith(\"/\")) {\n\t\t\tthrow new IllegalArgumentException(\n\t\t\t\t\"Cloud Foundry routes must start with \\\"/\\\". Route passed = [\" + routePath + \"].\");\n\t\t}\n\t\treturn routePath;\n\t}\n\n\tprivate String route(AppDeploymentRequest request) {\n\t\treturn request.getDeploymentProperties().get(CloudFoundryDeploymentProperties.ROUTE_PROPERTY);\n\t}\n\n\tprivate Set<String> routes(AppDeploymentRequest request) {\n\t\tSet<String> routes = new HashSet<>();\n\t\troutes.addAll(this.deploymentProperties.getRoutes());\n\t\troutes.addAll(StringUtils.commaDelimitedListToSet(\n\t\t\trequest.getDeploymentProperties().get(CloudFoundryDeploymentProperties.ROUTES_PROPERTY)));\n\t\treturn routes;\n\t}\n\n\tprivate ApplicationHealthCheck toApplicationHealthCheck(String raw) {\n\t\ttry {\n\t\t\treturn ApplicationHealthCheck.from(raw);\n\t\t} catch (IllegalArgumentException e) {\n\t\t\tthrow new IllegalArgumentException(String.format(\"Unsupported health-check value '%s'. Available values are %s\", raw,\n\t\t\t\tStringUtils.arrayToCommaDelimitedString(ApplicationHealthCheck.values())), e);\n\t\t}\n\t}\n\n\tprivate Boolean toggleNoRoute(AppDeploymentRequest request) {\n\t\treturn Optional\n\t\t\t.ofNullable(request.getDeploymentProperties().get(CloudFoundryDeploymentProperties.NO_ROUTE_PROPERTY))\n\t\t\t.map(Boolean::valueOf).orElse(null);\n\t}\n}\n"
  },
  {
    "path": "spring-cloud-deployer-cloudfoundry/src/main/java/org/springframework/cloud/deployer/spi/cloudfoundry/CloudFoundryAppInstanceStatus.java",
    "content": "/*\n * Copyright 2016-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.cloudfoundry;\n\nimport java.util.List;\nimport java.util.Map;\nimport java.util.TreeMap;\n\nimport org.cloudfoundry.operations.applications.ApplicationDetail;\nimport org.cloudfoundry.operations.applications.InstanceDetail;\n\nimport org.springframework.cloud.deployer.spi.app.AppInstanceStatus;\nimport org.springframework.cloud.deployer.spi.app.DeploymentState;\n\n/**\n * Maps status returned by the Cloud Foundry API to {@link AppInstanceStatus}.\n *\n * @author Eric Bottard\n * @author David Turanski\n */\npublic class CloudFoundryAppInstanceStatus implements AppInstanceStatus {\n\n\tprivate final InstanceDetail instanceDetail;\n\n\tprivate final ApplicationDetail applicationDetail;\n\n\tprivate final int index;\n\n\tprivate final Map<String, String> attributes = new TreeMap<>();\n\n\t/**\n\t * The deployer assigned unique id for each app instance.\n\t */\n\tstatic final String GUID = \"guid\";\n\n\t/**\n\t * The platform assigned guid - common among app instances with replicas\n\t */\n\tstatic final String CF_GUID = \"cf-guid\";\n\n\t/**\n\t * The app index.\n\t */\n\tstatic final String INDEX = \"index\";\n\n\tpublic CloudFoundryAppInstanceStatus(ApplicationDetail applicationDetail, InstanceDetail instanceDetail, int index) {\n\t\tthis.applicationDetail = applicationDetail;\n\t\tthis.instanceDetail = instanceDetail;\n\t\tthis.index = index;\n\t}\n\n\t@Override\n\tpublic String getId() {\n\t\treturn applicationDetail.getName() + \"-\" + index;\n\t}\n\n\t@Override\n\tpublic DeploymentState getState() {\n\t\tif (instanceDetail == null) {\n\t\t\treturn DeploymentState.failed;\n\t\t}\n\t\tswitch (instanceDetail.getState()) {\n\t\t\tcase \"STARTING\":\n\t\t\tcase \"DOWN\":\n\t\t\t\treturn DeploymentState.deploying;\n\t\t\tcase \"CRASHED\":\n\t\t\t\treturn DeploymentState.failed;\n\t\t\t// Seems the client incorrectly reports apps as FLAPPING when they are\n\t\t\t// obviously fine. Mapping as RUNNING for now\n\t\t\tcase \"FLAPPING\":\n\t\t\tcase \"RUNNING\":\n\t\t\t\treturn DeploymentState.deployed;\n\t\t\tcase \"UNKNOWN\":\n\t\t\t\treturn DeploymentState.unknown;\n\t\t\tdefault:\n\t\t\t\tthrow new IllegalStateException(\"Unsupported CF state: \" + instanceDetail.getState());\n\t\t}\n\t}\n\n\t@Override\n\tpublic Map<String, String> getAttributes() {\n\t\tif (instanceDetail != null) {\n\t\t\tif (instanceDetail.getCpu() != null) {\n\t\t\t\tattributes.put(\"metrics.machine.cpu\", String.format(\"%.1f%%\", instanceDetail.getCpu() * 100d));\n\t\t\t}\n\t\t\tif (instanceDetail.getDiskQuota() != null && instanceDetail.getDiskUsage() != null) {\n\t\t\t\tattributes.put(\"metrics.machine.disk\", String.format(\"%.1f%%\", 100d * instanceDetail.getDiskUsage() / instanceDetail.getDiskQuota()));\n\t\t\t}\n\t\t\tif (instanceDetail.getMemoryQuota() != null && instanceDetail.getMemoryUsage() != null) {\n\t\t\t\tattributes.put(\"metrics.machine.memory\", String.format(\"%.1f%%\", 100d * instanceDetail.getMemoryUsage() / instanceDetail.getMemoryQuota()));\n\t\t\t}\n\t\t}\n\t\tList<String> urls = applicationDetail.getUrls();\n\t\tif (!urls.isEmpty()) {\n\t\t\tattributes.put(\"url\", \"http://\" + urls.get(0));\n\t\t\tfor (int i = 0; i < urls.size() ; i++) {\n\t\t\t\tattributes.put(\"url.\" + i, \"http://\" + urls.get(i));\n\t\t\t}\n\t\t}\n\t\t// TODO cf-java-client versions > 2.8 will have an index formally added ot InstanceDetail\n\t\t/*\n\t\t The deployer GUID must be unique for each app instance, the CloudFoundry GUID is common to all instances of\n\t\t the same app.\n\t\t */\n\t\tattributes.put(GUID, applicationDetail.getName() + \":\" + index);\n\t\tattributes.put(CF_GUID, applicationDetail.getId());\n\t\tattributes.put(INDEX, String.valueOf(index));\n\t\treturn attributes;\n\t}\n\n\t@Override\n\tpublic String toString() {\n\t\treturn String.format(\"%s[%s : %s]\" , getClass().getSimpleName(), getId(), getState());\n\t}\n}\n"
  },
  {
    "path": "spring-cloud-deployer-cloudfoundry/src/main/java/org/springframework/cloud/deployer/spi/cloudfoundry/CloudFoundryAppNameGenerator.java",
    "content": "/*\n * Copyright 2016 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.cloudfoundry;\n\nimport java.util.Random;\n\nimport org.apache.commons.logging.Log;\nimport org.apache.commons.logging.LogFactory;\n\nimport org.springframework.beans.factory.InitializingBean;\nimport org.springframework.util.StringUtils;\n\n/**\n * CloudFoundry specific implementation of {@link AppNameGenerator}.  This takes into account the\n * configuration properties {@code enableRandomAppNamePrefix} and {@code appNamePrefix}.\n *\n * If {@code enableRandomAppNamePrefix} a random short string is prefixed to the base name.\n * It is true by default. If {@code appNamePrefix} is set, it is also prefixed before the random string.\n * By default the {@code appNamePrefix} is the value of {@code spring.application.name} if available,\n * otherwise the empty string.\n *\n * As an example, if {@code enableRandomAppNamePrefix=true}, {@code spring.application.name=server},\n * and we are deploying an application whose base name is {@code time}, then the deployed name on Cloud Foundry\n * may be {@code server-ug54dh5-time}\n *\n * @author Soby Chacko\n * @author Mark Pollack\n */\npublic class CloudFoundryAppNameGenerator implements AppNameGenerator, InitializingBean {\n\n\tprivate static final Log logger = LogFactory.getLog(CloudFoundryAppNameGenerator.class);\n\n\t/* Given that appnames are by default part of a hostname, limit to 63 chars max. */\n\tprivate static final int MAX_APPNAME_LENGTH = 63;\n\n\tprivate String prefixToUse = \"\";\n\n\tprivate final CloudFoundryDeploymentProperties properties;\n\n\tpublic CloudFoundryAppNameGenerator(CloudFoundryDeploymentProperties cloudFoundryDeploymentProperties) {\n\t\tthis.properties = cloudFoundryDeploymentProperties;\n\t}\n\n\t@Override\n\tpublic void afterPropertiesSet() throws Exception {\n\t\tif (properties.isEnableRandomAppNamePrefix()) {\n\t\t\tprefixToUse = createUniquePrefix();\n\t\t\tif (!StringUtils.isEmpty(properties.getAppNamePrefix())) {\n\t\t\t\tprefixToUse = String.format(\"%s-%s\", properties.getAppNamePrefix(), prefixToUse);\n\t\t\t}\n\t\t} else {\n\t\t\tif (!StringUtils.isEmpty(properties.getAppNamePrefix())) {\n\t\t\t\tprefixToUse = properties.getAppNamePrefix();\n\t\t\t}\n\t\t}\n\t\tlogger.info(String.format(\"Prefix to be used for deploying apps: %s\", prefixToUse));\n\t}\n\n\n\t@Override\n\tpublic String generateAppName(String appName) {\n\t\tif (StringUtils.isEmpty(prefixToUse)) {\n\t\t\treturn appName.substring(0, Math.min(MAX_APPNAME_LENGTH, appName.length()));\n\t\t} else {\n\t\t\tString string = String.format(\"%s-%s\", prefixToUse, appName);\n\t\t\treturn string.substring(0, Math.min(MAX_APPNAME_LENGTH, string.length()));\n\t\t}\n\t}\n\n\tprivate String createUniquePrefix() {\n\t\tString alphabet = \"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ\";\n\t\tchar[] result = new char[7];\n\t\tRandom random = new Random();\n\t\tfor (int i = 0 ; i < result.length ; i++) {\n\t\t\tresult[i] = alphabet.charAt(random.nextInt(alphabet.length()));\n\t\t}\n\t\treturn new String(result);\n\t}\n\n\n}\n"
  },
  {
    "path": "spring-cloud-deployer-cloudfoundry/src/main/java/org/springframework/cloud/deployer/spi/cloudfoundry/CloudFoundryConnectionProperties.java",
    "content": "/*\n * Copyright 2016-2020 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.cloudfoundry;\n\nimport java.net.URL;\n\nimport jakarta.validation.constraints.NotNull;\n\nimport org.springframework.validation.annotation.Validated;\n\n/**\n * Holds configuration properties for connecting to a Cloud Foundry runtime.\n *\n * @author Eric Bottard\n * @author Greg Turnquist\n */\n@Validated\npublic class CloudFoundryConnectionProperties {\n\n\t/**\n\t * Top level prefix for Cloud Foundry related configuration properties.\n\t */\n\tpublic static final String CLOUDFOUNDRY_PROPERTIES = \"spring.cloud.deployer.cloudfoundry\";\n\n\t/**\n\t * The organization to use when registering new applications.\n\t */\n\t@NotNull\n\tprivate String org;\n\n\t/**\n\t * The space to use when registering new applications.\n\t */\n\t@NotNull\n\tprivate String space;\n\n\t/**\n\t * Location of the CloudFoundry REST API endpoint to use.\n\t */\n\t@NotNull\n\tprivate URL url;\n\n\t/**\n\t * Username to use to authenticate against the Cloud Foundry API.\n\t */\n\t@NotNull\n\tprivate String username;\n\n\t/**\n\t * Password to use to authenticate against the Cloud Foundry API.\n\t */\n\t@NotNull\n\tprivate String password;\n\n\t/**\n\t * ClientId to use with token providers, effectively defaults to \"cf\" in\n\t * cloudfroundry client.\n\t */\n\tprivate String clientId;\n\n\t/**\n\t * ClientSecret to use with token providers, effectively defaults to empty in\n\t * cloudfroundry client.\n\t */\n\tprivate String clientSecret;\n\n\t/**\n\t * Indicates the identity provider to be used when accessing the Cloud Foundry API.\n\t * The passed string has to be a URL-Encoded JSON Object, containing the field origin with value as origin_key of an identity provider.\n\t */\n\tprivate String loginHint;\n\n\t/**\n\t * Allow operation using self-signed certificates.\n\t */\n\tprivate boolean skipSslValidation = false;\n\n\tpublic String getOrg() {\n\t\treturn org;\n\t}\n\n\tpublic void setOrg(String org) {\n\t\tthis.org = org;\n\t}\n\n\tpublic String getSpace() {\n\t\treturn space;\n\t}\n\n\tpublic void setSpace(String space) {\n\t\tthis.space = space;\n\t}\n\n\tpublic URL getUrl() {\n\t\treturn url;\n\t}\n\n\tpublic void setUrl(URL url) {\n\t\tthis.url = url;\n\t}\n\n\tpublic String getClientId() {\n\t\treturn clientId;\n\t}\n\n\tpublic void setClientId(String clientId) {\n\t\tthis.clientId = clientId;\n\t}\n\n\tpublic String getClientSecret() {\n\t\treturn clientSecret;\n\t}\n\n\tpublic void setClientSecret(String clientSecret) {\n\t\tthis.clientSecret = clientSecret;\n\t}\n\n\tpublic String getUsername() {\n\t\treturn username;\n\t}\n\n\tpublic void setUsername(String username) {\n\t\tthis.username = username;\n\t}\n\n\tpublic String getPassword() {\n\t\treturn password;\n\t}\n\n\tpublic void setPassword(String password) {\n\t\tthis.password = password;\n\t}\n\n\tpublic boolean isSkipSslValidation() {\n\t\treturn skipSslValidation;\n\t}\n\n\tpublic void setSkipSslValidation(boolean skipSslValidation) {\n\t\tthis.skipSslValidation = skipSslValidation;\n\t}\n\n\tpublic String getLoginHint() {\n\t\treturn loginHint;\n\t}\n\n\tpublic void setLoginHint(String loginHint) {\n\t\tthis.loginHint = loginHint;\n\t}\n}\n"
  },
  {
    "path": "spring-cloud-deployer-cloudfoundry/src/main/java/org/springframework/cloud/deployer/spi/cloudfoundry/CloudFoundryDeployerAutoConfiguration.java",
    "content": "/*\n * Copyright 2016-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.cloudfoundry;\n\nimport java.time.Duration;\n\nimport com.github.zafarkhaja.semver.Version;\nimport org.cloudfoundry.client.CloudFoundryClient;\nimport org.cloudfoundry.client.v2.info.GetInfoRequest;\nimport org.cloudfoundry.logcache.v1.LogCacheClient;\nimport org.cloudfoundry.operations.CloudFoundryOperations;\nimport org.cloudfoundry.operations.DefaultCloudFoundryOperations;\nimport org.cloudfoundry.reactor.ConnectionContext;\nimport org.cloudfoundry.reactor.DefaultConnectionContext;\nimport org.cloudfoundry.reactor.TokenProvider;\nimport org.cloudfoundry.reactor.client.ReactorCloudFoundryClient;\nimport org.cloudfoundry.reactor.logcache.v1.ReactorLogCacheClient;\nimport org.cloudfoundry.reactor.tokenprovider.PasswordGrantTokenProvider;\nimport org.cloudfoundry.reactor.tokenprovider.PasswordGrantTokenProvider.Builder;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.boot.autoconfigure.AutoConfigureOrder;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;\nimport org.springframework.boot.context.properties.ConfigurationProperties;\nimport org.springframework.boot.context.properties.ConfigurationPropertiesBinding;\nimport org.springframework.boot.context.properties.EnableConfigurationProperties;\nimport org.springframework.cloud.deployer.spi.app.AppDeployer;\nimport org.springframework.cloud.deployer.spi.core.RuntimeEnvironmentInfo;\nimport org.springframework.cloud.deployer.spi.task.TaskLauncher;\nimport org.springframework.cloud.deployer.spi.util.RuntimeVersionUtils;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.core.Ordered;\nimport org.springframework.util.StringUtils;\nimport org.springframework.web.client.RestTemplate;\n\n\n/**\n * Creates a {@link CloudFoundryAppDeployer}\n *\n * @author Eric Bottard\n * @author Ben Hale\n * @author David Turanski\n */\n@Configuration\n@EnableConfigurationProperties\n@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)\npublic class CloudFoundryDeployerAutoConfiguration {\n\n\tprivate static final Logger logger = LoggerFactory.getLogger(CloudFoundryDeployerAutoConfiguration.class);\n\n\t@Autowired\n\tprivate EarlyConnectionConfiguration connectionConfiguration;\n\n\t@Bean\n\t@ConditionalOnMissingBean\n\tpublic CloudFoundryOperations cloudFoundryOperations(CloudFoundryClient cloudFoundryClient, CloudFoundryConnectionProperties properties) {\n\t\treturn DefaultCloudFoundryOperations.builder()\n\t\t\t.cloudFoundryClient(cloudFoundryClient)\n\t\t\t.organization(properties.getOrg())\n\t\t\t.space(properties.getSpace())\n\t\t\t.build();\n\t}\n\n\tprivate RuntimeEnvironmentInfo runtimeEnvironmentInfo(Class spiClass, Class implementationClass, CloudFoundryConnectionProperties cloudFoundryConnectionProperties) {\n\t\tCloudFoundryClient client = connectionConfiguration.cloudFoundryClient(\n\t\t\tconnectionConfiguration.connectionContext(cloudFoundryConnectionProperties),\n\t\t\tconnectionConfiguration.tokenProvider(cloudFoundryConnectionProperties));\n\t\tVersion version = connectionConfiguration.version(client);\n\n\t\treturn new CloudFoundryPlatformSpecificInfo(new RuntimeEnvironmentInfo.Builder())\n\t\t\t.apiEndpoint(cloudFoundryConnectionProperties.getUrl().toString())\n\t\t\t.org(cloudFoundryConnectionProperties.getOrg())\n\t\t\t.space(cloudFoundryConnectionProperties.getSpace())\n\t\t\t.builder()\n\t\t\t\t.implementationName(implementationClass.getSimpleName())\n\t\t\t\t.spiClass(spiClass)\n\t\t\t\t.implementationVersion(RuntimeVersionUtils.getVersion(CloudFoundryAppDeployer.class))\n\t\t\t\t.platformType(\"Cloud Foundry\")\n\t\t\t\t.platformClientVersion(RuntimeVersionUtils.getVersion(client.getClass()))\n\t\t\t\t.platformApiVersion(version.toString())\n\t\t\t\t.platformHostVersion(\"unknown\")\n\t\t\t\t.build();\n\t}\n\n\t@Bean\n\t@ConditionalOnMissingBean(AppDeployer.class)\n\tpublic AppDeployer appDeployer(\n\t\tCloudFoundryOperations operations,\n\t\tAppNameGenerator applicationNameGenerator,\n\t\tApplicationLogAccessor applicationLogAccessor\n\t) {\n\t\treturn new CloudFoundryAppDeployer(\n\t\t\tapplicationNameGenerator,\n\t\t\tconnectionConfiguration.appDeploymentProperties(),\n\t\t\toperations,\n\t\t\truntimeEnvironmentInfo(AppDeployer.class, CloudFoundryAppDeployer.class, connectionConfiguration.cloudFoundryConnectionProperties()),\n\t\t\t\tapplicationLogAccessor);\n\t}\n\n\t@Bean\n\tCloudFoundryActuatorTemplate actuatorOperations(RestTemplate actuatorRestTemplate, AppDeployer appDeployer) {\n\t\treturn new CloudFoundryActuatorTemplate(actuatorRestTemplate, appDeployer,\n\t\t\t\tconnectionConfiguration.appDeploymentProperties().getAppAdmin());\n\t}\n\n\t@Bean\n\tRestTemplate actuatorRestTemplate() {\n\t\treturn new RestTemplate();\n\t}\n\n\t@Bean\n\t@ConditionalOnMissingBean(AppNameGenerator.class)\n\tpublic AppNameGenerator appDeploymentCustomizer() {\n\t\treturn new CloudFoundryAppNameGenerator(connectionConfiguration.appDeploymentProperties());\n\t}\n\n\t@Bean\n\t@ConditionalOnMissingBean(TaskLauncher.class)\n\tpublic TaskLauncher taskLauncher(\n\t\tCloudFoundryClient client,\n\t\tCloudFoundryOperations operations,\n\t\tVersion version,\n\t\tApplicationLogAccessor applicationLogAccessor\n\t) {\n\n\t\tif (version.greaterThanOrEqualTo(UnsupportedVersionTaskLauncher.MINIMUM_SUPPORTED_VERSION)) {\n\t\t\tRuntimeEnvironmentInfo runtimeEnvironmentInfo = runtimeEnvironmentInfo(TaskLauncher.class, CloudFoundryTaskLauncher.class,\n\t\t\t\tconnectionConfiguration.cloudFoundryConnectionProperties());\n\t\t\treturn new CloudFoundryTaskLauncher(\n\t\t\t\tclient,\n\t\t\t\tconnectionConfiguration.taskDeploymentProperties(),\n\t\t\t\toperations,\n\t\t\t\truntimeEnvironmentInfo,\n\t\t\t\t\tapplicationLogAccessor);\n\t\t} else {\n\t\t\tRuntimeEnvironmentInfo runtimeEnvironmentInfo = runtimeEnvironmentInfo(TaskLauncher.class, UnsupportedVersionTaskLauncher.class,\n\t\t\t\tconnectionConfiguration.cloudFoundryConnectionProperties());\n\t\t\treturn new UnsupportedVersionTaskLauncher(version, runtimeEnvironmentInfo);\n\t\t}\n\t}\n\n\t@Bean\n\t@ConditionalOnMissingBean(ApplicationLogAccessor.class)\n\tpublic ApplicationLogAccessor logHelper(LogCacheClient logCacheClient) {\n\t\treturn new ApplicationLogAccessor(logCacheClient);\n\t}\n\n\t@Bean\n\t@ConfigurationPropertiesBinding\n\tpublic DurationConverter durationConverter() {\n\t\treturn new DurationConverter();\n\t}\n\n\t/**\n\t * A subset of configuration beans that can be used on its own to connect to the Cloud Controller API\n\t * and query it for its version. Automatically applied in CloudFoundryDeployerAutoConfiguration by virtue\n\t * of being a static inner class of it.\n\t *\n\t * @author Eric Bottard\n\t */\n\t@Configuration\n\t@EnableConfigurationProperties\n\tpublic static class EarlyConnectionConfiguration {\n\n\t\t@Bean\n\t\t@ConditionalOnMissingBean(name = \"appDeploymentProperties\")\n\t\tpublic CloudFoundryDeploymentProperties appDeploymentProperties() {\n\t\t\treturn defaultSharedDeploymentProperties();\n\t\t}\n\n\n\t\t@Bean\n\t\t@ConditionalOnMissingBean(name = \"taskDeploymentProperties\")\n\t\tpublic CloudFoundryDeploymentProperties taskDeploymentProperties() {\n\t\t\treturn defaultSharedDeploymentProperties();\n\t\t}\n\n\t\t@Bean\n\t\t@ConfigurationProperties(prefix = CloudFoundryConnectionProperties.CLOUDFOUNDRY_PROPERTIES)\n\t\tpublic CloudFoundryDeploymentProperties defaultSharedDeploymentProperties() {\n\t\t\treturn new CloudFoundryDeploymentProperties();\n\t\t}\n\n\t\t@Bean\n\t\t@ConditionalOnMissingBean\n\t\tpublic Version version(CloudFoundryClient client) {\n\t\t\treturn client.info()\n\t\t\t\t.get(GetInfoRequest.builder()\n\t\t\t\t\t.build())\n\t\t\t\t.map(response -> Version.valueOf(response.getApiVersion()))\n\t\t\t\t.doOnError(e -> {\n\t\t\t\t\tthrow new RuntimeException(\"Bad credentials connecting to Cloud Foundry.\", e);\n\t\t\t\t})\n\t\t\t\t.doOnNext(version -> logger.info(\"Connecting to Cloud Foundry with API Version {}\", version))\n\t\t\t\t.block(Duration.ofSeconds(appDeploymentProperties().getApiTimeout()));\n\t\t}\n\n\t\t@Bean\n\t\t@ConditionalOnMissingBean\n\t\tpublic CloudFoundryClient cloudFoundryClient(ConnectionContext connectionContext, TokenProvider tokenProvider) {\n\t\t\treturn ReactorCloudFoundryClient.builder()\n\t\t\t\t.connectionContext(connectionContext)\n\t\t\t\t.tokenProvider(tokenProvider)\n\t\t\t\t.build();\n\t\t}\n\n\t\t@Bean\n\t\t@ConditionalOnMissingBean\n\t\tpublic LogCacheClient logCacheClient(ConnectionContext connectionContext, TokenProvider tokenProvider) {\n\t\t\treturn ReactorLogCacheClient.builder()\n\t\t\t\t.connectionContext(connectionContext)\n\t\t\t\t.tokenProvider(tokenProvider)\n\t\t\t\t.build();\n\t\t}\n\n\t\t@Bean\n\t\t@ConditionalOnMissingBean\n\t\tpublic TokenProvider tokenProvider(CloudFoundryConnectionProperties properties) {\n\t\t\tBuilder tokenProviderBuilder = PasswordGrantTokenProvider.builder()\n\t\t\t\t\t.username(properties.getUsername())\n\t\t\t\t\t.password(properties.getPassword())\n\t\t\t\t\t.loginHint(properties.getLoginHint());\n\t\t\tif (StringUtils.hasText(properties.getClientId())) {\n\t\t\t\ttokenProviderBuilder.clientId(properties.getClientId());\n\t\t\t}\n\t\t\tif (StringUtils.hasText(properties.getClientSecret())) {\n\t\t\t\ttokenProviderBuilder.clientSecret(properties.getClientSecret());\n\t\t\t}\n\t\t\treturn tokenProviderBuilder.build();\n\t\t}\n\n\t\t@Bean\n\t\t@ConditionalOnMissingBean\n\t\t@ConfigurationProperties(prefix = CloudFoundryConnectionProperties.CLOUDFOUNDRY_PROPERTIES)\n\t\tpublic CloudFoundryConnectionProperties cloudFoundryConnectionProperties() {\n\t\t\treturn new CloudFoundryConnectionProperties();\n\t\t}\n\n\t\t@Bean\n\t\t@ConditionalOnMissingBean\n\t\tpublic ConnectionContext connectionContext(CloudFoundryConnectionProperties properties) {\n\t\t\treturn DefaultConnectionContext.builder()\n\t\t\t\t.apiHost(properties.getUrl().getHost())\n\t\t\t\t.skipSslValidation(properties.isSkipSslValidation())\n\t\t\t\t.build();\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "spring-cloud-deployer-cloudfoundry/src/main/java/org/springframework/cloud/deployer/spi/cloudfoundry/CloudFoundryDeploymentProperties.java",
    "content": "/*\n * Copyright 2016-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.cloudfoundry;\n\nimport java.time.Duration;\nimport java.util.Collections;\nimport java.util.HashSet;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.Set;\n\nimport jakarta.validation.constraints.Min;\nimport jakarta.validation.constraints.NotNull;\n\nimport org.apache.commons.logging.Log;\nimport org.apache.commons.logging.LogFactory;\nimport org.cloudfoundry.operations.applications.ApplicationHealthCheck;\n\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.cloud.deployer.spi.app.AppAdmin;\nimport org.springframework.validation.annotation.Validated;\n\n\n/**\n * Holds configuration properties for specifying what resources and services an app\n * deployed to a Cloud Foundry runtime will get.\n *\n * @author Eric Bottard\n * @author Greg Turnquist\n * @author Ilayaperumal Gopinathan\n * @author David Turanski\n */\n@Validated\npublic class CloudFoundryDeploymentProperties {\n\n\tprivate static final Log log = LogFactory.getLog(CloudFoundryDeploymentProperties.class);\n\n\tpublic static final String SERVICES_PROPERTY_KEY = CloudFoundryConnectionProperties.CLOUDFOUNDRY_PROPERTIES + \".services\";\n\n\tpublic static final String HEALTHCHECK_PROPERTY_KEY = CloudFoundryConnectionProperties.CLOUDFOUNDRY_PROPERTIES + \".health-check\";\n\n\tpublic static final String HEALTHCHECK_HTTP_ENDPOINT_PROPERTY_KEY = CloudFoundryConnectionProperties.CLOUDFOUNDRY_PROPERTIES\n\t\t\t+ \".health-check-http-endpoint\";\n\n\tpublic static final String HEALTHCHECK_TIMEOUT_PROPERTY_KEY = CloudFoundryConnectionProperties.CLOUDFOUNDRY_PROPERTIES + \".health-check-timeout\";\n\n\tpublic static final String ROUTE_PATH_PROPERTY = CloudFoundryConnectionProperties.CLOUDFOUNDRY_PROPERTIES + \".route-path\";\n\n\tpublic static final String ROUTE_PROPERTY = CloudFoundryConnectionProperties.CLOUDFOUNDRY_PROPERTIES + \".route\";\n\n\tpublic static final String ROUTES_PROPERTY = CloudFoundryConnectionProperties.CLOUDFOUNDRY_PROPERTIES + \".routes\";\n\n\tpublic static final String NO_ROUTE_PROPERTY = CloudFoundryConnectionProperties.CLOUDFOUNDRY_PROPERTIES + \".no-route\";\n\n\tpublic static final String HOST_PROPERTY = CloudFoundryConnectionProperties.CLOUDFOUNDRY_PROPERTIES + \".host\";\n\n\tpublic static final String DOMAIN_PROPERTY = CloudFoundryConnectionProperties.CLOUDFOUNDRY_PROPERTIES + \".domain\";\n\n\tpublic static final String BUILDPACK_PROPERTY_KEY = CloudFoundryConnectionProperties.CLOUDFOUNDRY_PROPERTIES + \".buildpack\";\n\n\tpublic static final String BUILDPACKS_PROPERTY_KEY = CloudFoundryConnectionProperties.CLOUDFOUNDRY_PROPERTIES + \".buildpacks\";\n\n\tpublic static final String JAVA_OPTS_PROPERTY_KEY = CloudFoundryConnectionProperties.CLOUDFOUNDRY_PROPERTIES + \".javaOpts\";\n\n\tpublic static final String USE_SPRING_APPLICATION_JSON_KEY = CloudFoundryConnectionProperties.CLOUDFOUNDRY_PROPERTIES\n\t\t\t+ \".use-spring-application-json\";\n\n\tpublic static final String ENV_KEY = CloudFoundryConnectionProperties.CLOUDFOUNDRY_PROPERTIES + \".env\";\n\n\tprivate static final String DEFAULT_BUILDPACK = \"https://github.com/cloudfoundry/java-buildpack.git#v4.29.1\";\n\n\t/**\n\t * The names of services to bind to all applications deployed as a module. This should\n\t * typically contain a service capable of playing the role of a binding transport.\n\t */\n\tprivate Set<String> services = new HashSet<>();\n\n\t/**\n\t * The host name to use as part of the route. Defaults to hostname derived by Cloud\n\t * Foundry.\n\t */\n\tprivate String host = null;\n\n\t/**\n\t * The domain to use when mapping routes for applications.\n\t */\n\tprivate String domain;\n\n\t/**\n\t * The routes that the application should be bound to. Mutually exclusive with host and\n\t * domain.\n\t */\n\tprivate Set<String> routes = new HashSet<>();\n\n\t/**\n\t * The buildpack to use for deploying the application.\n\t */\n\t@Deprecated\n\tprivate String buildpack = DEFAULT_BUILDPACK;\n\n\t/**\n\t * The buildpacks to use for deploying the application.\n\t */\n\tprivate Set<String> buildpacks = new HashSet<>();\n\n\t/**\n\t * The amount of memory to allocate, if not overridden per-app. Default unit is mebibytes,\n\t * 'M' and 'G\" suffixes supported.\n\t */\n\tprivate String memory = \"1024m\";\n\n\t/**\n\t * The amount of disk space to allocate, if not overridden per-app. Default unit is\n\t * mebibytes, 'M' and 'G\" suffixes supported.\n\t */\n\tprivate String disk = \"1024m\";\n\n\t/**\n\t * The type of health check to perform on deployed application, if not overridden per-app.\n\t * Defaults to PORT\n\t */\n\tprivate ApplicationHealthCheck healthCheck = ApplicationHealthCheck.PORT;\n\n\t/**\n\t * The path that the http health check will use, defaults to @{code /health}\n\t */\n\tprivate String healthCheckHttpEndpoint = \"/health\";\n\n\t/**\n\t * The timeout value for health checks in seconds. Defaults to 120 seconds.\n\t */\n\tprivate String healthCheckTimeout = \"120\";\n\n\t/**\n\t * The number of instances to run.\n\t */\n\tprivate int instances = 1;\n\n\t/**\n\t * Flag to enable prefixing the app name with a random prefix.\n\t */\n\tprivate boolean enableRandomAppNamePrefix = true;\n\n\t/**\n\t * Timeout for blocking API calls, in seconds.\n\t */\n\tprivate long apiTimeout = 360L;\n\n\t/**\n\t * Timeout for status API operations in milliseconds\n\t */\n\tprivate long statusTimeout = 30_000L;\n\n\t/**\n\t * Flag to indicate whether application properties are fed into SPRING_APPLICATION_JSON or\n\t * ENVIRONMENT VARIABLES.\n\t */\n\tprivate boolean useSpringApplicationJson = true;\n\n\t/**\n\t * If set, override the timeout allocated for staging the app by the client.\n\t */\n\tprivate Duration stagingTimeout = Duration.ofMinutes(15L);\n\n\t/**\n\t * If set, override the timeout allocated for starting the app by the client.\n\t */\n\tprivate Duration startupTimeout = Duration.ofMinutes(5L);\n\n\t/**\n\t * String to use as prefix for name of deployed app. Defaults to spring.application.name.\n\t */\n\t@Value(\"${spring.application.name:}\")\n\tprivate String appNamePrefix;\n\n\t/**\n\t * Whether to also delete routes when un-deploying an application.\n\t */\n\tprivate boolean deleteRoutes = true;\n\n\t/**\n\t * Whether to push task apps\n\t */\n\tprivate boolean pushTaskAppsEnabled = true;\n\n\t/**\n\t * Whether to automatically delete cached Maven artifacts after deployment.\n\t */\n\tprivate boolean autoDeleteMavenArtifacts = true;\n\n\t/**\n\t * The maximum concurrent tasks allowed.\n\t */\n\t@Min(1)\n\tprivate int maximumConcurrentTasks = 20;\n\n\tprivate String javaOpts;\n\n\tprivate Optional<Map<String, String>> env = Optional.empty();\n\t\n\t/**\n\t * Location of the PCF scheduler REST API enpoint ot use.\n\t */\n\tprivate String schedulerUrl;\n\n\t/**\n\t * The number of retries allowed when scheduling a task if an {@link javax.net.ssl.SSLException} is thrown.\n\t */\n\tprivate int scheduleSSLRetryCount = 5;\n\n\t/**\n\t * The number of seconds to wait for a unSchedule to complete.\n\t */\n\tprivate int unScheduleTimeoutInSeconds = 30;\n\n\t/**\n\t * The number of seconds to wait for a schedule to complete.\n\t * This excludes the time it takes to stage the application on Cloud Foundry.\n\t */\n\tprivate int scheduleTimeoutInSeconds = 30;\n\n\t/**\n\t * The number of seconds to wait for a list of schedules to be returned.\n\t */\n\tprivate int listTimeoutInSeconds = 60;\n\n\tprivate AppAdmin appAdmin = new AppAdmin();\n\n\tpublic Set<String> getServices() {\n\t\treturn services;\n\t}\n\n\tpublic void setServices(Set<String> services) {\n\t\tthis.services = services;\n\t}\n\n\t@Deprecated\n\tpublic String getBuildpack() {\n\t\treturn buildpack;\n\t}\n\n\t@Deprecated\n\tpublic void setBuildpack(String buildpack) {\n\t\tthis.buildpack = buildpack;\n\t}\n\n\tpublic Set<String> getBuildpacks() {\n\t\treturn buildpacks;\n\t}\n\n\tpublic void setBuildpacks(Set<String> buildpacks) {\n\t\tthis.buildpacks = buildpacks;\n\t}\n\n\tpublic String getMemory() {\n\t\treturn memory;\n\t}\n\n\tpublic void setMemory(String memory) {\n\t\tthis.memory = memory;\n\t}\n\n\tpublic String getDisk() {\n\t\treturn disk;\n\t}\n\n\tpublic void setDisk(String disk) {\n\t\tthis.disk = disk;\n\t}\n\n\tpublic int getInstances() {\n\t\treturn instances;\n\t}\n\n\tpublic void setInstances(int instances) {\n\t\tthis.instances = instances;\n\t}\n\n\tpublic boolean isEnableRandomAppNamePrefix() {\n\t\treturn enableRandomAppNamePrefix;\n\t}\n\n\tpublic void setEnableRandomAppNamePrefix(boolean enableRandomAppNamePrefix) {\n\t\tthis.enableRandomAppNamePrefix = enableRandomAppNamePrefix;\n\t}\n\n\tpublic String getAppNamePrefix() {\n\t\treturn appNamePrefix;\n\t}\n\n\tpublic void setAppNamePrefix(String appNamePrefix) {\n\t\tthis.appNamePrefix = appNamePrefix;\n\t}\n\n\tpublic long getApiTimeout() {\n\t\treturn apiTimeout;\n\t}\n\n\tpublic void setApiTimeout(long apiTimeout) {\n\t\tthis.apiTimeout = apiTimeout;\n\t}\n\n\tpublic boolean isUseSpringApplicationJson() {\n\t\treturn useSpringApplicationJson;\n\t}\n\n\tpublic void setUseSpringApplicationJson(boolean useSpringApplicationJson) {\n\t\tthis.useSpringApplicationJson = useSpringApplicationJson;\n\t}\n\n\tpublic ApplicationHealthCheck getHealthCheck() {\n\t\treturn healthCheck;\n\t}\n\n\tpublic void setHealthCheck(ApplicationHealthCheck healthCheck) {\n\t\tthis.healthCheck = healthCheck;\n\t}\n\n\tpublic String getHealthCheckHttpEndpoint() {\n\t\treturn healthCheckHttpEndpoint;\n\t}\n\n\tpublic void setHealthCheckHttpEndpoint(String healthCheckHttpEndpoint) {\n\t\tthis.healthCheckHttpEndpoint = healthCheckHttpEndpoint;\n\t}\n\n\tpublic String getHealthCheckTimeout() {\n\t\treturn healthCheckTimeout;\n\t}\n\n\tpublic void setHealthCheckTimeout(String healthCheckTimeout) {\n\t\tthis.healthCheckTimeout = healthCheckTimeout;\n\t}\n\n\tpublic String getDomain() {\n\t\treturn domain;\n\t}\n\n\tpublic void setDomain(String domain) {\n\t\tthis.domain = domain;\n\t}\n\n\tpublic String getHost() {\n\t\treturn host;\n\t}\n\n\tpublic void setHost(String host) {\n\t\tthis.host = host;\n\t}\n\n\tpublic Set<String> getRoutes() {\n\t\treturn routes;\n\t}\n\n\tpublic void setRoutes(Set<String> routes) {\n\t\tthis.routes = routes;\n\t}\n\n\tpublic Duration getStagingTimeout() {\n\t\treturn stagingTimeout;\n\t}\n\n\tpublic void setStagingTimeout(Duration stagingTimeout) {\n\t\tthis.stagingTimeout = stagingTimeout;\n\t}\n\n\tpublic Duration getStartupTimeout() {\n\t\treturn startupTimeout;\n\t}\n\n\tpublic void setStartupTimeout(Duration startupTimeout) {\n\t\tthis.startupTimeout = startupTimeout;\n\t}\n\n\tpublic long getStatusTimeout() {\n\t\treturn statusTimeout;\n\t}\n\n\tpublic void setStatusTimeout(long statusTimeout) {\n\t\tthis.statusTimeout = statusTimeout;\n\t}\n\n\tpublic boolean isDeleteRoutes() {\n\t\treturn deleteRoutes;\n\t}\n\n\tpublic void setDeleteRoutes(boolean deleteRoutes) {\n\t\tthis.deleteRoutes = deleteRoutes;\n\t}\n\n\tpublic String getJavaOpts() {\n\t\treturn javaOpts;\n\t}\n\n\tpublic void setJavaOpts(String javaOpts) {\n\t\tthis.javaOpts = javaOpts;\n\t}\n\n\tpublic int getMaximumConcurrentTasks() {\n\t\treturn maximumConcurrentTasks;\n\t}\n\n\tpublic void setMaximumConcurrentTasks(int maximumConcurrentTasks) {\n\t\tthis.maximumConcurrentTasks = maximumConcurrentTasks;\n\t}\n\n\tpublic boolean isPushTaskAppsEnabled() {\n\t\treturn pushTaskAppsEnabled;\n\t}\n\n\tpublic void setPushTaskAppsEnabled(boolean pushTaskAppsEnabled) {\n\t\tthis.pushTaskAppsEnabled = pushTaskAppsEnabled;\n\t}\n\n\tpublic boolean isAutoDeleteMavenArtifacts() {\n\t\treturn autoDeleteMavenArtifacts;\n\t}\n\n\tpublic void setAutoDeleteMavenArtifacts(boolean autoDeleteMavenArtifacts) {\n\t\tthis.autoDeleteMavenArtifacts = autoDeleteMavenArtifacts;\n\t}\n\n\tpublic Map<String, String> getEnv() {\n\t\treturn env.orElseGet(Collections::emptyMap);\n\t}\n\n\tpublic void setEnv(@NotNull Map<String, String> env) {\n\t\tthis.env.map(e -> {\n\t\t\t\t\tlog.error(\"Environment is immutable. New entries have not been applied\");\n\t\t\t\t\treturn this.env;\n\t\t\t\t}\n\t\t).orElse(this.env = Optional.of(Collections.unmodifiableMap(env)));\n\t}\n\n\tpublic String getSchedulerUrl() {\n\t\treturn schedulerUrl;\n\t}\n\n\tpublic void setSchedulerUrl(String schedulerUrl) {\n\t\tthis.schedulerUrl = schedulerUrl;\n\t}\n\n\tpublic int getScheduleSSLRetryCount() {\n\t\treturn scheduleSSLRetryCount;\n\t}\n\n\tpublic void setScheduleSSLRetryCount(int scheduleSSLRetryCount) {\n\t\tthis.scheduleSSLRetryCount = scheduleSSLRetryCount;\n\t}\n\n\tpublic int getUnScheduleTimeoutInSeconds() {\n\t\treturn unScheduleTimeoutInSeconds;\n\t}\n\n\tpublic void setUnScheduleTimeoutInSeconds(int unScheduleTimeoutInSeconds) {\n\t\tthis.unScheduleTimeoutInSeconds = unScheduleTimeoutInSeconds;\n\t}\n\n\tpublic int getScheduleTimeoutInSeconds() {\n\t\treturn scheduleTimeoutInSeconds;\n\t}\n\n\tpublic void setScheduleTimeoutInSeconds(int scheduleTimeoutInSeconds) {\n\t\tthis.scheduleTimeoutInSeconds = scheduleTimeoutInSeconds;\n\t}\n\n\tpublic int getListTimeoutInSeconds() {\n\t\treturn listTimeoutInSeconds;\n\t}\n\n\tpublic void setListTimeoutInSeconds(int listTimeoutInSeconds) {\n\t\tthis.listTimeoutInSeconds = listTimeoutInSeconds;\n\t}\n\n\tpublic AppAdmin getAppAdmin() {\n\t\treturn appAdmin;\n\t}\n\n\tpublic void setAppAdmin(AppAdmin appAdmin) {\n\t\tthis.appAdmin = appAdmin;\n\t}\n}\n"
  },
  {
    "path": "spring-cloud-deployer-cloudfoundry/src/main/java/org/springframework/cloud/deployer/spi/cloudfoundry/CloudFoundryPlatformSpecificInfo.java",
    "content": "/*\n * Copyright 2020-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.cloudfoundry;\n\nimport org.springframework.cloud.deployer.spi.core.RuntimeEnvironmentInfo;\nimport org.springframework.util.Assert;\n\n/**\n * A Provides required platform specific values to {@link RuntimeEnvironmentInfo}.\n *\n * @author David Turanski\n */\npublic class CloudFoundryPlatformSpecificInfo {\n\tstatic final String API_ENDPOINT = \"API Endpoint\";\n\n\tstatic final String ORG = \"Organization\";\n\n\tstatic final String SPACE = \"Space\";\n\n\tprivate final RuntimeEnvironmentInfo.Builder runtimeEnvironmentInfo;\n\n\tprivate String apiEndpoint;\n\n\tprivate String org;\n\n\tprivate String space;\n\n\tpublic CloudFoundryPlatformSpecificInfo(RuntimeEnvironmentInfo.Builder runtimeEnvironmentInfo) {\n\t\tthis.runtimeEnvironmentInfo = runtimeEnvironmentInfo;\n\t}\n\n\tpublic CloudFoundryPlatformSpecificInfo apiEndpoint(String apiEndpoint) {\n\t\tthis.apiEndpoint = apiEndpoint;\n\t\treturn this;\n\t}\n\n\tpublic CloudFoundryPlatformSpecificInfo org(String org) {\n\t\tthis.org = org;\n\t\treturn this;\n\t}\n\n\tpublic CloudFoundryPlatformSpecificInfo space(String space) {\n\t\tthis.space = space;\n\t\treturn this;\n\t}\n\n\tpublic RuntimeEnvironmentInfo.Builder builder() {\n\t\tAssert.hasText(apiEndpoint, \"'apiEndpoint' must contain text\");\n\t\tAssert.hasText(org, \"'org' must contain text\");\n\t\tAssert.hasText(space, \"'space' must contain text\");\n\t\truntimeEnvironmentInfo.addPlatformSpecificInfo(API_ENDPOINT, apiEndpoint);\n\t\truntimeEnvironmentInfo.addPlatformSpecificInfo(ORG, org);\n\t\truntimeEnvironmentInfo.addPlatformSpecificInfo(SPACE, space);\n\t\treturn runtimeEnvironmentInfo;\n\t}\n}\n"
  },
  {
    "path": "spring-cloud-deployer-cloudfoundry/src/main/java/org/springframework/cloud/deployer/spi/cloudfoundry/CloudFoundryTaskLauncher.java",
    "content": "/*\n * Copyright 2016-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.cloudfoundry;\n\nimport java.time.Duration;\nimport java.util.NoSuchElementException;\nimport java.util.stream.Collectors;\nimport java.util.stream.Stream;\n\nimport org.cloudfoundry.client.CloudFoundryClient;\nimport org.cloudfoundry.client.v2.applications.SummaryApplicationResponse;\nimport org.cloudfoundry.client.v3.tasks.CreateTaskRequest;\nimport org.cloudfoundry.client.v3.tasks.CreateTaskResponse;\nimport org.cloudfoundry.operations.CloudFoundryOperations;\nimport org.cloudfoundry.operations.applications.AbstractApplicationSummary;\nimport org.cloudfoundry.operations.applications.ApplicationDetail;\nimport org.cloudfoundry.operations.applications.ApplicationHealthCheck;\nimport org.cloudfoundry.operations.applications.ApplicationManifest;\nimport org.cloudfoundry.operations.applications.ApplicationSummary;\nimport org.cloudfoundry.operations.applications.DeleteApplicationRequest;\nimport org.cloudfoundry.operations.applications.Docker;\nimport org.cloudfoundry.operations.applications.GetApplicationRequest;\nimport org.cloudfoundry.operations.applications.PushApplicationManifestRequest;\nimport org.cloudfoundry.operations.applications.StopApplicationRequest;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.cloud.deployer.spi.task.TaskStatus;\nimport org.springframework.util.Assert;\nimport reactor.core.publisher.Flux;\nimport reactor.core.publisher.Mono;\n\nimport org.springframework.cloud.deployer.spi.core.AppDeploymentRequest;\nimport org.springframework.cloud.deployer.spi.core.RuntimeEnvironmentInfo;\nimport org.springframework.cloud.deployer.spi.task.TaskLauncher;\n\n/**\n * {@link TaskLauncher} implementation for CloudFoundry.  When a task is launched, if it has not previously been\n * deployed, the app is created, the package is uploaded, and the droplet is created before launching the actual\n * task.  If the app has been deployed previously, the app/package/droplet is reused and a new task is created.\n *\n * @author Greg Turnquist\n * @author Michael Minella\n * @author Ben Hale\n * @author Ilayaperumal Gopinathan\n * @author Glenn Renfro\n * @author David Turanski\n * @author David Bernard\n */\npublic class CloudFoundryTaskLauncher extends AbstractCloudFoundryTaskLauncher {\n\n\tprivate static final Logger logger = LoggerFactory.getLogger(CloudFoundryTaskLauncher.class);\n\n\tprivate final CloudFoundryClient client;\n\n\tprivate final CloudFoundryDeploymentProperties deploymentProperties;\n\n\tprivate final CloudFoundryOperations operations;\n\n\tprivate final ApplicationLogAccessor applicationLogAccessor;\n\n\tpublic CloudFoundryTaskLauncher(CloudFoundryClient client,\n\t\t\t\t\t\t\t\t\t\t\t\tCloudFoundryDeploymentProperties deploymentProperties,\n\t\t\t\t\t\t\t\t\t\t\t\tCloudFoundryOperations operations,\n\t\t\t\t\t\t\t\t\t\t\t\tRuntimeEnvironmentInfo runtimeEnvironmentInfo,\n\t\t\t\t\t\t\t\t\t\t\t\tApplicationLogAccessor applicationLogAccessor) {\n\t\tsuper(client, deploymentProperties, runtimeEnvironmentInfo);\n\t\tthis.client = client;\n\t\tthis.deploymentProperties = deploymentProperties;\n\t\tthis.operations = operations;\n\t\tthis.applicationLogAccessor = applicationLogAccessor;\n\t}\n\n\t/**\n\t * Set up a reactor flow to launch a task. Before launch, check if the base application exists. If not, deploy\n\t * then launch task.\n\t *\n\t * @param appDeploymentRequest description of the application to be launched\n\t * @return name of the launched task, returned without waiting for reactor pipeline to complete\n\t */\n\t@Override\n\tpublic String launch(AppDeploymentRequest appDeploymentRequest) {\n\t\tfinal AppDeploymentRequest request = CfEnvAwareAppDeploymentRequest.of(appDeploymentRequest);\n\t\tif (this.maxConcurrentExecutionsReached()) {\n\t\t\tthrow new IllegalStateException(\n\t\t\t\tString.format(\"Cannot launch task %s. The maximum concurrent task executions is at its limit [%d].\",\n\t\t\t\t\trequest.getDefinition().getName(), this.getMaximumConcurrentTasks())\n\t\t\t);\n\t\t}\n\n\t\treturn getOrDeployApplication(request)\n\t\t\t.flatMap(application -> launchTask(application, request))\n\t\t\t.doOnSuccess(r -> {\n\t\t\t\tlogger.info(\"Task {} launch successful\", request.getDefinition().getName());\n\t\t\t})\n\t\t\t.doOnError(logError(String.format(\"Task %s launch failed\", request.getDefinition().getName())))\n\t\t\t.doOnTerminate(() -> {\n\t\t\t\tif (pushTaskAppsEnabled()) {\n\t\t\t\t\tdeleteLocalApplicationResourceFile(request);\n\t\t\t\t}\n\t\t\t})\n\t\t\t.block(Duration.ofSeconds(this.deploymentProperties.getApiTimeout()));\n\t}\n\n\t@Override\n\tpublic void destroy(String appName) {\n\t\tif (!pushTaskAppsEnabled()) {\n\t\t\tlogger.warn(\"The application {} will not be deleted since 'pushTaskApps' is not enabled.\", appName);\n\n\t\t\treturn;\n\t\t}\n\t\trequestDeleteApplication(appName)\n\t\t\t.timeout(Duration.ofSeconds(this.deploymentProperties.getApiTimeout()))\n\t\t\t.doOnSuccess(v -> logger.info(\"Successfully destroyed app {}\", appName))\n\t\t\t.doOnError(logError(String.format(\"Failed to destroy app %s\", appName)))\n\t\t\t.block(Duration.ofSeconds(this.deploymentProperties.getApiTimeout()));\n\t}\n\n\t@Override\n\tpublic String getLog(String id) {\n\t\tTaskStatus taskStatus = this.status(id);\n\t\tString appCfGuid = taskStatus.getAttributes().get(\"app-cf-guid\");\n\t\tlogger.debug(\"Retrieving log for app-guid-id {} for task-guid-id {}\", appCfGuid, id);\n\t\tAssert.hasText(appCfGuid, \"could not find a GUID app id for the task guid id \" + id);\n\t\treturn this.applicationLogAccessor.getLog(appCfGuid,\n\t\t\t\tDuration.ofSeconds(this.deploymentProperties.getApiTimeout()));\n\t}\n\n\t/**\n\t * Set up a reactor flow to stage a task. Before staging check if the base\n\t * application exists. If not, then stage it.\n\t *\n\t * @param request description of the application to be staged.\n\t * @return SummaryApplicationResponse containing the status of the staging.\n\t */\n\tpublic SummaryApplicationResponse stage(AppDeploymentRequest request) {\n\t\treturn getOrDeployApplication(request).doOnSuccess(r ->\n\t\t\t\t\tlogger.info(\"Task {} staged successfully\", request.getDefinition().getName()))\n\t\t\t\t.doOnError(logError(String.format(\"Task %s stage failed\", request.getDefinition().getName())))\n\t\t\t\t.block(Duration.ofSeconds(this.deploymentProperties.getApiTimeout()));\n\t}\n\n\t/**\n\t * Creates the command string required to launch a task by a service on Cloud Foundry.\n\t * @param application the {@link SummaryApplicationResponse} containing the result of the requested staging.\n\t * @param request The {@link AppDeploymentRequest} associated with the task staging.\n\t * @return the command string\n\t */\n\tpublic String getCommand(SummaryApplicationResponse application, AppDeploymentRequest request) {\n\t\tfinal boolean appHasCfEnv = hasCfEnv(request.getResource());\n\t\treturn Stream.concat(Stream.of(application.getDetectedStartCommand()), request.getCommandlineArguments().stream())\n\t\t\t\t.map( arg-> {\n\t\t\t\t\tint indexOfEquals = arg.indexOf(\"=\");\n\t\t\t\t\tif(indexOfEquals > -1) {\n\t\t\t\t\t\tString key = arg.substring(0, indexOfEquals);\n\t\t\t\t\t\tString value = arg.substring(indexOfEquals);\n\t\t\t\t\t\tkey = escapeChar(key, \"(\");\n\t\t\t\t\t\tkey = escapeChar(key, \")\");\n\t\t\t\t\t\targ = key + value;\n\t\t\t\t\t}\n\t\t\t\t\treturn appHasCfEnv ? CfEnvConfigurer.appendCloudProfileToSpringProfilesActiveArg(arg) : arg;\n\t\t\t\t})\n\t\t\t\t.collect(Collectors.joining(\" \"));\n\t}\n\n\tprivate String escapeChar(String value, String character) {\n\t\tif(value.contains(character)) {\n\t\t\tvalue = value.replace(character, \"\\\\\\\\\\\\\" + character);\n\t\t}\n\t\treturn value;\n\t}\n\n\tprivate boolean pushTaskAppsEnabled() {\n\t\treturn deploymentProperties.isPushTaskAppsEnabled();\n\t}\n\n\tprivate Mono<AbstractApplicationSummary> deployApplication(AppDeploymentRequest request) {\n\n\t\tString name = request.getDefinition().getName();\n\n\t\treturn pushApplication(name, request)\n\t\t\t.then(requestStopApplication(name))\n\t\t\t.then(requestGetApplication(name))\n\t\t\t.cast(AbstractApplicationSummary.class);\n\t}\n\n\n\tprivate Mono<AbstractApplicationSummary> getOptionalApplication(AppDeploymentRequest request) {\n\t\tString name = request.getDefinition().getName();\n\n\t\tFlux<ApplicationSummary> applications = requestListApplications()\n\t\t\t\t.filter(application -> name.equals(application.getName()));\n\n\t\tif (!pushTaskAppsEnabled()) {\n\t\t\treturn applications\n\t\t\t\t\t.single()\n\t\t\t\t\t.onErrorMap(t->\n\t\t\t\t\t\tt instanceof NoSuchElementException ?\n\t\t\t\t\t\t\t\tnew IllegalStateException(String.format(\"Application %s does not exist\", name)) : t)\n\t\t\t\t\t.cast(AbstractApplicationSummary.class);\n\t\t}\n\n\t\treturn applications\n\t\t\t\t.singleOrEmpty()\n\t\t\t\t.cast(AbstractApplicationSummary.class);\n\t}\n\n\tprivate Mono<SummaryApplicationResponse> getOrDeployApplication(AppDeploymentRequest request) {\n\t\treturn getOptionalApplication(request)\n\t\t\t.switchIfEmpty(deployApplication(request))\n\t\t\t.flatMap(application -> requestGetApplicationSummary(application.getId()));\n\t}\n\n\tprivate Mono<String> launchTask(SummaryApplicationResponse application, AppDeploymentRequest request) {\n\t\treturn requestCreateTask(application.getId(),\n\t\t\t\tgetCommand(application, request),\n\t\t\t\tmemory(request),\n\t\t\t\tdiskQuota(request),\n\t\t\t\trequest.getDefinition().getName())\n\t\t\t.map(CreateTaskResponse::getId);\n\t}\n\n\tprivate Mono<Void> pushApplication(String name, AppDeploymentRequest request) {\n\t\tif (!pushTaskAppsEnabled()) {\n\t\t\treturn Mono.empty();\n\t\t}\n\n\t\treturn requestPushApplication(PushApplicationManifestRequest.builder()\n\t\t\t.manifest(ApplicationManifest.builder()\n\t\t\t\t.path(getApplication(request))\n\t\t\t\t.docker(Docker.builder().image(getDockerImage(request)).build())\n\t\t\t\t.buildpacks(buildpacks(request))\n\t\t\t\t.command(\"echo '*** First run of container to allow droplet creation.***' && sleep 300\")\n\t\t\t\t.disk(diskQuota(request))\n\t\t\t\t.environmentVariables(mergeEnvironmentVariables(name, request))\n\t\t\t\t.healthCheckType(ApplicationHealthCheck.NONE)\n\t\t\t\t.memory(memory(request))\n\t\t\t\t.name(name)\n\t\t\t\t.noRoute(true)\n\t\t\t\t.services(servicesToBind(request))\n\t\t\t\t.build())\n\t\t\t.stagingTimeout(this.deploymentProperties.getStagingTimeout())\n\t\t\t.startupTimeout(this.deploymentProperties.getStartupTimeout())\n\t\t\t.build());\n\t}\n\n\tprivate Mono<CreateTaskResponse> requestCreateTask(String applicationId, String command, int memory, int disk, String name) {\n\t\treturn this.client.tasks()\n\t\t\t.create(CreateTaskRequest.builder()\n\t\t\t\t.applicationId(applicationId)\n\t\t\t\t.command(command)\n\t\t\t\t.memoryInMb(memory)\n\t\t\t\t.diskInMb(disk)\n\t\t\t\t.name(name)\n\t\t\t\t.build());\n\t}\n\n\tprivate Mono<Void> requestDeleteApplication(String name) {\n\t\treturn this.operations.applications()\n\t\t\t.delete(DeleteApplicationRequest.builder()\n\t\t\t\t.deleteRoutes(deploymentProperties.isDeleteRoutes())\n\t\t\t\t.name(name)\n\t\t\t\t.build());\n\t}\n\n\tprivate Mono<ApplicationDetail> requestGetApplication(String name) {\n\t\treturn this.operations.applications()\n\t\t\t.get(GetApplicationRequest.builder()\n\t\t\t\t.name(name)\n\t\t\t\t.build());\n\t}\n\n\tprivate Mono<SummaryApplicationResponse> requestGetApplicationSummary(String applicationId) {\n\t\treturn this.client.applicationsV2()\n\t\t\t.summary(org.cloudfoundry.client.v2.applications.SummaryApplicationRequest.builder()\n\t\t\t\t.applicationId(applicationId)\n\t\t\t\t.build());\n\t}\n\n\tprivate Flux<ApplicationSummary> requestListApplications() {\n\t\treturn this.operations.applications()\n\t\t\t.list();\n\t}\n\n\tprivate Mono<Void> requestPushApplication(PushApplicationManifestRequest request) {\n\t\treturn this.operations.applications()\n\t\t\t.pushManifest(request);\n\t}\n\n\tprivate Mono<Void> requestStopApplication(String name) {\n\t\treturn this.operations.applications()\n\t\t\t.stop(StopApplicationRequest.builder()\n\t\t\t\t.name(name)\n\t\t\t\t.build());\n\t}\n\n}\n"
  },
  {
    "path": "spring-cloud-deployer-cloudfoundry/src/main/java/org/springframework/cloud/deployer/spi/cloudfoundry/DurationConverter.java",
    "content": "/*\n * Copyright 2016 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.cloudfoundry;\n\nimport java.time.Duration;\nimport java.time.format.DateTimeParseException;\n\nimport org.springframework.core.convert.converter.Converter;\nimport org.springframework.util.StringUtils;\n\n/**\n * Converter from String to {@link java.time.Duration}, accepting human readable forms (without an H or a T).\n *\n * @author Eric Bottard\n */\npublic class DurationConverter implements Converter <String, Duration> {\n\n\t@Override\n\tpublic Duration convert(String source) {\n\t\tif (StringUtils.hasText(source)) {\n\t\t\treturn null;\n\t\t}\n\t\ttry {\n\t\t\treturn Duration.parse(source);\n\t\t}\n\t\tcatch (DateTimeParseException ignored) {\n\t\t\treturn Duration.parse(\"PT\" + source);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "spring-cloud-deployer-cloudfoundry/src/main/java/org/springframework/cloud/deployer/spi/cloudfoundry/ServiceParser.java",
    "content": "/*\n * Copyright 2019-2020 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.cloudfoundry;\n\nimport java.util.ArrayList;\nimport java.util.LinkedHashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\nimport java.util.stream.Stream;\n\nimport org.springframework.util.StringUtils;\n\n/**\n * Parses service instances and parses binding parameters if provided. Accepts Strings like\n * 'myservice foo=bar, cat=bat' or 'service foo:bar, cat:bat', White space is required between the\n * service name and parameters, but optional between the key-value pairs.\n *\n * @author David Turanski\n */\nabstract class ServiceParser {\n\n  private static Pattern serviceWithParameters = Pattern.compile(\"([^\\\\s]+)\\\\s*?(.*)?\");\n\n  private static Pattern singleQuotedLiteral = Pattern.compile(\"'([^']*?)'\");\n\n  /**\n   * @param serviceSpec the service instance followed by optional parameters.\n   * @return an Option<Map> of parameters.\n   */\n  static Optional<Map<String, String>> getServiceParameters(String serviceSpec) {\n    Matcher m = serviceWithParameters.matcher(serviceSpec);\n\n    if (m.matches()) {\n      return parseParameters(m.group(2), serviceSpec);\n    }\n\n    return Optional.ofNullable(null);\n  }\n\n  /**\n   * Extract the service name.\n   *\n   * @param serviceSpec the service instance name followed by optional parameters\n   * @return the service instance\n   */\n  static String getServiceInstanceName(String serviceSpec) {\n    Matcher m = serviceWithParameters.matcher(serviceSpec);\n    if (m.matches()) {\n      return m.group(1);\n    } else {\n      throw new IllegalArgumentException(\"invalid service specification: \" + serviceSpec);\n    }\n  }\n\n  static List<String> splitServiceProperties(String serviceProperties) {\n    List<String> serviceInstances = new ArrayList<>();\n\n    if (StringUtils.hasText(serviceProperties)) {\n      Matcher m = singleQuotedLiteral.matcher(serviceProperties);\n      int index = 0;\n\n      while (index >= 0 && m.find(index)) {\n        String val = m.group().replaceAll(\"'\", \"\");\n        serviceInstances.add(val);\n        serviceProperties = serviceProperties.replaceAll(m.group(), \"\");\n        index = serviceProperties.indexOf(\"'\");\n        m = singleQuotedLiteral.matcher(serviceProperties);\n      }\n\n      Stream.of(serviceProperties.split(\",\"))\n          .filter(StringUtils::hasText)\n          .map(String::trim)\n          .forEach(s -> serviceInstances.add(s));\n    }\n\n    return serviceInstances;\n  }\n\n  private static Optional<Map<String, String>> parseParameters(\n      String rawParametersString, String serviceSpec) {\n    if (StringUtils.hasText(rawParametersString)) {\n      Map<String, String> parameters = new LinkedHashMap<>();\n      String[] entries = rawParametersString.split(\",\");\n      Stream.of(entries)\n          .forEach(\n              s -> {\n                String[] pair = s.split(\"[:|=]\");\n                if (pair.length != 2) {\n                  throw new IllegalArgumentException(\n                      \"invalid service specification: \" + serviceSpec);\n                }\n                parameters.put(pair[0].trim(), pair[1].trim());\n              });\n      return Optional.of(parameters);\n    }\n    return Optional.ofNullable(null);\n  }\n}\n"
  },
  {
    "path": "spring-cloud-deployer-cloudfoundry/src/main/java/org/springframework/cloud/deployer/spi/cloudfoundry/UnsupportedVersionTaskLauncher.java",
    "content": "/*\n * Copyright 2017 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.cloudfoundry;\n\nimport com.github.zafarkhaja.semver.Version;\nimport org.cloudfoundry.operations.CloudFoundryOperations;\n\nimport org.springframework.cloud.deployer.spi.core.AppDeploymentRequest;\nimport org.springframework.cloud.deployer.spi.core.RuntimeEnvironmentInfo;\nimport org.springframework.cloud.deployer.spi.task.TaskLauncher;\nimport org.springframework.cloud.deployer.spi.task.TaskStatus;\nimport org.springframework.cloud.deployer.spi.util.RuntimeVersionUtils;\n\n/**\n * A failing implementation of {@link org.springframework.cloud.deployer.spi.task.TaskLauncher} for versions of the\n * Cloud Controller API below {@link #MINIMUM_SUPPORTED_VERSION}.\n *\n * @author Eric Bottard\n * @see CloudFoundryDeployerAutoConfiguration\n */\npublic class UnsupportedVersionTaskLauncher implements TaskLauncher {\n\n\tpublic static final Version MINIMUM_SUPPORTED_VERSION = Version.forIntegers(2, 63, 0);\n\n\tprivate final Version actualVersion;\n\n\tprivate final RuntimeEnvironmentInfo info;\n\n\tpublic UnsupportedVersionTaskLauncher(Version actualVersion, RuntimeEnvironmentInfo info) {\n\t\tthis.actualVersion = actualVersion;\n\t\tthis.info = info;\n\t}\n\n\t@Override\n\tpublic String launch(AppDeploymentRequest request) {\n\t\tthrow failure();\n\t}\n\n\t@Override\n\tpublic void cancel(String id) {\n\t\tthrow failure();\n\t}\n\n\t@Override\n\tpublic TaskStatus status(String id) {\n\t\tthrow failure();\n\t}\n\n\t@Override\n\tpublic void cleanup(String id) {\n\t\tthrow failure();\n\t}\n\n\t@Override\n\tpublic void destroy(String appName) {\n\t\tthrow failure();\n\t}\n\n\t@Override\n\tpublic RuntimeEnvironmentInfo environmentInfo() {\n\t\treturn info;\n\t}\n\t@Override\n\tpublic int getMaximumConcurrentTasks() {\n\t\tthrow failure();\n\t}\n\t@Override\n\tpublic int getRunningTaskExecutionCount() {\n\t\tthrow failure();\n\t}\n\n\t@Override\n\tpublic String getLog(String appName) {\n\t\tthrow failure();\n\t}\n\n\tprivate UnsupportedOperationException failure() {\n\t\treturn new UnsupportedOperationException(\"Cloud Foundry API version \" + actualVersion + \" is earlier than \"\n\t\t\t+ MINIMUM_SUPPORTED_VERSION + \" and is incompatible with cf-java-client \" +\n\t\t\tRuntimeVersionUtils.getVersion(CloudFoundryOperations.class)+ \". It is thus unsupported\");\n\t}\n\n}\n"
  },
  {
    "path": "spring-cloud-deployer-cloudfoundry/src/main/java/org/springframework/cloud/deployer/spi/scheduler/cloudfoundry/CloudFoundryAppScheduler.java",
    "content": "/*\n * Copyright 2018-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.scheduler.cloudfoundry;\n\nimport java.text.ParseException;\nimport java.time.Duration;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.stream.Collectors;\n\nimport javax.net.ssl.SSLException;\n\nimport io.jsonwebtoken.lang.Assert;\nimport io.pivotal.scheduler.SchedulerClient;\nimport io.pivotal.scheduler.v1.jobs.CreateJobRequest;\nimport io.pivotal.scheduler.v1.jobs.DeleteJobRequest;\nimport io.pivotal.scheduler.v1.jobs.Job;\nimport io.pivotal.scheduler.v1.jobs.ListJobsRequest;\nimport io.pivotal.scheduler.v1.jobs.ListJobsResponse;\nimport io.pivotal.scheduler.v1.jobs.ScheduleJobRequest;\nimport io.pivotal.scheduler.v1.jobs.ScheduleJobResponse;\nimport io.pivotal.scheduler.v1.schedules.ExpressionType;\nimport org.apache.commons.logging.Log;\nimport org.apache.commons.logging.LogFactory;\nimport org.cloudfoundry.client.v2.applications.SummaryApplicationResponse;\nimport org.cloudfoundry.operations.CloudFoundryOperations;\nimport org.cloudfoundry.operations.applications.AbstractApplicationSummary;\nimport org.cloudfoundry.operations.applications.ApplicationSummary;\nimport org.cloudfoundry.operations.spaces.SpaceSummary;\nimport reactor.core.publisher.Flux;\nimport reactor.core.publisher.Mono;\n\nimport org.springframework.cloud.deployer.spi.cloudfoundry.CloudFoundryConnectionProperties;\nimport org.springframework.cloud.deployer.spi.cloudfoundry.CloudFoundryDeploymentProperties;\nimport org.springframework.cloud.deployer.spi.cloudfoundry.CloudFoundryTaskLauncher;\nimport org.springframework.cloud.deployer.spi.core.AppDeploymentRequest;\nimport org.springframework.cloud.deployer.spi.scheduler.CreateScheduleException;\nimport org.springframework.cloud.deployer.spi.scheduler.ScheduleInfo;\nimport org.springframework.cloud.deployer.spi.scheduler.ScheduleRequest;\nimport org.springframework.cloud.deployer.spi.scheduler.Scheduler;\nimport org.springframework.cloud.deployer.spi.scheduler.SchedulerException;\nimport org.springframework.cloud.deployer.spi.scheduler.SchedulerPropertyKeys;\nimport org.springframework.cloud.deployer.spi.scheduler.UnScheduleException;\nimport org.springframework.cloud.deployer.spi.scheduler.cloudfoundry.expression.QuartzCronExpression;\nimport org.springframework.retry.RecoveryCallback;\nimport org.springframework.retry.RetryCallback;\nimport org.springframework.retry.RetryContext;\nimport org.springframework.retry.policy.SimpleRetryPolicy;\nimport org.springframework.retry.support.RetryTemplate;\n\n/**\n * A Cloud Foundry implementation of the Scheduler interface.\n *\n * @author Glenn Renfro\n */\npublic class CloudFoundryAppScheduler implements Scheduler {\n\n\tpublic final static String CRON_EXPRESSION_KEY = \"spring.cloud.deployer.cloudfoundry.cron.expression\";\n\n\tprivate final static int PCF_PAGE_START_NUM = 1; //First PageNum for PCFScheduler starts at 1.\n\n\tprivate final static int MAX_SCHEDULE_NAME_LENGTH = 255;\n\n\tprivate final static String SCHEDULER_SERVICE_ERROR_MESSAGE = \"Scheduler Service returned a null response.\";\n\n\tprotected final static Log logger = LogFactory.getLog(CloudFoundryAppScheduler.class);\n\tprivate final SchedulerClient client;\n\tprivate final CloudFoundryOperations operations;\n\tprivate final CloudFoundryConnectionProperties properties;\n\tprivate final CloudFoundryTaskLauncher taskLauncher;\n\tprivate final CloudFoundrySchedulerProperties schedulerProperties;\n\tprivate final CloudFoundryDeploymentProperties deploymentProperties;\n\n\t@Deprecated\n\tpublic CloudFoundryAppScheduler(SchedulerClient client, CloudFoundryOperations operations,\n\t\t\tCloudFoundryConnectionProperties properties, CloudFoundryTaskLauncher taskLauncher,\n\t\t\tCloudFoundrySchedulerProperties schedulerProperties) {\n\t\tAssert.notNull(client, \"client must not be null\");\n\t\tAssert.notNull(operations, \"operations must not be null\");\n\t\tAssert.notNull(properties, \"properties must not be null\");\n\t\tAssert.notNull(taskLauncher, \"taskLauncher must not be null\");\n\t\tAssert.notNull(schedulerProperties, \"schedulerProperties must not be null\");\n\n\t\tthis.client = client;\n\t\tthis.operations = operations;\n\t\tthis.properties = properties;\n\t\tthis.taskLauncher = taskLauncher;\n\t\tthis.schedulerProperties = schedulerProperties;\n\n\t\tthis.deploymentProperties = new CloudFoundryDeploymentProperties();\n\t\tthis.deploymentProperties.setSchedulerUrl(this.schedulerProperties.getSchedulerUrl());\n\t\tthis.deploymentProperties.setScheduleSSLRetryCount(this.schedulerProperties.getScheduleSSLRetryCount());\n\t\tthis.deploymentProperties.setScheduleTimeoutInSeconds(this.schedulerProperties.getScheduleTimeoutInSeconds());\n\t\tthis.deploymentProperties.setUnScheduleTimeoutInSeconds(this.schedulerProperties.getScheduleTimeoutInSeconds());\n\t\tthis.deploymentProperties.setListTimeoutInSeconds(this.schedulerProperties.getListTimeoutInSeconds());\n\t}\n\tpublic CloudFoundryAppScheduler(SchedulerClient client, CloudFoundryOperations operations,\n\t\t\tCloudFoundryConnectionProperties properties, CloudFoundryTaskLauncher taskLauncher,\n\t\t\tCloudFoundryDeploymentProperties deploymentProperties) {\n\t\tAssert.notNull(client, \"client must not be null\");\n\t\tAssert.notNull(operations, \"operations must not be null\");\n\t\tAssert.notNull(properties, \"properties must not be null\");\n\t\tAssert.notNull(taskLauncher, \"taskLauncher must not be null\");\n\t\tAssert.notNull(deploymentProperties, \"deployment must not be null\");\n\n\t\tthis.client = client;\n\t\tthis.operations = operations;\n\t\tthis.properties = properties;\n\t\tthis.taskLauncher = taskLauncher;\n\t\tthis.deploymentProperties = deploymentProperties;\n\t\tthis.schedulerProperties = null;\n\t}\n\n\t@Override\n\tpublic void schedule(ScheduleRequest scheduleRequest) {\n\t\tString appName = scheduleRequest.getDefinition().getName();\n\t\tString scheduleName = scheduleRequest.getScheduleName();\n\t\tlogger.debug(String.format(\"Scheduling: %s\", scheduleName));\n\n\t\tif (scheduleName.length() > MAX_SCHEDULE_NAME_LENGTH) {\n\t\t\tthrow new CreateScheduleException(String.format(\"Schedule can not be created because its name \" +\n\t\t\t\t\t\t\t\"'%s' has too many characters.  Schedule name length\" +\n\t\t\t\t\t\t\t\" must be %s characters or less.\",\n\t\t\t\t\tscheduleName, MAX_SCHEDULE_NAME_LENGTH), null);\n\t\t}\n\n\t\tString cronExpressionCandidate = null;\n\t\tif (cronExpressionCandidate == null && scheduleRequest.getSchedulerProperties() != null) {\n\t\t\tcronExpressionCandidate = scheduleRequest.getSchedulerProperties().get(SchedulerPropertyKeys.CRON_EXPRESSION);\n\t\t}\n\t\telse if (cronExpressionCandidate == null && scheduleRequest.getDeploymentProperties().get(\"spring.cloud.scheduler.cron.expression\") != null) {\n\t\t\tcronExpressionCandidate = scheduleRequest.getDeploymentProperties().get(\"spring.cloud.scheduler.cron.expression\");\n\t\t}\n\t\telse if (cronExpressionCandidate == null && scheduleRequest.getDeploymentProperties().get(\"spring.cloud.deployer.cron.expression\") != null) {\n\t\t\tcronExpressionCandidate = scheduleRequest.getDeploymentProperties().get(\"spring.cloud.deployer.cron.expression\");\n\t\t}\n\t\telse if (scheduleRequest.getDeploymentProperties().get(CRON_EXPRESSION_KEY) != null) {\n\t\t\tcronExpressionCandidate = scheduleRequest.getDeploymentProperties().get(CRON_EXPRESSION_KEY);\n\t\t}\n\t\tAssert.hasText(cronExpressionCandidate, String.format(\n\t\t\t\t\"request's scheduleProperties must have a %s or %s that is not null nor empty\",\n\t\t\t\tSchedulerPropertyKeys.CRON_EXPRESSION, CRON_EXPRESSION_KEY));\n\t\tString cronExpression = cronExpressionCandidate;\n\t\ttry {\n\t\t\tnew QuartzCronExpression(\"0 \" + cronExpression);\n\t\t}\n\t\tcatch(ParseException pe) {\n\t\t\tthrow new CreateScheduleException(\"Cron Expression is invalid: \" + pe.getMessage(), pe);\n\t\t}\n\t\tString command = stageTask(scheduleRequest);\n\n\t\tretryTemplate().execute(new RetryCallback<Void, RuntimeException>() {\n\t\t\t\t\t@Override\n\t\t\t\t\tpublic Void doWithRetry(RetryContext retryContext) throws RuntimeException {\n\t\t\t\t\t\tscheduleTask(appName, scheduleName, cronExpression, command);\n\t\t\t\t\t\treturn null;\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\tnew RecoveryCallback<Void>() {\n\t\t\t\t\t@Override\n\t\t\t\t\tpublic Void recover(RetryContext retryContext) throws Exception {\n\t\t\t\t\t\tif (retryContext.getLastThrowable() != null) {\n\t\t\t\t\t\t\tlogger.error(\"Retry Context reported the following exception: \" + retryContext.getLastThrowable().getMessage());\n\t\t\t\t\t\t}\n\t\t\t\t\t\tlogger.error(\"Unable to schedule application\");\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tlogger.debug(\"removing job portion of the schedule.\");\n\t\t\t\t\t\t\tunschedule(scheduleName);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tcatch (UnScheduleException ex) {\n\t\t\t\t\t\t\tlogger.debug(\"No job to be removed.\");\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthrow new CreateScheduleException(scheduleName, retryContext.getLastThrowable());\n\t\t\t\t\t}\n\t\t\t\t});\n\t}\n\n\t@Override\n\tpublic void unschedule(String scheduleName) {\n\t\tlogger.debug(String.format(\"Unscheduling: %s\", scheduleName));\n\t\tthis.client.jobs().delete(DeleteJobRequest.builder()\n\t\t\t\t.jobId(getJob(scheduleName))\n\t\t\t\t.build())\n\t\t\t\t.block(Duration.ofSeconds(this.deploymentProperties.getUnScheduleTimeoutInSeconds()));\n\t}\n\n\t@Override\n\tpublic List<ScheduleInfo> list(String taskDefinitionName) {\n\t\treturn list().stream().filter(scheduleInfo ->\n\t\t\t\tscheduleInfo.getTaskDefinitionName().equals(taskDefinitionName))\n\t\t\t\t.collect(Collectors.toList());\n\t}\n\n\t@Override\n\tpublic List<ScheduleInfo> list() {\n\t\tList<ScheduleInfo> result = new ArrayList<>();\n\t\tfor (int i = PCF_PAGE_START_NUM; i <= getJobPageCount(); i++) {\n\t\t\tList<ScheduleInfo> scheduleInfoPage = getSchedules(i)\n\t\t\t\t\t.collectList()\n\t\t\t\t\t.block(Duration.ofSeconds(this.deploymentProperties.getListTimeoutInSeconds()));\n\t\t\tif(scheduleInfoPage == null) {\n\t\t\t\tthrow new SchedulerException(SCHEDULER_SERVICE_ERROR_MESSAGE);\n\t\t\t}\n\t\t\tresult.addAll(scheduleInfoPage);\n\t\t}\n\t\treturn result;\n\t}\n\n\t/**\n\t * Schedules the job for the application.\n\t * @param appName The name of the task app to be scheduled.\n\t * @param scheduleName the name of the schedule.\n\t * @param expression the cron expression.\n\t * @param command the command returned from the staging.\n\t */\n\tprivate void scheduleTask(String appName, String scheduleName,\n\t\t\tString expression, String command) {\n\t\tlogger.debug(String.format(\"Scheduling Task: \", appName));\n\n\t\tScheduleJobResponse response = getApplicationByAppName(appName)\n\t\t\t\t.flatMap(abstractApplicationSummary -> {\n\t\t\t\t\treturn this.client.jobs().create(CreateJobRequest.builder()\n\t\t\t\t\t\t\t.applicationId(abstractApplicationSummary.getId()) // App GUID\n\t\t\t\t\t\t\t.command(command)\n\t\t\t\t\t\t\t.name(scheduleName)\n\t\t\t\t\t\t\t.build());\n\t\t\t\t}).flatMap(createJobResponse -> {\n\t\t\treturn this.client.jobs().schedule(ScheduleJobRequest.\n\t\t\t\t\tbuilder().\n\t\t\t\t\tjobId(createJobResponse.getId()).\n\t\t\t\t\texpression(expression).\n\t\t\t\t\texpressionType(ExpressionType.CRON).\n\t\t\t\t\tenabled(true).\n\t\t\t\t\tbuild());\n\t\t})\n\t\t\t\t.onErrorMap(e -> {\n\t\t\t\t\tif (e instanceof SSLException) {\n\t\t\t\t\t\tthrow new CloudFoundryScheduleSSLException(\"Failed to schedule\" + scheduleName, e);\n\t\t\t\t\t}\n\t\t\t\t\telse {\n\t\t\t\t\t\tthrow new CreateScheduleException(scheduleName, e);\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t\t.block(Duration.ofSeconds(this.deploymentProperties.getScheduleTimeoutInSeconds()));\n\t\tif(response == null) {\n\t\t\tthrow new SchedulerException(SCHEDULER_SERVICE_ERROR_MESSAGE);\n\t\t}\n\t}\n\n\t/**\n\t * Stages the application specified in the {@link ScheduleRequest} on the CF server.\n\t * @param scheduleRequest {@link ScheduleRequest} containing the information required to schedule a task.\n\t * @return the command string for the scheduled task.\n\t */\n\tprivate String stageTask(ScheduleRequest scheduleRequest) {\n\t\tlogger.debug(String.format(\"Staging Task: \",\n\t\t\t\tscheduleRequest.getDefinition().getName()));\n\t\tAppDeploymentRequest request = new AppDeploymentRequest(\n\t\t\t\tscheduleRequest.getDefinition(),\n\t\t\t\tscheduleRequest.getResource(),\n\t\t\t\tscheduleRequest.getDeploymentProperties(),\n\t\t\t\tscheduleRequest.getCommandlineArguments());\n\t\tSummaryApplicationResponse response = taskLauncher.stage(request);\n\t\treturn taskLauncher.getCommand(response, request);\n\t}\n\n\t/**\n\t * Retrieve a {@link Mono} containing the {@link ApplicationSummary} associated with the appId.\n\t * @param appName the name of the {@link AbstractApplicationSummary} to search.\n\t */\n\tprivate Mono<AbstractApplicationSummary> getApplicationByAppName(String appName) {\n\t\treturn requestListApplications()\n\t\t\t\t.filter(application -> appName.equals(application.getName()))\n\t\t\t\t.singleOrEmpty()\n\t\t\t\t.cast(AbstractApplicationSummary.class);\n\t}\n\n\t/**\n\t * Retrieve a  {@link Flux} of {@link ApplicationSummary}s.\n\t */\n\tprivate Flux<ApplicationSummary> requestListApplications() {\n\t\treturn this.operations.applications()\n\t\t\t\t.list();\n\t}\n\n\t/**\n\t * Retrieve a cached {@link Flux} of {@link ApplicationSummary}s.\n\t */\n\tprivate Flux<ApplicationSummary> cacheAppSummaries() {\n\t\treturn requestListApplications()\n\t\t\t\t.cache(); //cache results from first call.  No need to re-retrieve each time.\n\t}\n\n\t/**\n\t * Retrieve a {@link Flux} containing the available {@link SpaceSummary}s.\n\t * @return {@link Flux} of {@link SpaceSummary}s.\n\t */\n\tprivate Flux<SpaceSummary> requestSpaces() {\n\t\treturn this.operations.spaces()\n\t\t\t\t.list();\n\t}\n\n\t/**\n\t * Retrieve a {@link Mono} containing a {@link SpaceSummary} for the specified name.\n\t * @param spaceName the name of space to search.\n\t * @return the {@link SpaceSummary} associated with the spaceName.\n\t */\n\tprivate Mono<SpaceSummary> getSpace(String spaceName) {\n\t\treturn requestSpaces()\n\t\t\t\t.cache() //cache results from first call.\n\t\t\t\t.filter(space -> spaceName.equals(space.getName()))\n\t\t\t\t.singleOrEmpty()\n\t\t\t\t.cast(SpaceSummary.class);\n\t}\n\n\t/**\n\t * Retrieve a {@link Mono} containing the {@link ApplicationSummary} associated with the appId.\n\t * @param applicationSummaries {@link Flux} of {@link ApplicationSummary}s to filter.\n\t * @param appId the id of the {@link ApplicationSummary} to search.\n\t */\n\tprivate Mono<ApplicationSummary> getApplication(Flux<ApplicationSummary> applicationSummaries,\n\t\t\tString appId) {\n\t\treturn applicationSummaries\n\t\t\t\t.filter(application -> appId.equals(application.getId()))\n\t\t\t\t.singleOrEmpty();\n\t}\n\n\t/**\n\t * Retrieve a Flux of {@link ScheduleInfo}s for the pageNumber specified.\n\t * The PCF-Scheduler returns all data in pages of 50 entries.  This method\n\t * retrieves the specified page and transforms the {@link Flux} of {@link Job}s to\n\t * a {@link Flux} of {@link ScheduleInfo}s\n\t *\n\t * @param pageNumber integer containing the page offset for the {@link ScheduleInfo}s to retrieve.\n\t * @return {@link Flux} containing the {@link ScheduleInfo}s for the specified page number.\n\t */\n\tprivate Flux<ScheduleInfo> getSchedules(int pageNumber) {\n\t\tFlux<ApplicationSummary> applicationSummaries = cacheAppSummaries();\n\t\treturn this.getSpace(this.properties.getSpace()).flatMap(requestSummary -> {\n\t\t\treturn this.client.jobs().list(ListJobsRequest.builder()\n\t\t\t\t\t.spaceId(requestSummary.getId())\n\t\t\t\t\t.page(pageNumber)\n\t\t\t\t\t.detailed(true).build());})\n\t\t\t\t.flatMapIterable(jobs -> jobs.getResources())// iterate over the resources returned.\n\t\t\t\t.flatMap(job -> {\n\t\t\t\t\treturn getApplication(applicationSummaries,\n\t\t\t\t\t\t\tjob.getApplicationId()) // get the application name for each job.\n\t\t\t\t\t\t\t.map(optionalApp -> {\n\t\t\t\t\t\t\t\tScheduleInfo scheduleInfo = new ScheduleInfo();\n\t\t\t\t\t\t\t\tscheduleInfo.setScheduleProperties(new HashMap<>());\n\t\t\t\t\t\t\t\tscheduleInfo.setScheduleName(job.getName());\n\t\t\t\t\t\t\t\tscheduleInfo.setTaskDefinitionName(optionalApp.getName());\n\t\t\t\t\t\t\t\tif (job.getJobSchedules() != null) {\n\t\t\t\t\t\t\t\t\tscheduleInfo.getScheduleProperties().put(SchedulerPropertyKeys.CRON_EXPRESSION,\n\t\t\t\t\t\t\t\t\t\t\tjob.getJobSchedules().get(0).getExpression());\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\telse {\n\t\t\t\t\t\t\t\t\tlogger.warn(String.format(\"Job %s does not have an associated schedule\", job.getName()));\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\treturn scheduleInfo;\n\t\t\t\t\t\t\t});\n\t\t\t\t});\n\t}\n\n\t/**\n\t * Retrieves the number of pages that can be returned when retrieving a list of jobs.\n\t * @return an int containing the number of available pages.\n\t */\n\tprivate int getJobPageCount() {\n\t\tListJobsResponse response = this.getSpace(this.properties.getSpace()).flatMap(requestSummary -> {\n\t\t\treturn this.client.jobs().list(ListJobsRequest.builder()\n\t\t\t\t\t.spaceId(requestSummary.getId())\n\t\t\t\t\t.detailed(false).build());\n\t\t}).block();\n\t\tif(response == null) {\n\t\t\tthrow new SchedulerException(SCHEDULER_SERVICE_ERROR_MESSAGE);\n\t\t}\n\t\treturn response.getPagination().getTotalPages();\n\t}\n\n\t/**\n\t * Retrieve a {@link Mono} that contains the {@link Job} for the jobName or null.\n\t * @param jobName - the name of the job to search search.\n\t * @param page - the page to search.\n\t * @return {@link Mono} containing the {@link Job} if found or null if not found.\n\t */\n\tprivate Mono<Job> getJobMono(String jobName, int page) {\n\t\treturn this.getSpace(this.properties.getSpace()).flatMap(requestSummary -> {\n\t\t\treturn this.client\n\t\t\t\t\t.jobs()\n\t\t\t\t\t.list(ListJobsRequest.builder()\n\t\t\t\t\t\t\t.spaceId(requestSummary.getId())\n\t\t\t\t\t\t\t.page(page)\n\t\t\t\t\t\t\t.build()); })\n\t\t\t\t.flatMapIterable(jobs -> jobs.getResources())\n\t\t\t\t.filter(job -> job.getName().equals(jobName))\n\t\t\t\t.singleOrEmpty();// iterate over the resources returned.\n\t}\n\n\t/**\n\t * Retrieve the job id for the specified PCF Job Name.\n\t * @param jobName the name of the job to search.\n\t * @return The job id associated with the job.\n\t */\n\tprivate String getJob(String jobName) {\n\t\tJob result = null;\n\t\tfinal int pageCount = getJobPageCount();\n\t\tfor (int pageNum = PCF_PAGE_START_NUM; pageNum <= pageCount; pageNum++) {\n\t\t\tresult = getJobMono(jobName, pageNum)\n\t\t\t\t\t.block();\n\t\t\tif (result != null) {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\tif(result == null) {\n\t\t\tthrow new UnScheduleException(String.format(\"schedule %s does not exist.\", jobName));\n\t\t}\n\t\treturn result.getId();\n\t}\n\n\tprivate RetryTemplate retryTemplate() {\n\t\tRetryTemplate retryTemplate = new RetryTemplate();\n\t\tSimpleRetryPolicy retryPolicy = new SimpleRetryPolicy(\n\t\t\t\tthis.deploymentProperties.getScheduleSSLRetryCount(),\n\t\t\t\tCollections.singletonMap(CloudFoundryScheduleSSLException.class, true));\n\t\tretryTemplate.setRetryPolicy(retryPolicy);\n\t\treturn retryTemplate;\n\t}\n}\n"
  },
  {
    "path": "spring-cloud-deployer-cloudfoundry/src/main/java/org/springframework/cloud/deployer/spi/scheduler/cloudfoundry/CloudFoundryScheduleSSLException.java",
    "content": "/*\n * Copyright 2018-2019 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.scheduler.cloudfoundry;\n\n/**\n * A {@link RuntimeException} that wraps SSL based exceptions.\n *\n * @author Glenn Renfro\n */\npublic class CloudFoundryScheduleSSLException extends RuntimeException {\n\n\tpublic CloudFoundryScheduleSSLException(String message, Throwable t) {\n\t\tsuper(message, t);\n\t}\n\n\tpublic CloudFoundryScheduleSSLException(String t) {\n\t\tsuper(t);\n\t}\n}\n"
  },
  {
    "path": "spring-cloud-deployer-cloudfoundry/src/main/java/org/springframework/cloud/deployer/spi/scheduler/cloudfoundry/CloudFoundrySchedulerProperties.java",
    "content": "/*\n * Copyright 2018-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.scheduler.cloudfoundry;\n\nimport jakarta.validation.constraints.NotNull;\n\nimport org.springframework.boot.context.properties.ConfigurationProperties;\nimport org.springframework.validation.annotation.Validated;\n\n/**\n * Holds configuration properties for connecting to a Cloud Foundry Scheduler.\n *\n * @author Glenn Renfro\n */\n@Validated\n@ConfigurationProperties(prefix = CloudFoundrySchedulerProperties.CLOUDFOUNDRY_PROPERTIES)\n@Deprecated\npublic class CloudFoundrySchedulerProperties {\n\n\t/**\n\t * Top level prefix for Cloud Foundry related configuration properties.\n\t */\n\tpublic static final String CLOUDFOUNDRY_PROPERTIES = \"spring.cloud.scheduler.cloudfoundry\";\n\n\t/**\n\t * Location of the PCF scheduler REST API enpoint ot use.\n\t */\n\t@NotNull\n\tprivate String schedulerUrl;\n\n\t/**\n\t * The number of retries allowed when scheduling a task if an {@link javax.net.ssl.SSLException} is thrown.\n\t */\n\tprivate int scheduleSSLRetryCount = 5;\n\n\t/**\n\t * The number of seconds to wait for a unSchedule to complete.\n\t */\n\tprivate int unScheduleTimeoutInSeconds = 30;\n\n\t/**\n\t * The number of seconds to wait for a schedule to complete.\n\t * This excludes the time it takes to stage the application on Cloud Foundry.\n\t */\n\tprivate int scheduleTimeoutInSeconds = 30;\n\n\t/**\n\t * The number of seconds to wait for a list of schedules to be returned.\n\t */\n\tprivate int listTimeoutInSeconds = 60;\n\n\n\tpublic String getSchedulerUrl() {\n\t\treturn schedulerUrl;\n\t}\n\n\tpublic void setSchedulerUrl(String schedulerUrl) {\n\t\tthis.schedulerUrl = schedulerUrl;\n\t}\n\n\tpublic int getScheduleSSLRetryCount() {\n\t\treturn scheduleSSLRetryCount;\n\t}\n\n\tpublic void setScheduleSSLRetryCount(int scheduleSSLRetryCount) {\n\t\tthis.scheduleSSLRetryCount = scheduleSSLRetryCount;\n\t}\n\n\tpublic int getUnScheduleTimeoutInSeconds() {\n\t\treturn unScheduleTimeoutInSeconds;\n\t}\n\n\tpublic void setUnScheduleTimeoutInSeconds(int unScheduleTimeoutInSeconds) {\n\t\tthis.unScheduleTimeoutInSeconds = unScheduleTimeoutInSeconds;\n\t}\n\n\tpublic int getScheduleTimeoutInSeconds() {\n\t\treturn scheduleTimeoutInSeconds;\n\t}\n\n\tpublic void setScheduleTimeoutInSeconds(int scheduleTimeoutInSeconds) {\n\t\tthis.scheduleTimeoutInSeconds = scheduleTimeoutInSeconds;\n\t}\n\n\tpublic int getListTimeoutInSeconds() {\n\t\treturn listTimeoutInSeconds;\n\t}\n\n\tpublic void setListTimeoutInSeconds(int listTimeoutInSeconds) {\n\t\tthis.listTimeoutInSeconds = listTimeoutInSeconds;\n\t}\n}\n"
  },
  {
    "path": "spring-cloud-deployer-cloudfoundry/src/main/java/org/springframework/cloud/deployer/spi/scheduler/cloudfoundry/expression/QuartzCronExpression.java",
    "content": "/*\n * Copyright 2018-2019 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.scheduler.cloudfoundry.expression;\n\nimport java.text.ParseException;\nimport java.util.Calendar;\nimport java.util.HashMap;\nimport java.util.Locale;\nimport java.util.Map;\nimport java.util.StringTokenizer;\nimport java.util.TreeSet;\n\n/**\n * Provides a parser and evaluator for unix-like cron expressions. Cron\n * expressions provide the ability to specify complex time combinations such as\n * &quot;At 8:00am every Monday through Friday&quot; or &quot;At 1:30am every\n * last Friday of the month&quot;.\n * <P>\n * Cron expressions are comprised of 6 required fields and one optional field\n * separated by white space.\n *\n * Based on the CronExpression from Quartz.\n *\n * @author Glenn Renfro\n */\npublic final class QuartzCronExpression {\n\n\tprivate static final int SECOND = 0;\n\tprivate static final int MINUTE = 1;\n\tprivate static final int HOUR = 2;\n\tprivate static final int DAY_OF_MONTH = 3;\n\tprivate static final int MONTH = 4;\n\tprivate static final int DAY_OF_WEEK = 5;\n\tprivate static final int YEAR = 6;\n\tprivate static final int ALL_SPEC_INT = 99; // '*'\n\tprivate static final int NO_SPEC_INT = 98; // '?'\n\tprivate static final Integer ALL_SPEC = ALL_SPEC_INT;\n\tprivate static final Integer NO_SPEC = NO_SPEC_INT;\n\n\tprotected static final Map<String, Integer> monthMap = new HashMap<String, Integer>(20);\n\tprotected static final Map<String, Integer> dayMap = new HashMap<String, Integer>(60);\n\tstatic {\n\t\tmonthMap.put(\"JAN\", 0);\n\t\tmonthMap.put(\"FEB\", 1);\n\t\tmonthMap.put(\"MAR\", 2);\n\t\tmonthMap.put(\"APR\", 3);\n\t\tmonthMap.put(\"MAY\", 4);\n\t\tmonthMap.put(\"JUN\", 5);\n\t\tmonthMap.put(\"JUL\", 6);\n\t\tmonthMap.put(\"AUG\", 7);\n\t\tmonthMap.put(\"SEP\", 8);\n\t\tmonthMap.put(\"OCT\", 9);\n\t\tmonthMap.put(\"NOV\", 10);\n\t\tmonthMap.put(\"DEC\", 11);\n\n\t\tdayMap.put(\"SUN\", 1);\n\t\tdayMap.put(\"MON\", 2);\n\t\tdayMap.put(\"TUE\", 3);\n\t\tdayMap.put(\"WED\", 4);\n\t\tdayMap.put(\"THU\", 5);\n\t\tdayMap.put(\"FRI\", 6);\n\t\tdayMap.put(\"SAT\", 7);\n\t}\n\n\tprivate final String cronExpression;\n\tprivate TreeSet<Integer> seconds;\n\tprivate TreeSet<Integer> minutes;\n\tprivate TreeSet<Integer> hours;\n\tprivate TreeSet<Integer> daysOfMonth;\n\tprivate TreeSet<Integer> months;\n\tprivate TreeSet<Integer> daysOfWeek;\n\tprivate TreeSet<Integer> years;\n\n\tprivate int nthdayOfWeek = 0;\n\tprivate boolean lastdayOfMonth = false;\n\tprivate int lastdayOffset = 0;\n\n\tpublic static final int MAX_YEAR = Calendar.getInstance().get(Calendar.YEAR) + 100;\n\n\t/**\n\t * Constructs a new {@link QuartzCronExpression} based on the specified\n\t * parameter.\n\t *\n\t * @param cronExpression String representation of the cron expression the\n\t *                       new object should represent\n\t * @throws ParseException\n\t *         if the string expression cannot be parsed into a valid\n\t *         {@link QuartzCronExpression}\n\t */\n\tpublic QuartzCronExpression(String cronExpression) throws ParseException {\n\t\tif (cronExpression == null) {\n\t\t\tthrow new IllegalArgumentException(\"cronExpression cannot be null\");\n\t\t}\n\n\t\tthis.cronExpression = cronExpression.toUpperCase(Locale.ROOT);\n\n\t\tbuildExpression(this.cronExpression);\n\t}\n\n\t/**\n\t * Returns the string representation of the {@link QuartzCronExpression}\n\t *\n\t * @return a string representation of the {@link QuartzCronExpression}\n\t */\n\t@Override\n\tpublic String toString() {\n\t\treturn cronExpression;\n\t}\n\n\n\t////////////////////////////////////////////////////////////////////////////\n\t//\n\t// Expression Parsing Functions\n\t//\n\t////////////////////////////////////////////////////////////////////////////\n\n\tprotected void buildExpression(String expression) throws ParseException {\n\n\t\ttry {\n\n\t\t\tif (seconds == null) {\n\t\t\t\tseconds = new TreeSet<>();\n\t\t\t}\n\t\t\tif (minutes == null) {\n\t\t\t\tminutes = new TreeSet<>();\n\t\t\t}\n\t\t\tif (hours == null) {\n\t\t\t\thours = new TreeSet<>();\n\t\t\t}\n\t\t\tif (daysOfMonth == null) {\n\t\t\t\tdaysOfMonth = new TreeSet<>();\n\t\t\t}\n\t\t\tif (months == null) {\n\t\t\t\tmonths = new TreeSet<>();\n\t\t\t}\n\t\t\tif (daysOfWeek == null) {\n\t\t\t\tdaysOfWeek = new TreeSet<>();\n\t\t\t}\n\t\t\tif (years == null) {\n\t\t\t\tyears = new TreeSet<>();\n\t\t\t}\n\n\t\t\tint exprOn = SECOND;\n\n\t\t\tStringTokenizer exprsTok = new StringTokenizer(expression, \" \\t\",\n\t\t\t\t\tfalse);\n\n\t\t\twhile (exprsTok.hasMoreTokens() && exprOn <= YEAR) {\n\t\t\t\tString expr = exprsTok.nextToken().trim();\n\n\t\t\t\t// throw an exception if L is used with other days of the month\n\t\t\t\tif(exprOn == DAY_OF_MONTH && expr.indexOf('L') != -1 && expr.length() > 1 && expr.contains(\",\")) {\n\t\t\t\t\tthrow new ParseException(\"Support for specifying 'L' and 'LW' with other days of the month is not implemented\", -1);\n\t\t\t\t}\n\t\t\t\t// throw an exception if L is used with other days of the week\n\t\t\t\tif(exprOn == DAY_OF_WEEK && expr.indexOf('L') != -1 && expr.length() > 1  && expr.contains(\",\")) {\n\t\t\t\t\tthrow new ParseException(\"Support for specifying 'L' with other days of the week is not implemented\", -1);\n\t\t\t\t}\n\t\t\t\tif(exprOn == DAY_OF_WEEK && expr.indexOf('#') != -1 && expr.indexOf('#', expr.indexOf('#') +1) != -1) {\n\t\t\t\t\tthrow new ParseException(\"Support for specifying multiple \\\"nth\\\" days is not implemented.\", -1);\n\t\t\t\t}\n\n\t\t\t\tStringTokenizer vTok = new StringTokenizer(expr, \",\");\n\t\t\t\twhile (vTok.hasMoreTokens()) {\n\t\t\t\t\tString v = vTok.nextToken();\n\t\t\t\t\tstoreExpressionVals(0, v, exprOn);\n\t\t\t\t}\n\n\t\t\t\texprOn++;\n\t\t\t}\n\n\t\t\tif (exprOn <= DAY_OF_WEEK) {\n\t\t\t\tthrow new ParseException(\"Unexpected end of expression.\",\n\t\t\t\t\t\texpression.length());\n\t\t\t}\n\n\t\t\tif (exprOn <= YEAR) {\n\t\t\t\tstoreExpressionVals(0, \"*\", YEAR);\n\t\t\t}\n\n\t\t\tTreeSet<Integer> dow = getSet(DAY_OF_WEEK);\n\t\t\tTreeSet<Integer> dom = getSet(DAY_OF_MONTH);\n\n\t\t\t// Copying the logic from the UnsupportedOperationException below\n\t\t\tboolean dayOfMSpec = !dom.contains(NO_SPEC);\n\t\t\tboolean dayOfWSpec = !dow.contains(NO_SPEC);\n\n\t\t\tif (!dayOfMSpec || dayOfWSpec) {\n\t\t\t\tif (!dayOfWSpec || dayOfMSpec) {\n\t\t\t\t\tthrow new ParseException(\n\t\t\t\t\t\t\t\"Support for specifying both a day-of-week AND a day-of-month parameter is not implemented.\", 0);\n\t\t\t\t}\n\t\t\t}\n\t\t} catch (ParseException pe) {\n\t\t\tthrow pe;\n\t\t} catch (Exception e) {\n\t\t\tthrow new ParseException(\"Illegal cron expression format (\"\n\t\t\t\t\t+ e.toString() + \")\", 0);\n\t\t}\n\t}\n\n\tprivate void checkIncrementRange(int incr, int type, int idxPos) throws ParseException {\n\t\tif (incr > 59 && (type == SECOND || type == MINUTE)) {\n\t\t\tthrow new ParseException(\"Increment > 60 : \" + incr, idxPos);\n\t\t} else if (incr > 23 && (type == HOUR)) {\n\t\t\tthrow new ParseException(\"Increment > 24 : \" + incr, idxPos);\n\t\t} else if (incr > 31 && (type == DAY_OF_MONTH)) {\n\t\t\tthrow new ParseException(\"Increment > 31 : \" + incr, idxPos);\n\t\t} else if (incr > 7 && (type == DAY_OF_WEEK)) {\n\t\t\tthrow new ParseException(\"Increment > 7 : \" + incr, idxPos);\n\t\t} else if (incr > 12 && (type == MONTH)) {\n\t\t\tthrow new ParseException(\"Increment > 12 : \" + incr, idxPos);\n\t\t}\n\t}\n\n\tprotected int checkNext(int pos, String s, int val, int type)\n\t\t\tthrows ParseException {\n\n\t\tint end = -1;\n\t\tint i = pos;\n\n\t\tif (i >= s.length()) {\n\t\t\taddToSet(val, end, -1, type);\n\t\t\treturn i;\n\t\t}\n\n\t\tchar c = s.charAt(pos);\n\n\t\tif (c == 'L') {\n\t\t\tif (type == DAY_OF_WEEK) {\n\t\t\t\tif(val < 1 || val > 7)\n\t\t\t\t\tthrow new ParseException(\"Day-of-Week values must be between 1 and 7\", -1);\n\t\t\t} else {\n\t\t\t\tthrow new ParseException(\"'L' option is not valid here. (pos=\" + i + \")\", i);\n\t\t\t}\n\t\t\tTreeSet<Integer> set = getSet(type);\n\t\t\tset.add(val);\n\t\t\ti++;\n\t\t\treturn i;\n\t\t}\n\n\t\tif (c == 'W') {\n\t\t\tif (type != DAY_OF_MONTH) {\n\t\t\t\tthrow new ParseException(\"'W' option is not valid here. (pos=\" + i + \")\", i);\n\t\t\t}\n\t\t\tif(val > 31)\n\t\t\t\tthrow new ParseException(\"The 'W' option does not make sense with values larger than 31 (max number of days in a month)\", i);\n\t\t\tTreeSet<Integer> set = getSet(type);\n\t\t\tset.add(val);\n\t\t\ti++;\n\t\t\treturn i;\n\t\t}\n\n\t\tif (c == '#') {\n\t\t\tif (type != DAY_OF_WEEK) {\n\t\t\t\tthrow new ParseException(\"'#' option is not valid here. (pos=\" + i + \")\", i);\n\t\t\t}\n\t\t\ti++;\n\t\t\ttry {\n\t\t\t\tnthdayOfWeek = Integer.parseInt(s.substring(i));\n\t\t\t\tif (nthdayOfWeek < 1 || nthdayOfWeek > 5) {\n\t\t\t\t\tthrow new Exception();\n\t\t\t\t}\n\t\t\t} catch (Exception e) {\n\t\t\t\tthrow new ParseException(\n\t\t\t\t\t\t\"A numeric value between 1 and 5 must follow the '#' option\",\n\t\t\t\t\t\ti);\n\t\t\t}\n\n\t\t\tTreeSet<Integer> set = getSet(type);\n\t\t\tset.add(val);\n\t\t\ti++;\n\t\t\treturn i;\n\t\t}\n\n\t\tif (c == '-') {\n\t\t\ti++;\n\t\t\tc = s.charAt(i);\n\t\t\tint v = Integer.parseInt(String.valueOf(c));\n\t\t\tend = v;\n\t\t\ti++;\n\t\t\tif (i >= s.length()) {\n\t\t\t\taddToSet(val, end, 1, type);\n\t\t\t\treturn i;\n\t\t\t}\n\t\t\tc = s.charAt(i);\n\t\t\tif (c >= '0' && c <= '9') {\n\t\t\t\tValueSet vs = getValue(v, s, i);\n\t\t\t\tend = vs.value;\n\t\t\t\ti = vs.pos;\n\t\t\t}\n\t\t\tif (i < s.length() && ((c = s.charAt(i)) == '/')) {\n\t\t\t\ti++;\n\t\t\t\tc = s.charAt(i);\n\t\t\t\tint v2 = Integer.parseInt(String.valueOf(c));\n\t\t\t\ti++;\n\t\t\t\tif (i >= s.length()) {\n\t\t\t\t\taddToSet(val, end, v2, type);\n\t\t\t\t\treturn i;\n\t\t\t\t}\n\t\t\t\tc = s.charAt(i);\n\t\t\t\tif (c >= '0' && c <= '9') {\n\t\t\t\t\tValueSet vs = getValue(v2, s, i);\n\t\t\t\t\tint v3 = vs.value;\n\t\t\t\t\taddToSet(val, end, v3, type);\n\t\t\t\t\ti = vs.pos;\n\t\t\t\t\treturn i;\n\t\t\t\t} else {\n\t\t\t\t\taddToSet(val, end, v2, type);\n\t\t\t\t\treturn i;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\taddToSet(val, end, 1, type);\n\t\t\t\treturn i;\n\t\t\t}\n\t\t}\n\n\t\tif (c == '/') {\n\t\t\tif ((i + 1) >= s.length() || s.charAt(i + 1) == ' ' || s.charAt(i + 1) == '\\t') {\n\t\t\t\tthrow new ParseException(\"'/' must be followed by an integer.\", i);\n\t\t\t}\n\n\t\t\ti++;\n\t\t\tc = s.charAt(i);\n\t\t\tint v2 = Integer.parseInt(String.valueOf(c));\n\t\t\ti++;\n\t\t\tif (i >= s.length()) {\n\t\t\t\tcheckIncrementRange(v2, type, i);\n\t\t\t\taddToSet(val, end, v2, type);\n\t\t\t\treturn i;\n\t\t\t}\n\t\t\tc = s.charAt(i);\n\t\t\tif (c >= '0' && c <= '9') {\n\t\t\t\tValueSet vs = getValue(v2, s, i);\n\t\t\t\tint v3 = vs.value;\n\t\t\t\tcheckIncrementRange(v3, type, i);\n\t\t\t\taddToSet(val, end, v3, type);\n\t\t\t\ti = vs.pos;\n\t\t\t\treturn i;\n\t\t\t} else {\n\t\t\t\tthrow new ParseException(\"Unexpected character '\" + c + \"' after '/'\", i);\n\t\t\t}\n\t\t}\n\n\t\taddToSet(val, end, 0, type);\n\t\ti++;\n\t\treturn i;\n\t}\n\n\n\tprotected int skipWhiteSpace(int i, String s) {\n\t\tfor (; i < s.length() && (s.charAt(i) == ' ' || s.charAt(i) == '\\t'); i++) {\n\t\t\t;\n\t\t}\n\n\t\treturn i;\n\t}\n\n\tprotected int findNextWhiteSpace(int i, String s) {\n\t\tfor (; i < s.length() && (s.charAt(i) != ' ' || s.charAt(i) != '\\t'); i++) {\n\t\t\t;\n\t\t}\n\n\t\treturn i;\n\t}\n\n\tprotected void addToSet(int val, int end, int incr, int type)\n\t\t\tthrows ParseException {\n\n\t\tTreeSet<Integer> set = getSet(type);\n\n\t\tif (type == SECOND || type == MINUTE) {\n\t\t\tif ((val < 0 || val > 59 || end > 59) && (val != ALL_SPEC_INT)) {\n\t\t\t\tthrow new ParseException(\n\t\t\t\t\t\t\"Minute and Second values must be between 0 and 59\",\n\t\t\t\t\t\t-1);\n\t\t\t}\n\t\t} else if (type == HOUR) {\n\t\t\tif ((val < 0 || val > 23 || end > 23) && (val != ALL_SPEC_INT)) {\n\t\t\t\tthrow new ParseException(\n\t\t\t\t\t\t\"Hour values must be between 0 and 23\", -1);\n\t\t\t}\n\t\t} else if (type == DAY_OF_MONTH) {\n\t\t\tif ((val < 1 || val > 31 || end > 31) && (val != ALL_SPEC_INT)\n\t\t\t\t\t&& (val != NO_SPEC_INT)) {\n\t\t\t\tthrow new ParseException(\n\t\t\t\t\t\t\"Day of month values must be between 1 and 31\", -1);\n\t\t\t}\n\t\t} else if (type == MONTH) {\n\t\t\tif ((val < 1 || val > 12 || end > 12) && (val != ALL_SPEC_INT)) {\n\t\t\t\tthrow new ParseException(\n\t\t\t\t\t\t\"Month values must be between 1 and 12\", -1);\n\t\t\t}\n\t\t} else if (type == DAY_OF_WEEK) {\n\t\t\tif ((val == 0 || val > 7 || end > 7) && (val != ALL_SPEC_INT)\n\t\t\t\t\t&& (val != NO_SPEC_INT)) {\n\t\t\t\tthrow new ParseException(\n\t\t\t\t\t\t\"Day-of-Week values must be between 1 and 7\", -1);\n\t\t\t}\n\t\t}\n\n\t\tif ((incr == 0 || incr == -1) && val != ALL_SPEC_INT) {\n\t\t\tif (val != -1) {\n\t\t\t\tset.add(val);\n\t\t\t} else {\n\t\t\t\tset.add(NO_SPEC);\n\t\t\t}\n\n\t\t\treturn;\n\t\t}\n\n\t\tint startAt = val;\n\t\tint stopAt = end;\n\n\t\tif (val == ALL_SPEC_INT && incr <= 0) {\n\t\t\tincr = 1;\n\t\t\tset.add(ALL_SPEC); // put in a marker, but also fill values\n\t\t}\n\n\t\tif (type == SECOND || type == MINUTE) {\n\t\t\tif (stopAt == -1) {\n\t\t\t\tstopAt = 59;\n\t\t\t}\n\t\t\tif (startAt == -1 || startAt == ALL_SPEC_INT) {\n\t\t\t\tstartAt = 0;\n\t\t\t}\n\t\t} else if (type == HOUR) {\n\t\t\tif (stopAt == -1) {\n\t\t\t\tstopAt = 23;\n\t\t\t}\n\t\t\tif (startAt == -1 || startAt == ALL_SPEC_INT) {\n\t\t\t\tstartAt = 0;\n\t\t\t}\n\t\t} else if (type == DAY_OF_MONTH) {\n\t\t\tif (stopAt == -1) {\n\t\t\t\tstopAt = 31;\n\t\t\t}\n\t\t\tif (startAt == -1 || startAt == ALL_SPEC_INT) {\n\t\t\t\tstartAt = 1;\n\t\t\t}\n\t\t} else if (type == MONTH) {\n\t\t\tif (stopAt == -1) {\n\t\t\t\tstopAt = 12;\n\t\t\t}\n\t\t\tif (startAt == -1 || startAt == ALL_SPEC_INT) {\n\t\t\t\tstartAt = 1;\n\t\t\t}\n\t\t} else if (type == DAY_OF_WEEK) {\n\t\t\tif (stopAt == -1) {\n\t\t\t\tstopAt = 7;\n\t\t\t}\n\t\t\tif (startAt == -1 || startAt == ALL_SPEC_INT) {\n\t\t\t\tstartAt = 1;\n\t\t\t}\n\t\t} else if (type == YEAR) {\n\t\t\tif (stopAt == -1) {\n\t\t\t\tstopAt = MAX_YEAR;\n\t\t\t}\n\t\t\tif (startAt == -1 || startAt == ALL_SPEC_INT) {\n\t\t\t\tstartAt = 1970;\n\t\t\t}\n\t\t}\n\n\t\t// if the end of the range is before the start, then we need to overflow into\n\t\t// the next day, month etc. This is done by adding the maximum amount for that\n\t\t// type, and using modulus max to determine the value being added.\n\t\tint max = -1;\n\t\tif (stopAt < startAt) {\n\t\t\tswitch (type) {\n\t\t\t\tcase SECOND:\n\t\t\t\t\tmax = 60;\n\t\t\t\t\tbreak;\n\t\t\t\tcase MINUTE:\n\t\t\t\t\tmax = 60;\n\t\t\t\t\tbreak;\n\t\t\t\tcase HOUR:\n\t\t\t\t\tmax = 24;\n\t\t\t\t\tbreak;\n\t\t\t\tcase MONTH:\n\t\t\t\t\tmax = 12;\n\t\t\t\t\tbreak;\n\t\t\t\tcase DAY_OF_WEEK:\n\t\t\t\t\tmax = 7;\n\t\t\t\t\tbreak;\n\t\t\t\tcase DAY_OF_MONTH:\n\t\t\t\t\tmax = 31;\n\t\t\t\t\tbreak;\n\t\t\t\tcase YEAR:\n\t\t\t\t\tthrow new IllegalArgumentException(\"Start year must be less than stop year\");\n\t\t\t\tdefault:\n\t\t\t\t\tthrow new IllegalArgumentException(\"Unexpected type encountered\");\n\t\t\t}\n\t\t\tstopAt += max;\n\t\t}\n\n\t\tfor (int i = startAt; i <= stopAt; i += incr) {\n\t\t\tif (max == -1) {\n\t\t\t\t// ie: there's no max to overflow over\n\t\t\t\tset.add(i);\n\t\t\t} else {\n\t\t\t\t// take the modulus to get the real value\n\t\t\t\tint i2 = i % max;\n\n\t\t\t\t// 1-indexed ranges should not include 0, and should include their max\n\t\t\t\tif (i2 == 0 && (type == MONTH || type == DAY_OF_WEEK || type == DAY_OF_MONTH) ) {\n\t\t\t\t\ti2 = max;\n\t\t\t\t}\n\n\t\t\t\tset.add(i2);\n\t\t\t}\n\t\t}\n\t}\n\n\tTreeSet<Integer> getSet(int type) {\n\t\tswitch (type) {\n\t\t\tcase SECOND:\n\t\t\t\treturn seconds;\n\t\t\tcase MINUTE:\n\t\t\t\treturn minutes;\n\t\t\tcase HOUR:\n\t\t\t\treturn hours;\n\t\t\tcase DAY_OF_MONTH:\n\t\t\t\treturn daysOfMonth;\n\t\t\tcase MONTH:\n\t\t\t\treturn months;\n\t\t\tcase DAY_OF_WEEK:\n\t\t\t\treturn daysOfWeek;\n\t\t\tcase YEAR:\n\t\t\t\treturn years;\n\t\t\tdefault:\n\t\t\t\treturn null;\n\t\t}\n\t}\n\n\tprotected ValueSet getValue(int v, String s, int i) {\n\t\tchar c = s.charAt(i);\n\t\tStringBuilder s1 = new StringBuilder(String.valueOf(v));\n\t\twhile (c >= '0' && c <= '9') {\n\t\t\ts1.append(c);\n\t\t\ti++;\n\t\t\tif (i >= s.length()) {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tc = s.charAt(i);\n\t\t}\n\t\tValueSet val = new ValueSet();\n\n\t\tval.pos = (i < s.length()) ? i : i + 1;\n\t\tval.value = Integer.parseInt(s1.toString());\n\t\treturn val;\n\t}\n\n\tprotected int getNumericValue(String s, int i) {\n\t\tint endOfVal = findNextWhiteSpace(i, s);\n\t\tString val = s.substring(i, endOfVal);\n\t\treturn Integer.parseInt(val);\n\t}\n\n\tprotected int getMonthNumber(String s) {\n\t\tInteger integer = monthMap.get(s);\n\n\t\tif (integer == null) {\n\t\t\treturn -1;\n\t\t}\n\n\t\treturn integer;\n\t}\n\n\tprotected int getDayOfWeekNumber(String s) {\n\t\tInteger integer = dayMap.get(s);\n\n\t\tif (integer == null) {\n\t\t\treturn -1;\n\t\t}\n\n\t\treturn integer;\n\t}\n\n\n\tprotected int storeExpressionVals(int pos, String s, int type)\n\t\t\tthrows ParseException {\n\n\t\tint incr = 0;\n\t\tint i = skipWhiteSpace(pos, s);\n\t\tif (i >= s.length()) {\n\t\t\treturn i;\n\t\t}\n\t\tchar c = s.charAt(i);\n\t\tif ((c >= 'A') && (c <= 'Z') && (!s.equals(\"L\")) && (!s.equals(\"LW\")) && (!s.matches(\"^L-[0-9]*[W]?\"))) {\n\t\t\tString sub = s.substring(i, i + 3);\n\t\t\tint sval = -1;\n\t\t\tint eval = -1;\n\t\t\tif (type == MONTH) {\n\t\t\t\tsval = getMonthNumber(sub) + 1;\n\t\t\t\tif (sval <= 0) {\n\t\t\t\t\tthrow new ParseException(\"Invalid Month value: '\" + sub + \"'\", i);\n\t\t\t\t}\n\t\t\t\tif (s.length() > i + 3) {\n\t\t\t\t\tc = s.charAt(i + 3);\n\t\t\t\t\tif (c == '-') {\n\t\t\t\t\t\ti += 4;\n\t\t\t\t\t\tsub = s.substring(i, i + 3);\n\t\t\t\t\t\teval = getMonthNumber(sub) + 1;\n\t\t\t\t\t\tif (eval <= 0) {\n\t\t\t\t\t\t\tthrow new ParseException(\"Invalid Month value: '\" + sub + \"'\", i);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else if (type == DAY_OF_WEEK) {\n\t\t\t\tsval = getDayOfWeekNumber(sub);\n\t\t\t\tif (sval < 0) {\n\t\t\t\t\tthrow new ParseException(\"Invalid Day-of-Week value: '\"\n\t\t\t\t\t\t\t+ sub + \"'\", i);\n\t\t\t\t}\n\t\t\t\tif (s.length() > i + 3) {\n\t\t\t\t\tc = s.charAt(i + 3);\n\t\t\t\t\tif (c == '-') {\n\t\t\t\t\t\ti += 4;\n\t\t\t\t\t\tsub = s.substring(i, i + 3);\n\t\t\t\t\t\teval = getDayOfWeekNumber(sub);\n\t\t\t\t\t\tif (eval < 0) {\n\t\t\t\t\t\t\tthrow new ParseException(\n\t\t\t\t\t\t\t\t\t\"Invalid Day-of-Week value: '\" + sub\n\t\t\t\t\t\t\t\t\t\t\t+ \"'\", i);\n\t\t\t\t\t\t}\n\t\t\t\t\t} else if (c == '#') {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\ti += 4;\n\t\t\t\t\t\t\tnthdayOfWeek = Integer.parseInt(s.substring(i));\n\t\t\t\t\t\t\tif (nthdayOfWeek < 1 || nthdayOfWeek > 5) {\n\t\t\t\t\t\t\t\tthrow new Exception();\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} catch (Exception e) {\n\t\t\t\t\t\t\tthrow new ParseException(\n\t\t\t\t\t\t\t\t\t\"A numeric value between 1 and 5 must follow the '#' option\",\n\t\t\t\t\t\t\t\t\ti);\n\t\t\t\t\t\t}\n\t\t\t\t\t} else if (c == 'L') {\n\t\t\t\t\t\ti++;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t} else {\n\t\t\t\tthrow new ParseException(\n\t\t\t\t\t\t\"Illegal characters for this position: '\" + sub + \"'\",\n\t\t\t\t\t\ti);\n\t\t\t}\n\t\t\tif (eval != -1) {\n\t\t\t\tincr = 1;\n\t\t\t}\n\t\t\taddToSet(sval, eval, incr, type);\n\t\t\treturn (i + 3);\n\t\t}\n\n\t\tif (c == '?') {\n\t\t\ti++;\n\t\t\tif ((i + 1) < s.length()\n\t\t\t\t\t&& (s.charAt(i) != ' ' && s.charAt(i + 1) != '\\t')) {\n\t\t\t\tthrow new ParseException(\"Illegal character after '?': \"\n\t\t\t\t\t\t+ s.charAt(i), i);\n\t\t\t}\n\t\t\tif (type != DAY_OF_WEEK && type != DAY_OF_MONTH) {\n\t\t\t\tthrow new ParseException(\n\t\t\t\t\t\t\"'?' can only be specified for Day-of-Month or Day-of-Week.\",\n\t\t\t\t\t\ti);\n\t\t\t}\n\t\t\tif (type == DAY_OF_WEEK && !lastdayOfMonth) {\n\t\t\t\tint val = daysOfMonth.last();\n\t\t\t\tif (val == NO_SPEC_INT) {\n\t\t\t\t\tthrow new ParseException(\n\t\t\t\t\t\t\t\"'?' can only be specified for Day-of-Month -OR- Day-of-Week.\",\n\t\t\t\t\t\t\ti);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\taddToSet(NO_SPEC_INT, -1, 0, type);\n\t\t\treturn i;\n\t\t}\n\n\t\tif (c == '*' || c == '/') {\n\t\t\tif (c == '*' && (i + 1) >= s.length()) {\n\t\t\t\taddToSet(ALL_SPEC_INT, -1, incr, type);\n\t\t\t\treturn i + 1;\n\t\t\t} else if (c == '/'\n\t\t\t\t\t&& ((i + 1) >= s.length() || s.charAt(i + 1) == ' ' || s\n\t\t\t\t\t.charAt(i + 1) == '\\t')) {\n\t\t\t\tthrow new ParseException(\"'/' must be followed by an integer.\", i);\n\t\t\t} else if (c == '*') {\n\t\t\t\ti++;\n\t\t\t}\n\t\t\tc = s.charAt(i);\n\t\t\tif (c == '/') { // is an increment specified?\n\t\t\t\ti++;\n\t\t\t\tif (i >= s.length()) {\n\t\t\t\t\tthrow new ParseException(\"Unexpected end of string.\", i);\n\t\t\t\t}\n\n\t\t\t\tincr = getNumericValue(s, i);\n\n\t\t\t\ti++;\n\t\t\t\tif (incr > 10) {\n\t\t\t\t\ti++;\n\t\t\t\t}\n\t\t\t\tcheckIncrementRange(incr, type, i);\n\t\t\t} else {\n\t\t\t\tincr = 1;\n\t\t\t}\n\n\t\t\taddToSet(ALL_SPEC_INT, -1, incr, type);\n\t\t\treturn i;\n\t\t} else if (c == 'L') {\n\t\t\ti++;\n\t\t\tif (type == DAY_OF_MONTH) {\n\t\t\t\tlastdayOfMonth = true;\n\t\t\t}\n\t\t\tif (type == DAY_OF_WEEK) {\n\t\t\t\taddToSet(7, 7, 0, type);\n\t\t\t}\n\t\t\tif(type == DAY_OF_MONTH && s.length() > i) {\n\t\t\t\tc = s.charAt(i);\n\t\t\t\tif(c == '-') {\n\t\t\t\t\tValueSet vs = getValue(0, s, i+1);\n\t\t\t\t\tlastdayOffset = vs.value;\n\t\t\t\t\tif(lastdayOffset > 30)\n\t\t\t\t\t\tthrow new ParseException(\"Offset from last day must be <= 30\", i+1);\n\t\t\t\t\ti = vs.pos;\n\t\t\t\t}\n\t\t\t\tif(s.length() > i) {\n\t\t\t\t\tc = s.charAt(i);\n\t\t\t\t\tif(c == 'W') {\n\t\t\t\t\t\ti++;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn i;\n\t\t} else if (c >= '0' && c <= '9') {\n\t\t\tint val = Integer.parseInt(String.valueOf(c));\n\t\t\ti++;\n\t\t\tif (i >= s.length()) {\n\t\t\t\taddToSet(val, -1, -1, type);\n\t\t\t} else {\n\t\t\t\tc = s.charAt(i);\n\t\t\t\tif (c >= '0' && c <= '9') {\n\t\t\t\t\tValueSet vs = getValue(val, s, i);\n\t\t\t\t\tval = vs.value;\n\t\t\t\t\ti = vs.pos;\n\t\t\t\t}\n\t\t\t\ti = checkNext(i, s, val, type);\n\t\t\t\treturn i;\n\t\t\t}\n\t\t} else {\n\t\t\tthrow new ParseException(\"Unexpected character: \" + c, i);\n\t\t}\n\n\t\treturn i;\n\t}\n\n\tpublic static class ValueSet {\n\t\tpublic int value;\n\n\t\tpublic int pos;\n\t}\n}\n\n\n\n"
  },
  {
    "path": "spring-cloud-deployer-cloudfoundry/src/main/resources/META-INF/additional-spring-configuration-metadata.json",
    "content": "{\n  \"hints\": [\n    {\n      \"name\": \"spring.cloud.deployer.cloudfoundry.health-check\",\n      \"values\": [\n        {\n          \"value\": \"NONE\"\n        },\n        {\n          \"value\": \"HTTP\"\n        },\n        {\n          \"value\": \"PROCESS\"\n        },\n        {\n          \"value\": \"PORT\"\n        }\n      ]\n    }\n  ]\n}\n"
  },
  {
    "path": "spring-cloud-deployer-cloudfoundry/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports",
    "content": "org.springframework.cloud.deployer.spi.cloudfoundry.CloudFoundryDeployerAutoConfiguration\n"
  },
  {
    "path": "spring-cloud-deployer-cloudfoundry/src/main/resources/META-INF/spring.factories",
    "content": "org.springframework.boot.autoconfigure.EnableAutoConfiguration=\\\n  org.springframework.cloud.deployer.spi.cloudfoundry.CloudFoundryDeployerAutoConfiguration\n"
  },
  {
    "path": "spring-cloud-deployer-cloudfoundry/src/test/java/org/springframework/cloud/deployer/spi/cloudfoundry/AbstractAppDeployerTestSupport.java",
    "content": "/*\n * Copyright 2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.cloudfoundry;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport org.cloudfoundry.logcache.v1.LogCacheClient;\nimport org.cloudfoundry.operations.CloudFoundryOperations;\nimport org.cloudfoundry.operations.applications.ApplicationDetail;\nimport org.cloudfoundry.operations.applications.Applications;\nimport org.cloudfoundry.operations.applications.DeleteApplicationRequest;\nimport org.cloudfoundry.operations.applications.GetApplicationRequest;\nimport org.cloudfoundry.operations.applications.PushApplicationManifestRequest;\nimport org.cloudfoundry.operations.applications.ScaleApplicationRequest;\nimport org.cloudfoundry.operations.services.Services;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.mockito.Answers;\nimport org.mockito.Mock;\nimport org.mockito.MockitoAnnotations;\nimport reactor.core.publisher.Mono;\n\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.boot.test.mock.mockito.MockBean;\nimport org.springframework.cloud.deployer.spi.core.RuntimeEnvironmentInfo;\nimport org.springframework.context.ApplicationContext;\n\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.BDDMockito.given;\n\n/**\n * @author David Turanski\n */\npublic abstract class AbstractAppDeployerTestSupport {\n\tprotected final CloudFoundryDeploymentProperties deploymentProperties = new CloudFoundryDeploymentProperties();\n\n\t@Mock(answer = Answers.RETURNS_SMART_NULLS)\n\tprotected AppNameGenerator applicationNameGenerator;\n\n\t@Mock(answer = Answers.RETURNS_SMART_NULLS)\n\tprotected Applications applications;\n\n\tprotected CloudFoundryAppDeployer deployer;\n\n\t@Mock(answer = Answers.RETURNS_SMART_NULLS)\n\t@MockBean\n\tprotected CloudFoundryOperations operations;\n\n\t@Mock(answer = Answers.RETURNS_SMART_NULLS)\n\tprotected Services services;\n\n\t@Mock(answer = Answers.RETURNS_SMART_NULLS)\n\tprotected RuntimeEnvironmentInfo runtimeEnvironmentInfo;\n\n\t@Mock(answer = Answers.RETURNS_SMART_NULLS)\n\t@MockBean\n\tprotected LogCacheClient reactorLogCacheClient;\n\t@Autowired\n\tprotected ApplicationContext applicationContext;\n\n\t@Mock\n\tprotected ApplicationLogAccessor applicationLogAccessor;\n\n\t@BeforeEach\n\tpublic void setUp() {\n\t\tMockitoAnnotations.openMocks(this);\n\t\tgiven(this.operations.applications()).willReturn(this.applications);\n\t\tgiven(this.operations.services()).willReturn(this.services);\n\t\tthis.deployer = new CloudFoundryAppDeployer(this.applicationNameGenerator, this.deploymentProperties,\n\t\t\t\tthis.operations, this.runtimeEnvironmentInfo, this.applicationLogAccessor);\n\t\tpostSetUp();\n\t}\n\n\tprotected abstract void postSetUp();\n\n\tprotected void givenRequestScaleApplication(String id, Integer count, int memoryLimit, int diskLimit,\n\t\t\tMono<Void> response) {\n\t\tgiven(this.operations.applications()\n\t\t\t\t.scale(ScaleApplicationRequest.builder().name(id).instances(count).memoryLimit(memoryLimit)\n\t\t\t\t\t\t.diskLimit(diskLimit)\n\t\t\t\t\t\t.startupTimeout(this.deploymentProperties.getStartupTimeout())\n\t\t\t\t\t\t.stagingTimeout(this.deploymentProperties.getStagingTimeout()).build())).willReturn(response);\n\t}\n\n\tprotected void givenRequestDeleteApplication(String id, Mono<Void> response) {\n\t\tgiven(this.operations.applications()\n\t\t\t\t.delete(DeleteApplicationRequest.builder().deleteRoutes(true).name(id).build())).willReturn(response);\n\t}\n\n\t@SuppressWarnings(\"unchecked\")\n\tprotected void givenRequestGetApplication(String id, Mono<ApplicationDetail> response,\n\t\t\tMono<ApplicationDetail>... responses) {\n\t\tgiven(this.operations.applications().get(GetApplicationRequest.builder().name(id).build())).willReturn(response,\n\t\t\t\tresponses);\n\t}\n\n\tprotected void givenRequestPushApplication(PushApplicationManifestRequest request, Mono<Void> response) {\n\t\tgiven(this.operations.applications()\n\t\t\t\t.pushManifest(any(PushApplicationManifestRequest.class)))\n\t\t\t\t.willReturn(response);\n\t}\n\n\tprotected Map<String, String> defaultEnvironmentVariables() {\n\t\tMap<String, String> environmentVariables = new HashMap<>();\n\t\tenvironmentVariables.put(\"SPRING_APPLICATION_JSON\", \"{}\");\n\t\taddGuidAndIndex(environmentVariables);\n\t\treturn environmentVariables;\n\t}\n\n\tprotected void addGuidAndIndex(Map<String, String> environmentVariables) {\n\t\tenvironmentVariables.put(\"SPRING_APPLICATION_INDEX\", \"${vcap.application.instance_index}\");\n\t\tenvironmentVariables.put(\"SPRING_CLOUD_APPLICATION_GUID\",\n\t\t\t\t\"${vcap.application.name}:${vcap.application.instance_index}\");\n\t}\n}\n"
  },
  {
    "path": "spring-cloud-deployer-cloudfoundry/src/test/java/org/springframework/cloud/deployer/spi/cloudfoundry/ApplicationLogAccessorTests.java",
    "content": "/*\n * Copyright 2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.cloudfoundry;\n\nimport org.cloudfoundry.logcache.v1.Envelope;\nimport org.cloudfoundry.logcache.v1.EnvelopeBatch;\nimport org.cloudfoundry.logcache.v1.Log;\nimport org.cloudfoundry.logcache.v1.LogCacheClient;\nimport org.cloudfoundry.logcache.v1.ReadResponse;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.mockito.Mock;\nimport org.mockito.junit.jupiter.MockitoExtension;\nimport reactor.core.publisher.Mono;\n\nimport java.time.Duration;\nimport java.util.Base64;\n\nimport static org.assertj.core.api.AssertionsForClassTypes.assertThat;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.Mockito.when;\n\n@ExtendWith(MockitoExtension.class)\npublic class ApplicationLogAccessorTests {\n\n    @Mock\n    private LogCacheClient logCacheClient;\n\n    @Test\n    void testDefaultCase() {\n        String sampleData = \"foo\\nbar\\nbaz\\nboo\";\n        String exectedResult = \"boo\\nbaz\\nbar\\nfoo\";\n        when(logCacheClient.read(any())).thenReturn(getSampleResponse(sampleData));\n        ApplicationLogAccessor applicationLogAccessor = new ApplicationLogAccessor(this.logCacheClient);\n        assertThat(applicationLogAccessor.getLog(\"myDeploymentId\", Duration.ofSeconds(5))).isEqualTo(exectedResult);\n    }\n\n    private Mono<ReadResponse> getSampleResponse(String sampleData) {\n        Envelope envelope = Envelope.builder().log(getSampleLog(sampleData)).build();\n        EnvelopeBatch envelopeBatch = EnvelopeBatch.builder().batch(envelope).build();\n        return Mono.just(ReadResponse.builder().envelopes(envelopeBatch).build());\n    }\n\n    private Log getSampleLog(String sampleData) {\n        return Log.builder().payload(Base64.getEncoder().encodeToString(sampleData.getBytes())).build();\n    }\n}\n"
  },
  {
    "path": "spring-cloud-deployer-cloudfoundry/src/test/java/org/springframework/cloud/deployer/spi/cloudfoundry/CfEnvAwareResourceTests.java",
    "content": "/*\n * Copyright 2020-2024 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.cloudfoundry;\n\n\nimport java.io.IOException;\nimport java.net.URI;\nimport java.net.URISyntaxException;\n\nimport org.junit.jupiter.api.Test;\n\nimport org.springframework.core.io.ClassPathResource;\nimport org.springframework.core.io.Resource;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\n/**\n * @author David Turanski\n */\npublic class CfEnvAwareResourceTests {\n\n\t@Test\n\tpublic void testCfEnvResolverWithCfEnvJava17() throws IOException {\n\t\t// this task app is compiled from a spring-cloud-task sample using jdk17\n\t\t// and cfenv added to build\n\t\tCfEnvAwareResource resource = CfEnvAwareResource.of(new ClassPathResource(\"timestamp-task-3.1.2-SNAPSHOT.jar\"));\n\t\tassertThat(resource.hasCfEnv()).isTrue();\n\t}\n\n\t@Test\n\tpublic void testCfEnvResolverWithCfEnv() throws IOException {\n\t\tCfEnvAwareResource resource = CfEnvAwareResource.of(new ClassPathResource(\"log-sink-rabbit-3.0.0.BUILD-SNAPSHOT.jar\"));\n\t\tassertThat(resource.hasCfEnv()).isTrue();\n\t}\n\n\t@Test\n\tpublic void testCfEnvResolverWithNoCfEnv() throws IOException {\n\t\tCfEnvAwareResource resource = CfEnvAwareResource.of(new ClassPathResource(\"batch-job-1.0.0.BUILD-SNAPSHOT.jar\"));\n\t\tassertThat(resource.hasCfEnv()).isFalse();\n\t}\n\n\t@Test\n\tpublic void testDoesNothingWithDocker() throws IOException, URISyntaxException {\n\t\tResource docker = mock(Resource.class);\n\t\twhen(docker.getURI()).thenReturn(new URI(\"docker://fake/news\"));\n\t\tassertThat(CfEnvAwareResource.of(docker).hasCfEnv()).isFalse();\n\t}\n}\n"
  },
  {
    "path": "spring-cloud-deployer-cloudfoundry/src/test/java/org/springframework/cloud/deployer/spi/cloudfoundry/CfEnvConfigurerTests.java",
    "content": "/*\n * Copyright 2020-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.cloudfoundry;\n\nimport java.util.Collections;\nimport java.util.Map;\n\nimport org.cloudfoundry.util.FluentMap;\nimport org.junit.jupiter.api.Test;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\npublic class CfEnvConfigurerTests {\n\n\t@Test\n\tpublic void testCfEnvConfigurerActivateCloudProfile() {\n\t\tMap<String, String> env = CfEnvConfigurer.activateCloudProfile(\n\t\t\t\tCollections.emptyMap(), CfEnvConfigurer.SPRING_PROFILES_ACTIVE_FQN);\n\t\tassertThat(env).containsEntry(CfEnvConfigurer.SPRING_PROFILES_ACTIVE_FQN,\n\t\t\t\tCfEnvConfigurer.CLOUD_PROFILE_NAME);\n\t\tassertThat(env).doesNotContainKeys(\n\t\t\t\tCfEnvConfigurer.SPRING_PROFILES_ACTIVE,\n\t\t\t\tCfEnvConfigurer.SPRING_PROFILES_ACTIVE_HYPHENATED);\n\t}\n\n\t@Test\n\tpublic void testCfEnvConfigurerDoNotActivateCloudProfileIfNoneExists() {\n\t\tMap<String, String> env = CfEnvConfigurer.activateCloudProfile(\n\t\t\t\tCollections.emptyMap(), null);\n\t\tassertThat(env).doesNotContainKeys(CfEnvConfigurer.SPRING_PROFILES_ACTIVE_FQN,\n\t\t\t\tCfEnvConfigurer.SPRING_PROFILES_ACTIVE,\n\t\t\t\tCfEnvConfigurer.SPRING_PROFILES_ACTIVE_HYPHENATED);\n\t}\n\n\t@Test\n\tpublic void testCfEnvConfigurerActivateCloudProfileWithEmptyValue() {\n\t\tMap<String, String> env = CfEnvConfigurer.activateCloudProfile(\n\t\t\t\tFluentMap.<String, String>builder()\n\t\t\t\t\t\t.entry(CfEnvConfigurer.SPRING_PROFILES_ACTIVE, \" \")\n\t\t\t\t\t\t.build(),\n\t\t\t\tCfEnvConfigurer.SPRING_PROFILES_ACTIVE);\n\t\tassertThat(env).containsEntry(CfEnvConfigurer.SPRING_PROFILES_ACTIVE,\n\t\t\t\tCfEnvConfigurer.CLOUD_PROFILE_NAME);\n\t\tassertThat(env).doesNotContainKeys(CfEnvConfigurer.SPRING_PROFILES_ACTIVE_FQN,\n\t\t\t\tCfEnvConfigurer.SPRING_PROFILES_ACTIVE_HYPHENATED);\n\t}\n\n\t@Test\n\tpublic void testCfEnvConfigurerAppendToActiveProfiles() {\n\t\tMap<String, String> env = CfEnvConfigurer.activateCloudProfile(\n\t\t\t\tFluentMap.<String, String>builder()\n\t\t\t\t\t\t.entry(CfEnvConfigurer.SPRING_PROFILES_ACTIVE_FQN, \"foo,bar\")\n\t\t\t\t\t\t.build(), null);\n\t\tassertThat(env).containsEntry(CfEnvConfigurer.SPRING_PROFILES_ACTIVE_FQN, \"foo,bar,cloud\");\n\t\tassertThat(env).doesNotContainKeys(CfEnvConfigurer.SPRING_PROFILES_ACTIVE,\n\t\t\t\tCfEnvConfigurer.SPRING_PROFILES_ACTIVE_HYPHENATED);\n\t}\n\n\t@Test\n\tpublic void testCfEnvConfigurerDoesNotAppendCloudProfileIfAlreadyThere() {\n\t\tMap<String, String> env = CfEnvConfigurer.activateCloudProfile(\n\t\t\t\tFluentMap.<String, String>builder()\n\t\t\t\t\t\t.entry(CfEnvConfigurer.SPRING_PROFILES_ACTIVE_FQN, \"foo,cloud\")\n\t\t\t\t\t\t.build(), null);\n\t\tassertThat(env).containsEntry(CfEnvConfigurer.SPRING_PROFILES_ACTIVE_FQN, \"foo,cloud\");\n\n\t\tenv = CfEnvConfigurer.activateCloudProfile(\n\t\t\t\tFluentMap.<String, String>builder()\n\t\t\t\t\t\t.entry(CfEnvConfigurer.SPRING_PROFILES_ACTIVE_FQN, \"cloud,foo,bar\")\n\t\t\t\t\t\t.build(), null);\n\t\tassertThat(env).containsEntry(CfEnvConfigurer.SPRING_PROFILES_ACTIVE_FQN, \"cloud,foo,bar\");\n\t}\n\n\t@Test\n\tpublic void testCloudProfileAppendedToCommandLineArgs() {\n\t\tassertThat(CfEnvConfigurer.appendCloudProfileToSpringProfilesActiveArg(\"--spring-profiles-active=foo,bar\"))\n\t\t\t\t.isEqualTo(\"--spring-profiles-active=foo,bar,cloud\");\n\t\tassertThat(CfEnvConfigurer.appendCloudProfileToSpringProfilesActiveArg(\"--spring.profiles.active=foo\"))\n\t\t\t\t.isEqualTo(\"--spring.profiles.active=foo,cloud\");\n\t\tassertThat(CfEnvConfigurer.appendCloudProfileToSpringProfilesActiveArg(\"--spring.profiles.active=foo,cloud\"))\n\t\t\t\t.isEqualTo(\"--spring.profiles.active=foo,cloud\");\n\t\tassertThat(CfEnvConfigurer.appendCloudProfileToSpringProfilesActiveArg(\"--spring.profiles.active=cloud\"))\n\t\t\t\t.isEqualTo(\"--spring.profiles.active=cloud\");\n\t\tassertThat(CfEnvConfigurer.appendCloudProfileToSpringProfilesActiveArg(\"--baz=foo\"))\n\t\t\t\t.isEqualTo(\"--baz=foo\");\n\t}\n}\n"
  },
  {
    "path": "spring-cloud-deployer-cloudfoundry/src/test/java/org/springframework/cloud/deployer/spi/cloudfoundry/CloudFoundryActuatorTemplateTests.java",
    "content": "/*\n * Copyright 2022-2025 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.cloudfoundry;\n\nimport java.io.IOException;\nimport java.net.ServerSocket;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Map;\n\nimport org.cloudfoundry.operations.applications.ApplicationDetail;\nimport org.cloudfoundry.operations.applications.InstanceDetail;\nimport org.junit.jupiter.api.Test;\nimport org.mockito.Mock;\nimport org.springframework.cloud.deployer.spi.app.ActuatorOperations;\nimport org.springframework.cloud.deployer.spi.app.AppAdmin;\nimport org.springframework.http.HttpEntity;\nimport org.springframework.http.HttpMethod;\nimport org.springframework.http.HttpStatus;\nimport org.springframework.http.ResponseEntity;\nimport org.springframework.util.LinkedMultiValueMap;\nimport org.springframework.util.MultiValueMap;\nimport org.springframework.web.client.RestTemplate;\nimport reactor.core.publisher.Mono;\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.mockito.Mockito.when;\n\npublic class CloudFoundryActuatorTemplateTests extends AbstractAppDeployerTestSupport {\n\n\t@Mock\n\tprivate RestTemplate restTemplate;\n\n\tprivate ActuatorOperations actuatorOperations;\n\n\t@Override\n\tprotected void postSetUp() {\n\t\tthis.actuatorOperations = new CloudFoundryActuatorTemplate(restTemplate, this.deployer, new AppAdmin());\n\t\tint port = findRandomOpenPort();\n\t\tgivenRequestGetApplication(\"test-application-id\", Mono.just(\n\t\t\tApplicationDetail.builder()\n\t\t\t\t.diskQuota(0)\n\t\t\t\t.id(\"test-application-id\")\n\t\t\t\t.instances(1)\n\t\t\t\t.memoryLimit(0)\n\t\t\t\t.name(\"test-application\")\n\t\t\t\t.requestedState(\"RUNNING\")\n\t\t\t\t.runningInstances(1)\n\t\t\t\t.stack(\"test-stack\")\n\t\t\t\t.urls(\"localhost:\" + port) // No longer dynamic\n\t\t\t\t.instanceDetail(InstanceDetail.builder().state(\"RUNNING\").index(\"1\").build())\n\t\t\t\t.build()\n\t\t));\n\n\t\tMultiValueMap<String, String> header  = new LinkedMultiValueMap<>();\n\t\theader.add(\"X-Cf-App-Instance\", \"test-application-id:0\");\n\t\theader.add(\"Accept\", \"application/json\");\n\t\theader.add(\"Content-Type\", \"application/json\");\n\t\twhen(restTemplate.exchange(getUrl(port) + \"/actuator/info\", HttpMethod.GET,new HttpEntity<>(header), Map.class))\n\t\t\t.thenReturn(new ResponseEntity<>(Collections.singletonMap(\"app\", Collections.singletonMap(\"name\", \"log-sink-rabbit\")), HttpStatus.OK));\n\t\twhen(restTemplate.exchange(getUrl(port) + \"/actuator/bindings\",HttpMethod.GET,new HttpEntity<>(header), List.class))\n\t\t\t.thenReturn(new ResponseEntity<>(Collections.singletonList(Collections.singletonMap(\"bindingName\", \"input\")), HttpStatus.OK));\n\t\twhen(restTemplate.exchange(getUrl(port) + \"/actuator/bindings/input\",HttpMethod.GET,new HttpEntity<>(header), Map.class))\n\t\t\t.thenReturn(new ResponseEntity<>(Collections.singletonMap(\"bindingName\", \"input\"), HttpStatus.OK));\n\t\twhen(restTemplate.exchange(getUrl(port) + \"/actuator/bindings/input\", HttpMethod.POST, new HttpEntity<>(Collections.singletonMap(\"state\", \"STOPPED\"), header), Map.class))\n\t\t\t.thenReturn(new ResponseEntity<>(Collections.singletonMap(\"state\", \"STOPPED\"), HttpStatus.OK));\n\t}\n\n\t@Test\n\tvoid actuatorInfo() {\n\t\tMap<String, Object> info = actuatorOperations\n\t\t\t.getFromActuator(\"test-application-id\", \"test-application:0\", \"/info\", Map.class);\n\t\tassertThat(((Map<?, ?>) info.get(\"app\")).get(\"name\")).isEqualTo(\"log-sink-rabbit\");\n\t}\n\n\t@Test\n\tvoid actuatorBindings() {\n\t\tList<?> bindings = actuatorOperations\n\t\t\t\t.getFromActuator(\"test-application-id\", \"test-application:0\", \"/bindings\", List.class);\n\n\t\tassertThat(((Map<?,?>) (bindings.get(0))).get(\"bindingName\")).isEqualTo(\"input\");\n\t}\n\n\t@Test\n\tvoid actuatorBindingInput() {\n\t\tMap<String, Object> binding = actuatorOperations\n\t\t\t\t.getFromActuator(\"test-application-id\",  \"test-application:0\", \"/bindings/input\", Map.class);\n\t\tassertThat(binding.get(\"bindingName\")).isEqualTo(\"input\");\n\t}\n\n\t@Test\n\tvoid actuatorPostBindingInput() {\n\t\tMap<String, Object> state = actuatorOperations\n\t\t\t\t.postToActuator(\"test-application-id\",  \"test-application:0\", \"/bindings/input\",\n\t\t\t\t\t\tCollections.singletonMap(\"state\", \"STOPPED\"), Map.class);\n\t\tassertThat(state.get(\"state\")).isEqualTo(\"STOPPED\");\n\t}\n\n\tpublic static String getUrl(int port) {\n\t\treturn \"http://localhost:\" + port;\n\t}\n\tpublic static int findRandomOpenPort() {\n\t\ttry (ServerSocket socket = new ServerSocket(0)) {\n\t\t\tsocket.setReuseAddress(true);\n\t\t\treturn socket.getLocalPort();\n\t\t} catch (IOException e) {\n\t\t\tthrow new RuntimeException(\"Failed to find a random open port\", e);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "spring-cloud-deployer-cloudfoundry/src/test/java/org/springframework/cloud/deployer/spi/cloudfoundry/CloudFoundryAppDeployerIntegrationIT.java",
    "content": "/*\n * Copyright 2016-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.cloudfoundry;\n\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Disabled;\nimport org.junit.jupiter.api.Test;\n\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.boot.autoconfigure.EnableAutoConfiguration;\nimport org.springframework.boot.context.properties.EnableConfigurationProperties;\nimport org.springframework.boot.test.context.SpringBootTest;\nimport org.springframework.cloud.deployer.spi.app.AppDeployer;\nimport org.springframework.cloud.deployer.spi.test.AbstractAppDeployerIntegrationJUnit5Tests;\nimport org.springframework.cloud.deployer.spi.test.Timeout;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.test.context.ContextConfiguration;\n\n/**\n * Integration tests for CloudFoundryAppDeployer.\n *\n * @author Eric Bottard\n * @author Greg Turnquist\n */\n\n\n@SpringBootTest(webEnvironment= SpringBootTest.WebEnvironment.NONE,\n\tproperties = {\"spring.cloud.deployer.cloudfoundry.enableRandomAppNamePrefix=false\"})\n@ContextConfiguration(classes = CloudFoundryAppDeployerIntegrationIT.Config.class)\npublic class CloudFoundryAppDeployerIntegrationIT extends AbstractAppDeployerIntegrationJUnit5Tests {\n\n\t@Autowired\n\tprivate AppDeployer appDeployer;\n\n\t@Override\n\tprotected AppDeployer provideAppDeployer() {\n\t\treturn appDeployer;\n\t}\n\n\t/**\n\t * Execution environments may override this default value to have tests wait longer for a deployment, for example if\n\t * running in an environment that is known to be slow.\n\t */\n\tprotected double timeoutMultiplier = 1.0D;\n\n\tprotected int maxRetries = 60;\n\n\t@BeforeEach\n\tpublic void init() {\n\t\tString multiplier = System.getenv(\"CF_DEPLOYER_TIMEOUT_MULTIPLIER\");\n\t\tif (multiplier != null) {\n\t\t\ttimeoutMultiplier = Double.parseDouble(multiplier);\n\t\t}\n\t}\n\n\t@Override\n\t@Test\n\t@Disabled(\"Need to look into args escaping better. Disabling for the time being\")\n\tpublic void testCommandLineArgumentsPassing() {\n\t}\n\n\t@Override\n\tprotected String randomName() {\n\t\t// This will become the hostname part and is limited to 63 chars\n\t\tString name = super.randomName();\n\t\treturn name.substring(0, Math.min(63, name.length()));\n\t}\n\n\t@Override\n\tprotected Timeout deploymentTimeout() {\n\t\treturn new Timeout(maxRetries, (int) (5000 * timeoutMultiplier));\n\t}\n\n\t@Override\n\tprotected Timeout undeploymentTimeout() {\n\t\treturn new Timeout(maxRetries, (int) (5000 * timeoutMultiplier));\n\t}\n\n\t/**\n\t * This triggers the use of {@link CloudFoundryDeployerAutoConfiguration}.\n\t *\n\t * @author Eric Bottard\n\t */\n\t@Configuration\n\t@EnableAutoConfiguration\n\t@EnableConfigurationProperties\n\tpublic static class Config {\n\n\t}\n}\n"
  },
  {
    "path": "spring-cloud-deployer-cloudfoundry/src/test/java/org/springframework/cloud/deployer/spi/cloudfoundry/CloudFoundryAppDeployerTests.java",
    "content": "/*\n * Copyright 2016-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.cloudfoundry;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.net.URI;\nimport java.net.URISyntaxException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.time.Duration;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.Map;\nimport java.util.NoSuchElementException;\nimport java.util.concurrent.CountDownLatch;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.atomic.AtomicInteger;\n\nimport com.fasterxml.jackson.core.JsonProcessingException;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport org.apache.commons.compress.utils.Sets;\nimport org.assertj.core.data.MapEntry;\nimport org.cloudfoundry.client.v2.ClientV2Exception;\nimport org.cloudfoundry.operations.applications.ApplicationDetail;\nimport org.cloudfoundry.operations.applications.ApplicationHealthCheck;\nimport org.cloudfoundry.operations.applications.ApplicationManifest;\nimport org.cloudfoundry.operations.applications.Docker;\nimport org.cloudfoundry.operations.applications.InstanceDetail;\nimport org.cloudfoundry.operations.applications.PushApplicationManifestRequest;\nimport org.cloudfoundry.operations.applications.Route;\nimport org.cloudfoundry.operations.applications.StartApplicationRequest;\nimport org.cloudfoundry.operations.services.BindServiceInstanceRequest;\nimport org.cloudfoundry.util.FluentMap;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.io.TempDir;\nimport reactor.core.publisher.Mono;\n\nimport org.springframework.cloud.deployer.resource.docker.DockerResource;\nimport org.springframework.cloud.deployer.resource.maven.MavenResource;\nimport org.springframework.cloud.deployer.spi.app.AppDeployer;\nimport org.springframework.cloud.deployer.spi.app.AppScaleRequest;\nimport org.springframework.cloud.deployer.spi.app.AppStatus;\nimport org.springframework.cloud.deployer.spi.app.DeploymentState;\nimport org.springframework.cloud.deployer.spi.core.AppDefinition;\nimport org.springframework.cloud.deployer.spi.core.AppDeploymentRequest;\nimport org.springframework.core.io.FileSystemResource;\nimport org.springframework.core.io.Resource;\nimport org.springframework.core.io.UrlResource;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.assertThatThrownBy;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.BDDMockito.given;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\n/**\n * Unit tests for the {@link CloudFoundryAppDeployer}.\n *\n * @author Greg Turnquist\n * @author Eric Bottard\n * @author Ilayaperumal Gopinathan\n * @author Ben Hale\n * @author David Turanski\n */\npublic class CloudFoundryAppDeployerTests extends AbstractAppDeployerTestSupport {\n\n\t@TempDir\n\tpublic Path folder;\n\n\t@Override\n\tprotected void postSetUp() {\n\t\tthis.deploymentProperties.setServices(new HashSet<>(Arrays.asList(\"test-service-1\", \"test-service-2\")));\n\t\tthis.deploymentProperties.setEnv(Collections.singletonMap(\"SOME_GLOBAL_PROPERTY\", \"someGlobalValue\"));\n\t\tthis.deploymentProperties.getAppAdmin().setUser(\"user\");\n\t\tthis.deploymentProperties.getAppAdmin().setPassword(\"password\");\n\t}\n\n\t@SuppressWarnings(\"unchecked\")\n\t@Test\n\tpublic void deploy() throws IOException {\n\t\tResource resource = new FileSystemResource(\"src/test/resources/demo-0.0.1-SNAPSHOT.jar\");\n\n\t\tgiven(this.applicationNameGenerator.generateAppName(\"test-application\")).willReturn(\"test-application-id\");\n\n\t\tgivenRequestGetApplication(\"test-application-id\", Mono.error(new IllegalArgumentException()), Mono.just(\n\t\t\t\tApplicationDetail.builder()\n\t\t\t\t\t\t.diskQuota(0)\n\t\t\t\t\t\t.id(\"test-application-id\")\n\t\t\t\t\t\t.instances(1)\n\t\t\t\t\t\t.memoryLimit(0)\n\t\t\t\t\t\t.name(\"test-application\")\n\t\t\t\t\t\t.requestedState(\"RUNNING\")\n\t\t\t\t\t\t.runningInstances(0)\n\t\t\t\t\t\t.stack(\"test-stack\")\n\t\t\t\t\t\t.build()));\n\n\t\tgivenRequestPushApplication(PushApplicationManifestRequest.builder()\n\t\t\t\t.manifest(ApplicationManifest.builder()\n\t\t\t\t\t\t.path(resource.getFile().toPath())\n\t\t\t\t\t\t.buildpack(deploymentProperties.getBuildpack())\n\t\t\t\t\t\t.disk(1024)\n\t\t\t\t\t\t.environmentVariables(defaultEnvironmentVariables())\n\t\t\t\t\t\t.instances(1)\n\t\t\t\t\t\t.memory(1024)\n\t\t\t\t\t\t.name(\"test-application-id\")\n\t\t\t\t\t\t.service(\"test-service-2\")\n\t\t\t\t\t\t.service(\"test-service-1\")\n\t\t\t\t\t\t.build())\n\t\t\t\t.stagingTimeout(this.deploymentProperties.getStagingTimeout())\n\t\t\t\t.startupTimeout(this.deploymentProperties.getStartupTimeout())\n\t\t\t\t.build(), Mono.empty());\n\n\t\tString deploymentId = this.deployer.deploy(\n\t\t\t\tnew AppDeploymentRequest(new AppDefinition(\"test-application\", Collections.emptyMap()), resource,\n\t\t\t\t\t\tCollections.EMPTY_MAP));\n\n\t\tassertThat(deploymentId).isEqualTo(\"test-application-id\");\n\t}\n\n\t@Test\n\tvoid getLog() {\n\t\tfinal String LOG_RESULTS = \"log results\";\n\n\t\twhen(this.operations.applications().get(any())).thenReturn(applicationDetailRunningApp());\n\t\tApplicationLogAccessor applicationLogAccessor = mock(ApplicationLogAccessor.class);\n\t\twhen(applicationLogAccessor.getLog(any(), any())).thenReturn(LOG_RESULTS);\n\t\tCloudFoundryAppDeployer deployer = new CloudFoundryAppDeployer(this.applicationNameGenerator,\n\t\t\t\tthis.deploymentProperties,\n\t\t\t\tthis.operations, this.runtimeEnvironmentInfo, applicationLogAccessor);\n\t\tassertThat(deployer.getLog(\"test-application-id\")).isEqualTo(LOG_RESULTS);\n\t}\n\t@Test\n\tvoid getLogWithNoResult() {\n\t\twhen(this.operations.applications().get(any())).thenThrow(new NoSuchElementException());\n\t\tApplicationLogAccessor applicationLogAccessor = mock(ApplicationLogAccessor.class);\n\t\tCloudFoundryAppDeployer deployer = new CloudFoundryAppDeployer(this.applicationNameGenerator,\n\t\t\t\tthis.deploymentProperties,\n\t\t\t\tthis.operations, this.runtimeEnvironmentInfo, applicationLogAccessor);\n\t\tassertThatThrownBy( () -> deployer.getLog(\"test-application-id\"))\n\t\t\t\t.isInstanceOf(IllegalArgumentException.class)\n\t\t\t\t.hasMessage(\"Unable to find cf-guid\");\n\t}\n\n\t@Test\n\tpublic void deployMavenArtifactShouldDeleteByDefault() throws IOException, URISyntaxException {\n\t\tMavenResource resource = mock(MavenResource.class);\n\n\t\tPath mavenPath = folder.resolve(\"maven\");\n\t\tPath artifactPath = mavenPath.resolve(\"artifact.jar\");\n\t\tFile mavenArtifact = artifactPath.toFile();\n\t\tgiven(resource.getFile()).willReturn(mavenArtifact);\n\t\tgiven(resource.getURI()).willReturn(new URI(\"maven://test:demo:0.0.1\"));\n\t\tassertThat(this.deploymentProperties.isAutoDeleteMavenArtifacts()).isTrue();\n\t\tdeployResource(this.deployer, resource);\n\t\tassertThat(mavenArtifact.getParentFile().exists()).isFalse();\n\n\t}\n\n\t@Test\n\tpublic void deployMavenArtifactShouldNotDeleteIfConfigured() throws IOException, URISyntaxException {\n\t\tMavenResource resource = mock(MavenResource.class);\n\t\tPath mavenPath = folder.resolve(\"maven\");\n\t\tPath artifactPath = mavenPath.resolve(\"artifact.jar\");\n\t\tFile mavenArtifact = artifactPath.toFile();\n\t\tFiles.createDirectories(artifactPath.getParent());\n\t\tgiven(resource.getFile()).willReturn(mavenArtifact);\n\t\tgiven(resource.getURI()).willReturn(new URI(\"maven://test:demo:0.0.1\"));\n\n\t\tCloudFoundryDeploymentProperties deploymentProperties = new CloudFoundryDeploymentProperties();\n\t\tdeploymentProperties.setAutoDeleteMavenArtifacts(false);\n\t\tCloudFoundryAppDeployer deployer = new CloudFoundryAppDeployer(this.applicationNameGenerator,\n\t\t\t\tdeploymentProperties,\n\t\t\t\tthis.operations, this.runtimeEnvironmentInfo, this.applicationLogAccessor);\n\n\t\tdeployResource(deployer, resource);\n\t\tassertThat(mavenArtifact.getParentFile().exists()).isTrue();\n\t}\n\n\t@Test\n\tpublic void deployHttpArtifactShouldDelete() throws IOException, URISyntaxException {\n\n\t\tUrlResource resource = mock(UrlResource.class);\n\n\t\tPath downloadPath = folder.resolve(\"download\");\n\t\tPath artifactPath = downloadPath.resolve(\"artifact.jar\");\n\t\tFile downloadedArtifact = artifactPath.toFile();\n\n\t\tgiven(resource.getFile()).willReturn(downloadedArtifact);\n\t\tgiven(resource.getURI()).willReturn(new URI(\"http://somehost/artifact.jar\"));\n\t\tassertThat(this.deploymentProperties.isAutoDeleteMavenArtifacts()).isTrue();\n\t\tdeployResource(this.deployer, resource);\n\t\tassertThat(downloadedArtifact.exists()).isFalse();\n\t}\n\n\t@SuppressWarnings(\"unchecked\")\n\tprivate void deployResource(CloudFoundryAppDeployer deployer, Resource resource) throws IOException {\n\t\tgiven(this.applicationNameGenerator.generateAppName(\"test-application\")).willReturn(\"test-application-id\");\n\n\t\tgivenRequestGetApplication(\"test-application-id\", Mono.error(new IllegalArgumentException()), Mono.just(\n\t\t\t\tApplicationDetail.builder()\n\t\t\t\t\t\t.diskQuota(0)\n\t\t\t\t\t\t.id(\"test-application-id\")\n\t\t\t\t\t\t.instances(1)\n\t\t\t\t\t\t.memoryLimit(0)\n\t\t\t\t\t\t.name(\"test-application\")\n\t\t\t\t\t\t.requestedState(\"RUNNING\")\n\t\t\t\t\t\t.runningInstances(0)\n\t\t\t\t\t\t.stack(\"test-stack\")\n\t\t\t\t\t\t.build()));\n\n\t\tgivenRequestPushApplication(PushApplicationManifestRequest.builder()\n\t\t\t\t.manifest(ApplicationManifest.builder()\n\t\t\t\t\t\t.path(resource.getFile().toPath())\n\t\t\t\t\t\t.buildpack(deploymentProperties.getBuildpack())\n\t\t\t\t\t\t.disk(1024)\n\t\t\t\t\t\t.instances(1)\n\t\t\t\t\t\t.memory(1024)\n\t\t\t\t\t\t.name(\"test-application-id\")\n\t\t\t\t\t\t.build())\n\t\t\t\t.stagingTimeout(this.deploymentProperties.getStagingTimeout())\n\t\t\t\t.startupTimeout(this.deploymentProperties.getStartupTimeout())\n\t\t\t\t.build(), Mono.empty());\n\n\t\tdeployer.deploy(\n\t\t\t\tnew AppDeploymentRequest(new AppDefinition(\"test-application\", Collections.emptyMap()), resource,\n\t\t\t\t\t\tCollections.EMPTY_MAP));\n\n\t}\n\n\t@SuppressWarnings(\"unchecked\")\n\t@Test\n\tpublic void deployWithServiceParameters() throws IOException {\n\t\tResource resource = new FileSystemResource(\"src/test/resources/demo-0.0.1-SNAPSHOT.jar\");\n\n\t\tgiven(this.applicationNameGenerator.generateAppName(\"test-application\")).willReturn(\"test-application-id\");\n\t\tgiven(this.services.bind(any(BindServiceInstanceRequest.class)))\n\t\t\t\t.willReturn(Mono.empty());\n\t\tgiven(this.applications.start(any(StartApplicationRequest.class)))\n\t\t\t\t.willReturn(Mono.empty());\n\n\t\tgivenRequestGetApplication(\"test-application-id\", Mono.error(new IllegalArgumentException()), Mono.just(\n\t\t\t\tApplicationDetail.builder()\n\t\t\t\t\t\t.diskQuota(0)\n\t\t\t\t\t\t.id(\"test-application-id\")\n\t\t\t\t\t\t.instances(1)\n\t\t\t\t\t\t.memoryLimit(0)\n\t\t\t\t\t\t.name(\"test-application\")\n\t\t\t\t\t\t.requestedState(\"RUNNING\")\n\t\t\t\t\t\t.runningInstances(0)\n\t\t\t\t\t\t.stack(\"test-stack\")\n\t\t\t\t\t\t.build()));\n\n\t\tgivenRequestPushApplication(PushApplicationManifestRequest.builder()\n\t\t\t\t.manifest(ApplicationManifest.builder()\n\t\t\t\t\t\t.path(resource.getFile().toPath())\n\t\t\t\t\t\t.buildpack(deploymentProperties.getBuildpack())\n\t\t\t\t\t\t.disk(1024)\n\t\t\t\t\t\t.environmentVariables(defaultEnvironmentVariables())\n\t\t\t\t\t\t.instances(1)\n\t\t\t\t\t\t.memory(1024)\n\t\t\t\t\t\t.name(\"test-application-id\")\n\t\t\t\t\t\t.service(\"test-service-2\")\n\t\t\t\t\t\t.service(\"test-service-1\")\n\t\t\t\t\t\t.build())\n\t\t\t\t.stagingTimeout(this.deploymentProperties.getStagingTimeout())\n\t\t\t\t.startupTimeout(this.deploymentProperties.getStartupTimeout())\n\t\t\t\t.build(), Mono.empty());\n\n\t\tString deploymentId = this.deployer.deploy(\n\t\t\t\tnew AppDeploymentRequest(new AppDefinition(\"test-application\", Collections.emptyMap()), resource,\n\t\t\t\t\t\tCollections.singletonMap(\n\t\t\t\t\t\t\t\tCloudFoundryDeploymentProperties.SERVICES_PROPERTY_KEY, \"'test-service-3 foo:bar'\")));\n\n\t\tassertThat(deploymentId).isEqualTo(\"test-application-id\");\n\t}\n\n\n\t@SuppressWarnings(\"unchecked\")\n\t@Test\n\tpublic void deployWithServiceParametersAndBindingError() throws Exception {\n\t\tResource resource = new FileSystemResource(\"src/test/resources/demo-0.0.1-SNAPSHOT.jar\");\n\n\t\tgiven(this.applicationNameGenerator.generateAppName(\"test-application\")).willReturn(\"test-application-id\");\n\t\tAtomicInteger count = new AtomicInteger();\n\t\tCountDownLatch okLatch = new CountDownLatch(1);\n\t\tClientV2Exception e = new ClientV2Exception(500, 10001,\n\t\t\t\t\"The service broker could not perform this operation in parallel with other running operations\",\n\t\t\t\t\"CF-ConcurrencyError\");\n\t\t// fail 2 times\n\t\tgiven(this.services.bind(any(BindServiceInstanceRequest.class)))\n\t\t\t.will(x -> {\n\t\t\t\tif (count.getAndIncrement() < 2) {\n\t\t\t\t\treturn Mono.error(e);\n\t\t\t\t}\n\t\t\t\tokLatch.countDown();\n\t\t\t\treturn Mono.empty();\n\t\t\t});\n\t\tgiven(this.applications.start(any(StartApplicationRequest.class)))\n\t\t\t\t.willReturn(Mono.empty());\n\n\t\tgivenRequestGetApplication(\"test-application-id\", Mono.error(new IllegalArgumentException()), Mono.just(\n\t\t\t\tApplicationDetail.builder()\n\t\t\t\t\t\t.diskQuota(0)\n\t\t\t\t\t\t.id(\"test-application-id\")\n\t\t\t\t\t\t.instances(1)\n\t\t\t\t\t\t.memoryLimit(0)\n\t\t\t\t\t\t.name(\"test-application\")\n\t\t\t\t\t\t.requestedState(\"RUNNING\")\n\t\t\t\t\t\t.runningInstances(0)\n\t\t\t\t\t\t.stack(\"test-stack\")\n\t\t\t\t\t\t.build()));\n\n\t\tgivenRequestPushApplication(PushApplicationManifestRequest.builder()\n\t\t\t\t.manifest(ApplicationManifest.builder()\n\t\t\t\t\t\t.path(resource.getFile().toPath())\n\t\t\t\t\t\t.buildpack(deploymentProperties.getBuildpack())\n\t\t\t\t\t\t.disk(1024)\n\t\t\t\t\t\t.environmentVariables(defaultEnvironmentVariables())\n\t\t\t\t\t\t.instances(1)\n\t\t\t\t\t\t.memory(1024)\n\t\t\t\t\t\t.name(\"test-application-id\")\n\t\t\t\t\t\t.service(\"test-service-2\")\n\t\t\t\t\t\t.service(\"test-service-1\")\n\t\t\t\t\t\t.build())\n\t\t\t\t.stagingTimeout(this.deploymentProperties.getStagingTimeout())\n\t\t\t\t.startupTimeout(this.deploymentProperties.getStartupTimeout())\n\t\t\t\t.build(), Mono.empty());\n\n\t\tString deploymentId = this.deployer.deploy(\n\t\t\t\tnew AppDeploymentRequest(new AppDefinition(\"test-application\", Collections.emptyMap()), resource,\n\t\t\t\t\t\tCollections.singletonMap(\n\t\t\t\t\t\t\t\tCloudFoundryDeploymentProperties.SERVICES_PROPERTY_KEY, \"'test-service-3 foo:bar'\")));\n\n\t\tassertThat(deploymentId).isEqualTo(\"test-application-id\");\n\t\t// deploy is actually subscribe and forget so need to wait and\n\t\t// check that we got retries.\n\t\tassertThat(okLatch.await(30, TimeUnit.SECONDS)).isTrue();\n\t\tassertThat(count.get()).isEqualTo(3);\n\t}\n\n\t@SuppressWarnings(\"unchecked\")\n\t@Test\n\tpublic void deployWithAdditionalProperties() throws IOException {\n\t\tResource resource = new FileSystemResource(\"src/test/resources/demo-0.0.1-SNAPSHOT.jar\");\n\n\t\tgiven(this.applicationNameGenerator.generateAppName(\"test-application\")).willReturn(\"test-application-id\");\n\n\t\tgivenRequestGetApplication(\"test-application-id\", Mono.error(new IllegalArgumentException()), Mono.just(\n\t\t\t\tApplicationDetail.builder()\n\t\t\t\t\t\t.diskQuota(0)\n\t\t\t\t\t\t.id(\"test-application-id\")\n\t\t\t\t\t\t.instances(1)\n\t\t\t\t\t\t.memoryLimit(0)\n\t\t\t\t\t\t.name(\"test-application\")\n\t\t\t\t\t\t.requestedState(\"RUNNING\")\n\t\t\t\t\t\t.runningInstances(0)\n\t\t\t\t\t\t.stack(\"test-stack\")\n\t\t\t\t\t\t.build()));\n\n\t\tMap<String, String> environmentVariables = new HashMap<>();\n\t\tenvironmentVariables.put(\"test-key-1\", \"test-value-1\");\n\t\taddGuidAndIndex(environmentVariables);\n\n\t\tgivenRequestPushApplication(PushApplicationManifestRequest.builder()\n\t\t\t\t.manifest(ApplicationManifest.builder()\n\t\t\t\t\t\t.path(resource.getFile().toPath())\n\t\t\t\t\t\t.buildpack(deploymentProperties.getBuildpack())\n\t\t\t\t\t\t.disk(1024)\n\t\t\t\t\t\t.environmentVariables(environmentVariables)\n\t\t\t\t\t\t.instances(1)\n\t\t\t\t\t\t.memory(1024)\n\t\t\t\t\t\t.name(\"test-application-id\")\n\t\t\t\t\t\t.service(\"test-service-2\")\n\t\t\t\t\t\t.service(\"test-service-1\")\n\t\t\t\t\t\t.build())\n\t\t\t\t.stagingTimeout(this.deploymentProperties.getStagingTimeout())\n\t\t\t\t.startupTimeout(this.deploymentProperties.getStartupTimeout())\n\t\t\t\t.build(), Mono.empty());\n\n\t\tString deploymentId = this.deployer.deploy(new AppDeploymentRequest(\n\t\t\t\tnew AppDefinition(\"test-application\", Collections.singletonMap(\"test-key-1\", \"test-value-1\")), resource,\n\t\t\t\tFluentMap.<String, String>builder().entry(\"test-key-2\", \"test-value-2\")\n\t\t\t\t\t\t.entry(CloudFoundryDeploymentProperties.USE_SPRING_APPLICATION_JSON_KEY, String.valueOf(false))\n\t\t\t\t\t\t.build()));\n\n\t\tassertThat(deploymentId).isEqualTo(\"test-application-id\");\n\t}\n\n\t@SuppressWarnings(\"unchecked\")\n\t@Test\n\tpublic void deployWithAdditionalPropertiesInSpringApplicationJson() throws IOException {\n\t\tResource resource = new FileSystemResource(\"src/test/resources/demo-0.0.1-SNAPSHOT.jar\");\n\n\t\tgiven(this.applicationNameGenerator.generateAppName(\"test-application\")).willReturn(\"test-application-id\");\n\n\t\tgivenRequestGetApplication(\"test-application-id\", Mono.error(new IllegalArgumentException()), Mono.just(\n\t\t\t\tApplicationDetail.builder()\n\t\t\t\t\t\t.diskQuota(0)\n\t\t\t\t\t\t.id(\"test-application-id\")\n\t\t\t\t\t\t.instances(1)\n\t\t\t\t\t\t.memoryLimit(0)\n\t\t\t\t\t\t.name(\"test-application\")\n\t\t\t\t\t\t.requestedState(\"RUNNING\")\n\t\t\t\t\t\t.runningInstances(0)\n\t\t\t\t\t\t.stack(\"test-stack\")\n\t\t\t\t\t\t.build()));\n\n\t\tMap<String, String> environmentVariables = new HashMap<>();\n\t\tenvironmentVariables.put(\"SPRING_APPLICATION_JSON\", \"{\\\"test-key-1\\\":\\\"test-value-1\\\"}\");\n\t\taddGuidAndIndex(environmentVariables);\n\n\t\tgivenRequestPushApplication(PushApplicationManifestRequest.builder()\n\t\t\t\t.manifest(ApplicationManifest.builder()\n\t\t\t\t\t\t.path(resource.getFile().toPath())\n\t\t\t\t\t\t.buildpack(deploymentProperties.getBuildpack())\n\t\t\t\t\t\t.disk(1024)\n\t\t\t\t\t\t.environmentVariables(environmentVariables)\n\t\t\t\t\t\t.instances(1)\n\t\t\t\t\t\t.memory(1024)\n\t\t\t\t\t\t.name(\"test-application-id\")\n\t\t\t\t\t\t.service(\"test-service-2\")\n\t\t\t\t\t\t.service(\"test-service-1\")\n\t\t\t\t\t\t.build())\n\t\t\t\t.stagingTimeout(this.deploymentProperties.getStagingTimeout())\n\t\t\t\t.startupTimeout(this.deploymentProperties.getStartupTimeout())\n\t\t\t\t.build(), Mono.empty());\n\n\t\tString deploymentId = this.deployer.deploy(new AppDeploymentRequest(\n\t\t\t\tnew AppDefinition(\"test-application\", Collections.singletonMap(\"test-key-1\", \"test-value-1\")), resource,\n\t\t\t\tCollections.singletonMap(\"test-key-2\", \"test-value-2\")));\n\n\t\tassertThat(deploymentId).isEqualTo(\"test-application-id\");\n\t}\n\n\t@Test\n\tpublic void deployWithDeployerEnvironmentVariables() throws IOException {\n\t\tResource resource = new FileSystemResource(\"src/test/resources/demo-0.0.1-SNAPSHOT.jar\");\n\n\t\tgiven(this.applicationNameGenerator.generateAppName(\"test-application\")).willReturn(\"test-application-id\");\n\n\t\tgivenRequestGetApplication(\"test-application-id\", Mono.error(new IllegalArgumentException()), applicationDetailRunningApp());\n\n\t\tgivenRequestPushApplication(PushApplicationManifestRequest.builder()\n\t\t\t\t.manifest(ApplicationManifest.builder()\n\t\t\t\t\t\t.path(resource.getFile().toPath())\n\t\t\t\t\t\t.buildpack(\"test-buildpack\")\n\t\t\t\t\t\t.disk(0)\n\t\t\t\t\t\t.environmentVariables(defaultEnvironmentVariables())\n\t\t\t\t\t\t.healthCheckType(ApplicationHealthCheck.NONE)\n\t\t\t\t\t\t.instances(0)\n\t\t\t\t\t\t.memory(0)\n\t\t\t\t\t\t.name(\"test-application-id\")\n\t\t\t\t\t\t.noRoute(false)\n\t\t\t\t\t\t.host(\"test-host\")\n\t\t\t\t\t\t.domain(\"test-domain\")\n\t\t\t\t\t\t.service(\"test-service-2\")\n\t\t\t\t\t\t.service(\"test-service-1\")\n\t\t\t\t\t\t.build())\n\t\t\t\t.build(), Mono.empty());\n\n\t\tAppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(new AppDefinition(\"test-application\",\n\t\t\t\tCollections.emptyMap()), resource,\n\t\t\t\tFluentMap.<String, String>builder().entry(CloudFoundryDeploymentProperties.BUILDPACK_PROPERTY_KEY, \"test-buildpack\")\n\t\t\t\t\t\t.entry(AppDeployer.DISK_PROPERTY_KEY, \"0\")\n\t\t\t\t\t\t.entry(CloudFoundryDeploymentProperties.DOMAIN_PROPERTY, \"test-domain\")\n\t\t\t\t\t\t.entry(CloudFoundryDeploymentProperties.HEALTHCHECK_PROPERTY_KEY, \"none\")\n\t\t\t\t\t\t.entry(CloudFoundryDeploymentProperties.HOST_PROPERTY, \"test-host\")\n\t\t\t\t\t\t.entry(AppDeployer.COUNT_PROPERTY_KEY, \"0\")\n\t\t\t\t\t\t.entry(AppDeployer.MEMORY_PROPERTY_KEY, \"0\")\n\t\t\t\t\t\t.entry(CloudFoundryDeploymentProperties.NO_ROUTE_PROPERTY, \"false\")\n\t\t\t\t\t\t.entry(CloudFoundryDeploymentProperties.ROUTE_PATH_PROPERTY, \"/test-route-path\")\n\t\t\t\t\t\t.entry(CloudFoundryDeploymentProperties.ENV_KEY + \".JBP_CONFIG_SPRING_AUTO_RECONFIGURATION\", CfEnvConfigurer.ENABLED_FALSE)\n\t\t\t\t\t\t.entry(CloudFoundryDeploymentProperties.ENV_KEY + \".SPRING_PROFILES_ACTIVE\", \"cloud,foo\")\n\t\t\t\t\t\t.build());\n\n\t\tString deploymentId = deployer.deploy(appDeploymentRequest);\n\n\t\tMap<String, String> merged = this.deployer.mergeEnvironmentVariables(\"test-application-id\", appDeploymentRequest);\n\t\tassertThat(merged).containsEntry(\"JBP_CONFIG_SPRING_AUTO_RECONFIGURATION\", CfEnvConfigurer.ENABLED_FALSE);\n\t\tassertThat(merged).containsEntry(\"SPRING_PROFILES_ACTIVE\", \"cloud,foo\");\n\t\tassertThat(merged).containsEntry(\"SOME_GLOBAL_PROPERTY\", \"someGlobalValue\");\n\t\tassertThat(merged).containsKey(\"SPRING_APPLICATION_JSON\");\n\t\tassertThat(merged.get(\"SPRING_CLOUD_STREAMAPP_SECURITY_ADMIN-USER\")).isEqualTo(\"user\");\n\t\tassertThat(merged.get(\"SPRING_CLOUD_STREAMAPP_SECURITY_ADMIN-PASSWORD\")).isEqualTo(\"password\");\n\t\tassertThat(deploymentId).isEqualTo(\"test-application-id\");\n\t}\n\n\tprivate Mono<ApplicationDetail> applicationDetailRunningApp() {\n\t\treturn Mono.just(\n\t\t\t\tApplicationDetail.builder()\n\t\t\t\t\t\t.diskQuota(0)\n\t\t\t\t\t\t.id(\"test-application-id\")\n\t\t\t\t\t\t.instances(1)\n\t\t\t\t\t\t.memoryLimit(0)\n\t\t\t\t\t\t.name(\"test-application\")\n\t\t\t\t\t\t.requestedState(\"RUNNING\")\n\t\t\t\t\t\t.runningInstances(0)\n\t\t\t\t\t\t.stack(\"test-stack\")\n\t\t\t\t\t\t.build());\n\t}\n\n\t@Test\n\tpublic void automaticallyConfigureForCfEnv() throws JsonProcessingException {\n\t\tResource resource = new FileSystemResource(\"src/test/resources/log-sink-rabbit-3.0.0.BUILD-SNAPSHOT.jar\");\n\t\tAppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(new AppDefinition(\"test-application\",\n\t\t\t\tCollections.emptyMap()), resource, Collections.emptyMap());\n\n\t\tMap<String, String> env = deployer.mergeEnvironmentVariables(\"test-application-id\", appDeploymentRequest);\n\t\tassertThat(env).containsEntry(\"SOME_GLOBAL_PROPERTY\", \"someGlobalValue\");\n\t\tassertThat(env).containsEntry(CfEnvConfigurer.JBP_CONFIG_SPRING_AUTO_RECONFIGURATION, CfEnvConfigurer.ENABLED_FALSE);\n\t\tassertThat(env).containsKey(\"SPRING_APPLICATION_JSON\");\n\t\tObjectMapper objectMapper = new ObjectMapper();\n\t\tMap<String, String> saj = objectMapper.readValue(env.get(\"SPRING_APPLICATION_JSON\"), HashMap.class);\n\t\tassertThat(saj).containsEntry(CfEnvConfigurer.SPRING_PROFILES_ACTIVE_FQN, CfEnvConfigurer.CLOUD_PROFILE_NAME);\n\n\t\tresource = new FileSystemResource(\"src/test/resources/log-sink-rabbit-2.1.5.RELEASE.jar\");\n\t\tappDeploymentRequest = new AppDeploymentRequest(new AppDefinition(\"test-application\",\n\t\t\t\tCollections.emptyMap()), resource, Collections.emptyMap());\n\t\tenv = deployer.mergeEnvironmentVariables(\"test-application-id\", appDeploymentRequest);\n\t\tassertThat(env).doesNotContainKey(CfEnvConfigurer.JBP_CONFIG_SPRING_AUTO_RECONFIGURATION);\n\t}\n\n\t@SuppressWarnings(\"unchecked\")\n\t@Test\n\tpublic void deployWithApplicationDeploymentProperties() throws IOException {\n\t\tResource resource = new FileSystemResource(\"src/test/resources/demo-0.0.1-SNAPSHOT.jar\");\n\n\t\tgiven(this.applicationNameGenerator.generateAppName(\"test-application\")).willReturn(\"test-application-id\");\n\n\t\tgivenRequestGetApplication(\"test-application-id\", Mono.error(new IllegalArgumentException()), Mono.just(\n\t\t\t\tApplicationDetail.builder()\n\t\t\t\t\t\t.diskQuota(0)\n\t\t\t\t\t\t.id(\"test-application-id\")\n\t\t\t\t\t\t.instances(1)\n\t\t\t\t\t\t.memoryLimit(0)\n\t\t\t\t\t\t.name(\"test-application\")\n\t\t\t\t\t\t.requestedState(\"RUNNING\")\n\t\t\t\t\t\t.runningInstances(0)\n\t\t\t\t\t\t.stack(\"test-stack\")\n\t\t\t\t\t\t.build()));\n\n\t\tgivenRequestPushApplication(PushApplicationManifestRequest.builder()\n\t\t\t\t.manifest(ApplicationManifest.builder()\n\t\t\t\t\t\t.path(resource.getFile().toPath())\n\t\t\t\t\t\t.buildpack(\"test-buildpack\")\n\t\t\t\t\t\t.disk(0)\n\t\t\t\t\t\t.environmentVariables(defaultEnvironmentVariables())\n\t\t\t\t\t\t.healthCheckType(ApplicationHealthCheck.NONE)\n\t\t\t\t\t\t.instances(0)\n\t\t\t\t\t\t.memory(0)\n\t\t\t\t\t\t.name(\"test-application-id\")\n\t\t\t\t\t\t.noRoute(false)\n\t\t\t\t\t\t.host(\"test-host\")\n\t\t\t\t\t\t.domain(\"test-domain\")\n\t\t\t\t\t\t.routePath(\"/test-route-path\")\n\t\t\t\t\t\t.service(\"test-service-2\")\n\t\t\t\t\t\t.service(\"test-service-1\")\n\t\t\t\t\t\t.build())\n\t\t\t\t.stagingTimeout(this.deploymentProperties.getStagingTimeout())\n\t\t\t\t.startupTimeout(this.deploymentProperties.getStartupTimeout())\n\t\t\t\t.build(), Mono.empty());\n\n\t\tString deploymentId = this.deployer.deploy(\n\t\t\t\tnew AppDeploymentRequest(new AppDefinition(\"test-application\", Collections.emptyMap()), resource,\n\t\t\t\t\t\tFluentMap.<String, String>builder().entry(CloudFoundryDeploymentProperties.BUILDPACK_PROPERTY_KEY, \"test-buildpack\")\n\t\t\t\t\t\t\t\t.entry(AppDeployer.DISK_PROPERTY_KEY, \"0\")\n\t\t\t\t\t\t\t\t.entry(CloudFoundryDeploymentProperties.DOMAIN_PROPERTY, \"test-domain\")\n\t\t\t\t\t\t\t\t.entry(CloudFoundryDeploymentProperties.HEALTHCHECK_PROPERTY_KEY, \"none\")\n\t\t\t\t\t\t\t\t.entry(CloudFoundryDeploymentProperties.HOST_PROPERTY, \"test-host\")\n\t\t\t\t\t\t\t\t.entry(AppDeployer.COUNT_PROPERTY_KEY, \"0\")\n\t\t\t\t\t\t\t\t.entry(AppDeployer.MEMORY_PROPERTY_KEY, \"0\")\n\t\t\t\t\t\t\t\t.entry(CloudFoundryDeploymentProperties.NO_ROUTE_PROPERTY, \"false\")\n\t\t\t\t\t\t\t\t.entry(CloudFoundryDeploymentProperties.ROUTE_PATH_PROPERTY, \"/test-route-path\")\n\t\t\t\t\t\t\t\t.build()));\n\n\t\tassertThat(deploymentId).isEqualTo(\"test-application-id\");\n\t}\n\n\t@SuppressWarnings(\"unchecked\")\n\t@Test\n\tpublic void deployWithInvalidRoutePathProperty() throws IOException {\n\t\tResource resource = new FileSystemResource(\"src/test/resources/demo-0.0.1-SNAPSHOT.jar\");\n\n\t\tgiven(this.applicationNameGenerator.generateAppName(\"test-application\")).willReturn(\"test-application-id\");\n\n\t\tgivenRequestGetApplication(\"test-application-id\",\n\t\t\t\tMono.error(new IllegalArgumentException()),\n\t\t\t\tMono.just(ApplicationDetail.builder()\n\t\t\t\t\t\t.diskQuota(0)\n\t\t\t\t\t\t.id(\"test-application-id\")\n\t\t\t\t\t\t.instances(1)\n\t\t\t\t\t\t.memoryLimit(0)\n\t\t\t\t\t\t.name(\"test-application\")\n\t\t\t\t\t\t.requestedState(\"RUNNING\")\n\t\t\t\t\t\t.runningInstances(0)\n\t\t\t\t\t\t.stack(\"test-stack\")\n\t\t\t\t\t\t.build()));\n\n\t\tgivenRequestPushApplication(PushApplicationManifestRequest.builder()\n\t\t\t\t.manifest(ApplicationManifest.builder()\n\t\t\t\t\t\t.path(resource.getFile().toPath())\n\t\t\t\t\t\t.buildpack(\"test-buildpack\")\n\t\t\t\t\t\t.disk(0)\n\t\t\t\t\t\t.environmentVariables(defaultEnvironmentVariables())\n\t\t\t\t\t\t.healthCheckType(ApplicationHealthCheck.NONE)\n\t\t\t\t\t\t.instances(0)\n\t\t\t\t\t\t.memory(0)\n\t\t\t\t\t\t.name(\"test-application-id\")\n\t\t\t\t\t\t.noRoute(false)\n\t\t\t\t\t\t.host(\"test-host\")\n\t\t\t\t\t\t.domain(\"test-domain\")\n\t\t\t\t\t\t.routePath(\"/test-route-path\")\n\t\t\t\t\t\t.service(\"test-service-2\")\n\t\t\t\t\t\t.service(\"test-service-1\")\n\t\t\t\t\t\t.build())\n\t\t\t\t.stagingTimeout(this.deploymentProperties.getStagingTimeout())\n\t\t\t\t.startupTimeout(this.deploymentProperties.getStartupTimeout())\n\t\t\t\t.build(), Mono.empty());\n\t\ttry {\n\t\t\tassertThatThrownBy(() -> {\n\t\t\t\tthis.deployer.deploy(new AppDeploymentRequest(\n\t\t\t\t\tnew AppDefinition(\"test-application\", Collections.emptyMap()),\n\t\t\t\t\tresource,\n\t\t\t\t\tFluentMap.<String, String>builder()\n\t\t\t\t\t\t\t.entry(CloudFoundryDeploymentProperties.BUILDPACK_PROPERTY_KEY, \"test-buildpack\")\n\t\t\t\t\t\t\t.entry(AppDeployer.DISK_PROPERTY_KEY, \"0\")\n\t\t\t\t\t\t\t.entry(CloudFoundryDeploymentProperties.DOMAIN_PROPERTY, \"test-domain\")\n\t\t\t\t\t\t\t.entry(CloudFoundryDeploymentProperties.HEALTHCHECK_PROPERTY_KEY, \"none\")\n\t\t\t\t\t\t\t.entry(CloudFoundryDeploymentProperties.HOST_PROPERTY, \"test-host\")\n\t\t\t\t\t\t\t.entry(AppDeployer.COUNT_PROPERTY_KEY, \"0\")\n\t\t\t\t\t\t\t.entry(AppDeployer.MEMORY_PROPERTY_KEY, \"0\")\n\t\t\t\t\t\t\t.entry(CloudFoundryDeploymentProperties.NO_ROUTE_PROPERTY, \"false\")\n\t\t\t\t\t\t\t.entry(CloudFoundryDeploymentProperties.ROUTE_PATH_PROPERTY, \"test-route-path\")\n\t\t\t\t\t\t\t.build()));\n\n\t\t\t}).isInstanceOf(IllegalArgumentException.class).as(\"Illegal Argument exception is expected.\");\n\t\t}\n\t\tcatch (IllegalArgumentException e) {\n\t\t\tassertThat(e.getMessage()).isEqualTo(\"Cloud Foundry routes must start with \\\"/\\\". Route passed = [test-route-path].\");\n\t\t}\n\t}\n\n\t@SuppressWarnings(\"unchecked\")\n\t@Test\n\tpublic void deployWithCustomDeploymentProperties() throws IOException {\n\t\tResource resource = new FileSystemResource(\"src/test/resources/demo-0.0.1-SNAPSHOT.jar\");\n\n\t\tgiven(this.applicationNameGenerator.generateAppName(\"test-application\")).willReturn(\"test-application-id\");\n\n\t\tgivenRequestGetApplication(\"test-application-id\", Mono.error(new IllegalArgumentException()), Mono.just(\n\t\t\t\tApplicationDetail.builder()\n\t\t\t\t\t\t.diskQuota(0)\n\t\t\t\t\t\t.id(\"test-application-id\")\n\t\t\t\t\t\t.instances(1)\n\t\t\t\t\t\t.memoryLimit(0)\n\t\t\t\t\t\t.name(\"test-application\")\n\t\t\t\t\t\t.requestedState(\"RUNNING\")\n\t\t\t\t\t\t.runningInstances(0)\n\t\t\t\t\t\t.stack(\"test-stack\")\n\t\t\t\t\t\t.build()));\n\n\t\tthis.deploymentProperties.setBuildpack(\"test-buildpack\");\n\t\tthis.deploymentProperties.setDisk(\"0\");\n\t\tthis.deploymentProperties.setDomain(\"test-domain\");\n\t\tthis.deploymentProperties.setHealthCheck(ApplicationHealthCheck.NONE);\n\t\tthis.deploymentProperties.setHost(\"test-host\");\n\t\tthis.deploymentProperties.setInstances(0);\n\t\tthis.deploymentProperties.setMemory(\"0\");\n\n\t\tgivenRequestPushApplication(PushApplicationManifestRequest.builder()\n\t\t\t\t.manifest(ApplicationManifest.builder()\n\t\t\t\t\t\t.path(resource.getFile().toPath())\n\t\t\t\t\t\t.buildpack(\"test-buildpack\")\n\t\t\t\t\t\t.disk(0)\n\t\t\t\t\t\t.domain(\"test-domain\")\n\t\t\t\t\t\t.environmentVariables(defaultEnvironmentVariables())\n\t\t\t\t\t\t.healthCheckType(ApplicationHealthCheck.NONE)\n\t\t\t\t\t\t.host(\"test-host\")\n\t\t\t\t\t\t.instances(0)\n\t\t\t\t\t\t.memory(0)\n\t\t\t\t\t\t.name(\"test-application-id\")\n\t\t\t\t\t\t.service(\"test-service-2\")\n\t\t\t\t\t\t.service(\"test-service-1\")\n\t\t\t\t\t\t.build())\n\t\t\t\t.stagingTimeout(this.deploymentProperties.getStagingTimeout())\n\t\t\t\t.startupTimeout(this.deploymentProperties.getStartupTimeout())\n\t\t\t\t.build(), Mono.empty());\n\n\t\tString deploymentId = this.deployer.deploy(\n\t\t\t\tnew AppDeploymentRequest(new AppDefinition(\"test-application\", Collections.emptyMap()), resource,\n\t\t\t\t\t\tCollections.emptyMap()));\n\n\t\tassertThat(deploymentId).isEqualTo(\"test-application-id\");\n\t}\n\n\t@SuppressWarnings(\"unchecked\")\n\t@Test\n\tpublic void deployWithMultipleRoutes() throws IOException {\n\t\tResource resource = new FileSystemResource(\"src/test/resources/demo-0.0.1-SNAPSHOT.jar\");\n\n\t\tgiven(this.applicationNameGenerator.generateAppName(\"test-application\")).willReturn(\"test-application-id\");\n\n\t\tgivenRequestGetApplication(\"test-application-id\",\n\t\t\t\tMono.error(new IllegalArgumentException()),\n\t\t\t\tMono.just(ApplicationDetail.builder()\n\t\t\t\t\t\t.diskQuota(0)\n\t\t\t\t\t\t.id(\"test-application-id\")\n\t\t\t\t\t\t.instances(1)\n\t\t\t\t\t\t.memoryLimit(0)\n\t\t\t\t\t\t.name(\"test-application\")\n\t\t\t\t\t\t.requestedState(\"RUNNING\")\n\t\t\t\t\t\t.runningInstances(0)\n\t\t\t\t\t\t.stack(\"test-stack\")\n\t\t\t\t\t\t.build()));\n\n\t\tthis.deploymentProperties.setBuildpack(\"test-buildpack\");\n\t\tthis.deploymentProperties.setDisk(\"0\");\n\t\tthis.deploymentProperties.setHealthCheck(ApplicationHealthCheck.NONE);\n\t\tthis.deploymentProperties.setRoutes(Sets.newHashSet(\"route1.test-domain\", \"route2.test-domain\"));\n\t\tthis.deploymentProperties.setInstances(0);\n\t\tthis.deploymentProperties.setMemory(\"0\");\n\n\t\tgivenRequestPushApplication(PushApplicationManifestRequest.builder()\n\t\t\t\t.manifest(ApplicationManifest.builder()\n\t\t\t\t\t\t.path(resource.getFile().toPath())\n\t\t\t\t\t\t.buildpack(\"test-buildpack\")\n\t\t\t\t\t\t.disk(0)\n\t\t\t\t\t\t.routes(Sets.newHashSet(\n\t\t\t\t\t\t\t\tRoute.builder().route(\"route1.test-domain\").build(),\n\t\t\t\t\t\t\t\tRoute.builder().route(\"route2.test-domain\").build()))\n\t\t\t\t\t\t.environmentVariables(defaultEnvironmentVariables())\n\t\t\t\t\t\t.healthCheckType(ApplicationHealthCheck.NONE)\n\t\t\t\t\t\t.instances(0)\n\t\t\t\t\t\t.memory(0)\n\t\t\t\t\t\t.name(\"test-application-id\")\n\t\t\t\t\t\t.service(\"test-service-2\")\n\t\t\t\t\t\t.service(\"test-service-1\")\n\t\t\t\t\t\t.build())\n\t\t\t\t.stagingTimeout(this.deploymentProperties.getStagingTimeout())\n\t\t\t\t.startupTimeout(this.deploymentProperties.getStartupTimeout())\n\t\t\t\t.build(), Mono.empty());\n\n\t\tString deploymentId = this.deployer.deploy(new AppDeploymentRequest(\n\t\t\t\tnew AppDefinition(\"test-application\", Collections.emptyMap()),\n\t\t\t\tresource,\n\t\t\t\tCollections.emptyMap()));\n\n\t\tassertThat(deploymentId).isEqualTo(\"test-application-id\");\n\t}\n\n\t@SuppressWarnings(\"unchecked\")\n\t@Test\n\tpublic void deployWithMultipleRoutesAndHostOrDomainMutuallyExclusive() {\n\t\tResource resource = new FileSystemResource(\"src/test/resources/demo-0.0.1-SNAPSHOT.jar\");\n\n\t\tgiven(this.applicationNameGenerator.generateAppName(\"test-application\")).willReturn(\"test-application-id\");\n\n\t\tassertThatThrownBy(() -> {\n\t\t\tgivenRequestGetApplication(\"test-application-id\",\n\t\t\tMono.error(new IllegalArgumentException()),\n\t\t\tMono.just(ApplicationDetail.builder()\n\t\t\t\t\t.id(\"test-application-id\")\n\t\t\t\t\t.name(\"test-application\")\n\t\t\t\t\t.build()));\n\t\t}).isInstanceOf(IllegalStateException.class)\n\t\t\t\t.as(\"routes and hosts cannot be set, expect cf java client to throw an exception\");\n\n\t\tthis.deploymentProperties.setHost(\"route0\");\n\t\tthis.deploymentProperties.setDomain(\"test-domain\");\n\t\tthis.deploymentProperties.setRoutes(Sets.newHashSet(\"route1.test-domain\", \"route2.test-domain\"));\n\n\t\tassertThatThrownBy(() -> {\n\t\t\tthis.deployer.deploy(new AppDeploymentRequest(new AppDefinition(\"test-application\", Collections.emptyMap()),\n\t\t\t\t\tresource, Collections.emptyMap()));\n\t\t}).isInstanceOf(IllegalStateException.class)\n\t\t\t\t.as(\"routes and hosts cannot be set, expect cf java client to throw an exception\");\n\t}\n\n\t@SuppressWarnings(\"unchecked\")\n\t@Test\n\tpublic void deployWithGroup() throws IOException {\n\t\tResource resource = new FileSystemResource(\"src/test/resources/demo-0.0.1-SNAPSHOT.jar\");\n\n\t\tgiven(this.applicationNameGenerator.generateAppName(\"test-group-test-application\")).willReturn(\n\t\t\t\t\"test-group-test-application-id\");\n\n\t\tgivenRequestGetApplication(\"test-group-test-application-id\", Mono.error(new IllegalArgumentException()),\n\t\t\t\tMono.just(ApplicationDetail.builder()\n\t\t\t\t\t\t.diskQuota(0)\n\t\t\t\t\t\t.id(\"test-group-test-application-id\")\n\t\t\t\t\t\t.instances(1)\n\t\t\t\t\t\t.memoryLimit(0)\n\t\t\t\t\t\t.name(\"test-group-test-application\")\n\t\t\t\t\t\t.requestedState(\"RUNNING\")\n\t\t\t\t\t\t.runningInstances(0)\n\t\t\t\t\t\t.stack(\"test-stack\")\n\t\t\t\t\t\t.build()));\n\n\t\tgivenRequestPushApplication(PushApplicationManifestRequest.builder()\n\t\t\t\t.manifest(ApplicationManifest.builder()\n\t\t\t\t\t\t.path(resource.getFile().toPath())\n\t\t\t\t\t\t.buildpack(deploymentProperties.getBuildpack())\n\t\t\t\t\t\t.disk(1024)\n\t\t\t\t\t\t.environmentVariable(\"SPRING_CLOUD_APPLICATION_GROUP\", \"test-group\")\n\t\t\t\t\t\t.environmentVariable(\"SPRING_APPLICATION_JSON\", \"{}\")\n\t\t\t\t\t\t.environmentVariable(\"SPRING_APPLICATION_INDEX\", \"${vcap.application.instance_index}\")\n\t\t\t\t\t\t.environmentVariable(\"SPRING_CLOUD_APPLICATION_GUID\",\n\t\t\t\t\t\t\t\t\"${vcap.application.name}:${vcap.application.instance_index}\")\n\t\t\t\t\t\t.instances(1)\n\t\t\t\t\t\t.memory(1024)\n\t\t\t\t\t\t.name(\"test-group-test-application-id\")\n\t\t\t\t\t\t.service(\"test-service-2\")\n\t\t\t\t\t\t.service(\"test-service-1\")\n\t\t\t\t\t\t.build())\n\t\t\t\t.stagingTimeout(this.deploymentProperties.getStagingTimeout())\n\t\t\t\t.startupTimeout(this.deploymentProperties.getStartupTimeout())\n\t\t\t\t.build(), Mono.empty());\n\n\t\tString deploymentId = this.deployer.deploy(\n\t\t\t\tnew AppDeploymentRequest(new AppDefinition(\"test-application\", Collections.emptyMap()), resource,\n\t\t\t\t\t\tCollections.singletonMap(AppDeployer.GROUP_PROPERTY_KEY, \"test-group\")));\n\n\t\tassertThat(deploymentId).isEqualTo(\"test-group-test-application-id\");\n\t}\n\n\t@SuppressWarnings(\"unchecked\")\n\t@Test\n\tpublic void deployDockerResource() {\n\t\tResource resource = new DockerResource(\"somecorp/someimage:latest\");\n\n\t\tgiven(this.applicationNameGenerator.generateAppName(\"test-application\")).willReturn(\"test-application-id\");\n\n\t\tgivenRequestGetApplication(\"test-application-id\", Mono.error(new IllegalArgumentException()), Mono.just(\n\t\t\t\tApplicationDetail.builder()\n\t\t\t\t\t\t.diskQuota(0)\n\t\t\t\t\t\t.id(\"test-application-id\")\n\t\t\t\t\t\t.instances(1)\n\t\t\t\t\t\t.memoryLimit(0)\n\t\t\t\t\t\t.name(\"test-application\")\n\t\t\t\t\t\t.requestedState(\"RUNNING\")\n\t\t\t\t\t\t.runningInstances(0)\n\t\t\t\t\t\t.stack(\"test-stack\")\n\t\t\t\t\t\t.build()));\n\n\t\tgivenRequestPushApplication(PushApplicationManifestRequest.builder()\n\t\t\t\t.manifest(ApplicationManifest.builder()\n\t\t\t\t\t\t.docker(Docker.builder().image(\"somecorp/someimage:latest\").build())\n\t\t\t\t\t\t.disk(1024)\n\t\t\t\t\t\t.environmentVariables(defaultEnvironmentVariables())\n\t\t\t\t\t\t.instances(1)\n\t\t\t\t\t\t.memory(1024)\n\t\t\t\t\t\t.name(\"test-application-id\")\n\t\t\t\t\t\t.service(\"test-service-2\")\n\t\t\t\t\t\t.service(\"test-service-1\")\n\t\t\t\t\t\t.build())\n\t\t\t\t.stagingTimeout(this.deploymentProperties.getStagingTimeout())\n\t\t\t\t.startupTimeout(this.deploymentProperties.getStartupTimeout())\n\t\t\t\t.build(), Mono.empty());\n\n\t\tString deploymentId = this.deployer.deploy(\n\t\t\t\tnew AppDeploymentRequest(new AppDefinition(\"test-application\", Collections.emptyMap()), resource,\n\t\t\t\t\t\tCollections.emptyMap()));\n\n\t\tassertThat(deploymentId).isEqualTo(\"test-application-id\");\n\t}\n\n\t@SuppressWarnings(\"unchecked\")\n\t@Test\n\tpublic void statusOfCrashedApplicationIsFailed() {\n\t\tgivenRequestGetApplication(\"test-application-id\", Mono.just(ApplicationDetail.builder()\n\t\t\t\t.diskQuota(0)\n\t\t\t\t.id(\"test-application-id\")\n\t\t\t\t.instances(1)\n\t\t\t\t.memoryLimit(0)\n\t\t\t\t.name(\"test-application\")\n\t\t\t\t.requestedState(\"RUNNING\")\n\t\t\t\t.runningInstances(1)\n\t\t\t\t.stack(\"test-stack\")\n\t\t\t\t.instanceDetail(InstanceDetail.builder().state(\"CRASHED\").index(\"1\").build())\n\t\t\t\t.build()));\n\n\t\tAppStatus status = this.deployer.status(\"test-application-id\");\n\n\t\tassertThat(status.getState()).isEqualTo(DeploymentState.failed);\n\t}\n\n\t@SuppressWarnings(\"unchecked\")\n\t@Test\n\tpublic void statusOfDownApplicationIsDeploying() {\n\t\tgivenRequestGetApplication(\"test-application-id\", Mono.just(ApplicationDetail.builder()\n\t\t\t\t.diskQuota(0)\n\t\t\t\t.id(\"test-application-id\")\n\t\t\t\t.instances(1)\n\t\t\t\t.memoryLimit(0)\n\t\t\t\t.name(\"test-application\")\n\t\t\t\t.requestedState(\"RUNNING\")\n\t\t\t\t.runningInstances(1)\n\t\t\t\t.stack(\"test-stack\")\n\t\t\t\t.instanceDetail(InstanceDetail.builder().state(\"DOWN\").index(\"1\").build())\n\t\t\t\t.build()));\n\n\t\tAppStatus status = this.deployer.status(\"test-application-id\");\n\n\t\tassertThat(status.getState()).isEqualTo(DeploymentState.deploying);\n\t}\n\n\t@SuppressWarnings(\"unchecked\")\n\t@Test\n\tpublic void statusOfFlappingApplicationIsDeployed() {\n\t\tgivenRequestGetApplication(\"test-application-id\", Mono.just(ApplicationDetail.builder()\n\t\t\t\t.diskQuota(0)\n\t\t\t\t.id(\"test-application-id\")\n\t\t\t\t.instances(1)\n\t\t\t\t.memoryLimit(0)\n\t\t\t\t.name(\"test-application\")\n\t\t\t\t.requestedState(\"RUNNING\")\n\t\t\t\t.runningInstances(1)\n\t\t\t\t.stack(\"test-stack\")\n\t\t\t\t.instanceDetail(InstanceDetail.builder().state(\"FLAPPING\").index(\"1\").build())\n\t\t\t\t.build()));\n\n\t\tAppStatus status = deployer.status(\"test-application-id\");\n\n\t\tassertThat(status.getState()).isEqualTo(DeploymentState.deployed);\n\t}\n\n\t@SuppressWarnings(\"unchecked\")\n\t@Test\n\tpublic void statusOfRunningApplicationIsDeployed() {\n\t\tgivenRequestGetApplication(\"test-application-id\", Mono.just(ApplicationDetail.builder()\n\t\t\t\t.diskQuota(0)\n\t\t\t\t.id(\"test-application-id\")\n\t\t\t\t.instances(1)\n\t\t\t\t.memoryLimit(0)\n\t\t\t\t.name(\"test-application\")\n\t\t\t\t.requestedState(\"RUNNING\")\n\t\t\t\t.runningInstances(1)\n\t\t\t\t.stack(\"test-stack\")\n\t\t\t\t.instanceDetail(InstanceDetail.builder().state(\"RUNNING\").index(\"1\").build())\n\t\t\t\t.build()));\n\n\t\tAppStatus status = this.deployer.status(\"test-application-id\");\n\n\t\tassertThat(status.getState()).isEqualTo(DeploymentState.deployed);\n\t\tassertThat(status.getInstances().get(\"test-application-0\").toString())\n\t\t\t\t.isEqualTo(\"CloudFoundryAppInstanceStatus[test-application-0 : deployed]\");\n\t\tassertThat(status.getInstances().get(\"test-application-0\").getAttributes())\n\t\t\t\t.containsOnly(\n\t\t\t\t\t\tMapEntry.entry(CloudFoundryAppInstanceStatus.GUID, \"test-application:0\"),\n\t\t\t\t\t\tMapEntry.entry(CloudFoundryAppInstanceStatus.INDEX, \"0\"),\n\t\t\t\t\t\tMapEntry.entry(CloudFoundryAppInstanceStatus.CF_GUID, \"test-application-id\"));\n\t}\n\n\t@SuppressWarnings(\"unchecked\")\n\t@Test\n\tpublic void statusOfStartingApplicationIsDeploying() {\n\t\tgivenRequestGetApplication(\"test-application-id\", Mono.just(ApplicationDetail.builder()\n\t\t\t\t.diskQuota(0)\n\t\t\t\t.id(\"test-application-id\")\n\t\t\t\t.instances(1)\n\t\t\t\t.memoryLimit(0)\n\t\t\t\t.name(\"test-application\")\n\t\t\t\t.requestedState(\"RUNNING\")\n\t\t\t\t.runningInstances(1)\n\t\t\t\t.stack(\"test-stack\")\n\t\t\t\t.instanceDetail(InstanceDetail.builder().state(\"STARTING\").index(\"1\").build())\n\t\t\t\t.build()));\n\n\t\tAppStatus status = this.deployer.status(\"test-application-id\");\n\n\t\tassertThat(status.getState()).isEqualTo(DeploymentState.deploying);\n\t}\n\n\t@SuppressWarnings(\"unchecked\")\n\t@Test\n\tpublic void statusOfUnknownApplicationIsUnknown() {\n\t\tgivenRequestGetApplication(\"test-application-id\", Mono.just(ApplicationDetail.builder()\n\t\t\t\t.diskQuota(0)\n\t\t\t\t.id(\"test-application-id\")\n\t\t\t\t.instances(1)\n\t\t\t\t.memoryLimit(0)\n\t\t\t\t.name(\"test-application\")\n\t\t\t\t.requestedState(\"RUNNING\")\n\t\t\t\t.runningInstances(1)\n\t\t\t\t.stack(\"test-stack\")\n\t\t\t\t.instanceDetail(InstanceDetail.builder().state(\"UNKNOWN\").index(\"1\").build())\n\t\t\t\t.build()));\n\n\t\tAppStatus status = this.deployer.status(\"test-application-id\");\n\n\t\tassertThat(status.getState()).isEqualTo(DeploymentState.unknown);\n\t}\n\n\t@SuppressWarnings(\"unchecked\")\n\t@Test\n\tpublic void statusWithAbnormalInstanceStateThrowsException() {\n\t\tgivenRequestGetApplication(\"test-application-id\", Mono.just(ApplicationDetail.builder()\n\t\t\t\t.diskQuota(0)\n\t\t\t\t.id(\"test-application-id\")\n\t\t\t\t.instances(1)\n\t\t\t\t.memoryLimit(0)\n\t\t\t\t.name(\"test-application\")\n\t\t\t\t.requestedState(\"RUNNING\")\n\t\t\t\t.runningInstances(1)\n\t\t\t\t.stack(\"test-stack\")\n\t\t\t\t.instanceDetail(InstanceDetail.builder().state(\"ABNORMAL\").index(\"1\").build())\n\t\t\t\t.build()));\n\n\t\tassertThatThrownBy(() -> {\n\t\t\tthis.deployer.status(\"test-application-id\").getState();\n\t\t}).isInstanceOf(IllegalStateException.class).hasMessageContaining(\"Unsupported CF state\");\n\t}\n\n\t@SuppressWarnings(\"unchecked\")\n\t@Test\n\tpublic void statusWithFailingCAPICallRetries() {\n\t\tAtomicInteger i = new AtomicInteger();\n\t\tMono<ApplicationDetail> m = Mono.create(s -> {\n\t\t\tif (i.incrementAndGet() == 2) {\n\t\t\t\ts.success(ApplicationDetail.builder()\n\t\t\t\t\t\t.diskQuota(0)\n\t\t\t\t\t\t.id(\"test-application-id\")\n\t\t\t\t\t\t.instances(1)\n\t\t\t\t\t\t.memoryLimit(0)\n\t\t\t\t\t\t.name(\"test-application\")\n\t\t\t\t\t\t.requestedState(\"RUNNING\")\n\t\t\t\t\t\t.runningInstances(1)\n\t\t\t\t\t\t.stack(\"test-stack\")\n\t\t\t\t\t\t.instanceDetail(InstanceDetail.builder().state(\"UNKNOWN\").index(\"1\").build())\n\t\t\t\t\t\t.build());\n\t\t\t}\n\t\t\telse {\n\t\t\t\ts.error(new RuntimeException(\"Simulated Server Side error\"));\n\t\t\t}\n\t\t});\n\t\tgivenRequestGetApplication(\"test-application-id\", m);\n\n\t\tDeploymentState state = this.deployer.status(\"test-application-id\").getState();\n\t\tassertThat(state).isEqualTo(DeploymentState.unknown);\n\t}\n\n\t@SuppressWarnings(\"unchecked\")\n\t@Test\n\tpublic void statusWithFailingCAPICallRetriesEventualError() {\n\t\tAtomicInteger i = new AtomicInteger();\n\t\tMono<ApplicationDetail> m = Mono.create(s -> {\n\t\t\tif (i.incrementAndGet() == 12) { // 12 is more than the number of retries\n\t\t\t\ts.success(ApplicationDetail.builder()\n\t\t\t\t\t\t.diskQuota(0)\n\t\t\t\t\t\t.id(\"test-application-id\")\n\t\t\t\t\t\t.instances(1)\n\t\t\t\t\t\t.memoryLimit(0)\n\t\t\t\t\t\t.name(\"test-application\")\n\t\t\t\t\t\t.requestedState(\"RUNNING\")\n\t\t\t\t\t\t.runningInstances(1)\n\t\t\t\t\t\t.stack(\"test-stack\")\n\t\t\t\t\t\t.instanceDetail(InstanceDetail.builder().state(\"UNKNOWN\").index(\"1\").build())\n\t\t\t\t\t\t.build());\n\t\t\t}\n\t\t\telse {\n\t\t\t\ts.error(new RuntimeException(\"Simulated Server Side error\"));\n\t\t\t}\n\t\t});\n\t\tgivenRequestGetApplication(\"test-application-id\", m);\n\t\tthis.deployer.deploymentProperties.setStatusTimeout(200); // Will cause wait of 20ms then 40ms,80ms\n\n\t\tDeploymentState state = this.deployer.status(\"test-application-id\").getState();\n\t\tassertThat(state).isEqualTo(DeploymentState.error);\n\t}\n\n\t@SuppressWarnings(\"unchecked\")\n\t@Test\n\tpublic void statusWithErrorThrownOnBlocking() {\n\t\tAtomicInteger i = new AtomicInteger();\n\t\tMono<ApplicationDetail> m = Mono.delay(Duration.ofSeconds(30)).then(Mono.create(s -> {\n\t\t\ti.incrementAndGet();\n\t\t\ts.success(ApplicationDetail.builder()\n\t\t\t\t\t.diskQuota(0)\n\t\t\t\t\t.id(\"test-application-id\")\n\t\t\t\t\t.instances(1)\n\t\t\t\t\t.memoryLimit(0)\n\t\t\t\t\t.name(\"test-application\")\n\t\t\t\t\t.requestedState(\"RUNNING\")\n\t\t\t\t\t.runningInstances(1)\n\t\t\t\t\t.stack(\"test-stack\")\n\t\t\t\t\t.instanceDetail(InstanceDetail.builder().state(\"UNKNOWN\").index(\"1\").build())\n\t\t\t\t\t.build());\n\t\t}));\n\t\tgivenRequestGetApplication(\"test-application-id\", m);\n\t\tthis.deployer.deploymentProperties.setStatusTimeout(1);// Is less than the delay() above\n\n\t\tDeploymentState state = this.deployer.status(\"test-application-id\").getState();\n\t\tassertThat(state).isEqualTo(DeploymentState.error);\n\t\tassertThat(i.get()).isEqualTo(0);\n\t}\n\n\t@SuppressWarnings(\"unchecked\")\n\t@Test\n\tpublic void undeploy() {\n\t\tgivenRequestGetApplication(\"test-application-id\", Mono.just(ApplicationDetail.builder()\n\t\t\t\t.diskQuota(0)\n\t\t\t\t.id(\"test-application-id\")\n\t\t\t\t.instances(1)\n\t\t\t\t.memoryLimit(0)\n\t\t\t\t.name(\"test-application\")\n\t\t\t\t.requestedState(\"RUNNING\")\n\t\t\t\t.runningInstances(1)\n\t\t\t\t.stack(\"test-stack\")\n\t\t\t\t.instanceDetail(InstanceDetail.builder().state(\"RUNNING\").index(\"1\").build())\n\t\t\t\t.build()));\n\t\tgivenRequestDeleteApplication(\"test-application-id\", Mono.empty());\n\n\t\tthis.deployer.undeploy(\"test-application-id\");\n\n\t}\n\n\t@SuppressWarnings(\"unchecked\")\n\t@Test\n\tpublic void scale() {\n\t\tgivenRequestGetApplication(\"test-application-id\", Mono.just(ApplicationDetail.builder()\n\t\t\t\t.diskQuota(0)\n\t\t\t\t.id(\"test-application-id\")\n\t\t\t\t.instances(2)\n\t\t\t\t.memoryLimit(0)\n\t\t\t\t.name(\"test-application\")\n\t\t\t\t.requestedState(\"RUNNING\")\n\t\t\t\t.runningInstances(2)\n\t\t\t\t.stack(\"test-stack\")\n\t\t\t\t.instanceDetail(InstanceDetail.builder().state(\"RUNNING\").index(\"1\").build())\n\t\t\t\t.build()));\n\t\tgivenRequestScaleApplication(\"test-application-id\", 2, 1024, 1024, Mono.empty());\n\t\tthis.deployer.scale(new AppScaleRequest(\"test-application-id\", 2));\n\t}\n}\n"
  },
  {
    "path": "spring-cloud-deployer-cloudfoundry/src/test/java/org/springframework/cloud/deployer/spi/cloudfoundry/CloudFoundryAppNameGeneratorTest.java",
    "content": "/*\n * Copyright 2016-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage org.springframework.cloud.deployer.spi.cloudfoundry;\n\nimport org.junit.jupiter.api.Test;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n\n/**\n * @author Soby Chacko\n * @author Mark Pollack\n */\npublic class CloudFoundryAppNameGeneratorTest {\n\n\t@Test\n\tpublic void testDeploymentIdWithAppNamePrefixAndRandomAppNamePrefixFalse() throws Exception {\n\t\tCloudFoundryDeploymentProperties properties = new CloudFoundryDeploymentProperties();\n\t\tproperties.setEnableRandomAppNamePrefix(false);\n\t\tproperties.setAppNamePrefix(\"dataflow\");\n\t\tCloudFoundryAppNameGenerator deploymentCustomizer =\n\t\t\t\tnew CloudFoundryAppNameGenerator(properties);\n\t\tdeploymentCustomizer.afterPropertiesSet();\n\n\t\tassertThat(deploymentCustomizer.generateAppName(\"foo\")).isEqualTo(\"dataflow-foo\");\n\t}\n\n\t@Test\n\tpublic void testDeploymentIdWithAppNamePrefixAndRandomAppNamePrefixTrue() throws Exception {\n\t\tCloudFoundryDeploymentProperties properties = new CloudFoundryDeploymentProperties();\n\t\tproperties.setEnableRandomAppNamePrefix(true);\n\t\tproperties.setAppNamePrefix(\"dataflow-longername\");\n\t\tCloudFoundryAppNameGenerator deploymentCustomizer =\n\t\t\t\tnew CloudFoundryAppNameGenerator(properties);\n\t\tdeploymentCustomizer.afterPropertiesSet();\n\n\t\tString deploymentIdWithUniquePrefix = deploymentCustomizer.generateAppName(\"foo\");\n\t\tassertThat(deploymentIdWithUniquePrefix).matches(\"dataflow-longername-\\\\w+-foo\");\n\n\t\tString deploymentIdWithUniquePrefixAgain = deploymentCustomizer.generateAppName(\"foo\");\n\n\t\tassertThat(deploymentIdWithUniquePrefix).isEqualTo(deploymentIdWithUniquePrefixAgain);\n\t}\n\n\t@Test\n\tpublic void testDeploymentIdWithoutAppNamePrefixAndRandomAppNamePrefixTrue() throws Exception {\n\t\tCloudFoundryDeploymentProperties properties = new CloudFoundryDeploymentProperties();\n\t\tproperties.setEnableRandomAppNamePrefix(true);\n\t\tproperties.setAppNamePrefix(\"\");\n\t\tCloudFoundryAppNameGenerator deploymentCustomizer =\n\t\t\t\tnew CloudFoundryAppNameGenerator(properties);\n\t\tdeploymentCustomizer.afterPropertiesSet();\n\n\t\tString deploymentIdWithUniquePrefix = deploymentCustomizer.generateAppName(\"foo\");\n\t\tassertThat(deploymentIdWithUniquePrefix).matches(\"\\\\w+-foo\");\n\t}\n\n\t@Test\n\tpublic void testDeploymentIdWithoutAppNamePrefixAndRandomAppNamePrefixFalse() throws Exception {\n\t\tCloudFoundryDeploymentProperties properties = new CloudFoundryDeploymentProperties();\n\t\tproperties.setEnableRandomAppNamePrefix(false);\n\t\tproperties.setAppNamePrefix(\"\");\n\t\tCloudFoundryAppNameGenerator deploymentCustomizer =\n\t\t\t\tnew CloudFoundryAppNameGenerator(properties);\n\t\tdeploymentCustomizer.afterPropertiesSet();\n\n\t\tassertThat(deploymentCustomizer.generateAppName(\"foo\")).isEqualTo(\"foo\");\n\t}\n}\n"
  },
  {
    "path": "spring-cloud-deployer-cloudfoundry/src/test/java/org/springframework/cloud/deployer/spi/cloudfoundry/CloudFoundryConnectionPropertiesTests.java",
    "content": "/*\n * Copyright 2019-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.cloudfoundry;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport org.junit.jupiter.api.Test;\n\nimport org.springframework.boot.context.properties.ConfigurationProperties;\nimport org.springframework.boot.context.properties.EnableConfigurationProperties;\nimport org.springframework.boot.test.context.runner.ApplicationContextRunner;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.core.env.StandardEnvironment;\nimport org.springframework.core.env.SystemEnvironmentPropertySource;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\npublic class CloudFoundryConnectionPropertiesTests {\n\n\tprivate final ApplicationContextRunner contextRunner = new ApplicationContextRunner();\n\n\t@Test\n\tpublic void setAllProperties() {\n\t\tthis.contextRunner\n\t\t.withInitializer(context -> {\n\t\t\tMap<String, Object> map = new HashMap<>();\n\t\t\tmap.put(\"spring.cloud.deployer.cloudfoundry.org\", \"org\");\n\t\t\tmap.put(\"spring.cloud.deployer.cloudfoundry.space\", \"space\");\n\t\t\tmap.put(\"spring.cloud.deployer.cloudfoundry.url\", \"http://example.com\");\n\t\t\tmap.put(\"spring.cloud.deployer.cloudfoundry.username\", \"username\");\n\t\t\tmap.put(\"spring.cloud.deployer.cloudfoundry.password\", \"password\");\n\t\t\tmap.put(\"spring.cloud.deployer.cloudfoundry.client-id\", \"id\");\n\t\t\tmap.put(\"spring.cloud.deployer.cloudfoundry.client-secret\", \"secret\");\n\t\t\tmap.put(\"spring.cloud.deployer.cloudfoundry.login-hint\", \"hint\");\n\t\t\tmap.put(\"spring.cloud.deployer.cloudfoundry.skip-ssl-validation\", \"true\");\n\t\t\tcontext.getEnvironment().getPropertySources().addLast(new SystemEnvironmentPropertySource(\n\t\t\t\tStandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, map));\n\t\t\t})\n\t\t\t.withUserConfiguration(Config1.class)\n\t\t\t.run((context) -> {\n\t\t\t\tCloudFoundryConnectionProperties properties = context.getBean(CloudFoundryConnectionProperties.class);\n\t\t\t\tassertThat(properties.getOrg()).isEqualTo(\"org\");\n\t\t\t\tassertThat(properties.getSpace()).isEqualTo(\"space\");\n\t\t\t\tassertThat(properties.getUrl().toString()).isEqualTo(\"http://example.com\");\n\t\t\t\tassertThat(properties.getUsername()).isEqualTo(\"username\");\n\t\t\t\tassertThat(properties.getPassword()).isEqualTo(\"password\");\n\t\t\t\tassertThat(properties.getClientId()).isEqualTo(\"id\");\n\t\t\t\tassertThat(properties.getClientSecret()).isEqualTo(\"secret\");\n\t\t\t\tassertThat(properties.getLoginHint()).isEqualTo(\"hint\");\n\t\t\t\tassertThat(properties.isSkipSslValidation()).isTrue();\n\t\t\t});\n\t}\n\n\t@EnableConfigurationProperties\n\tprivate static class Config1 {\n\n\t\t@Bean\n\t\t@ConfigurationProperties(prefix = CloudFoundryConnectionProperties.CLOUDFOUNDRY_PROPERTIES)\n\t\tpublic TestCloudFoundryConnectionProperties testCloudFoundryConnectionProperties() {\n\t\t\treturn new TestCloudFoundryConnectionProperties();\n\t\t}\n\t}\n\n\tprivate static class TestCloudFoundryConnectionProperties extends CloudFoundryConnectionProperties {\n\t\t// not to get Configuration Processor @Bean Duplicate Prefix Definition error\n\t}\n}\n"
  },
  {
    "path": "spring-cloud-deployer-cloudfoundry/src/test/java/org/springframework/cloud/deployer/spi/cloudfoundry/CloudFoundryDeployerTests.java",
    "content": "/*\n * Copyright 2020 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage org.springframework.cloud.deployer.spi.cloudfoundry;\n\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.Map;\nimport java.util.Set;\n\nimport org.junit.jupiter.api.Test;\nimport org.mockito.Answers;\nimport org.mockito.Mock;\n\nimport org.springframework.cloud.deployer.spi.core.AppDefinition;\nimport org.springframework.cloud.deployer.spi.core.AppDeploymentRequest;\nimport org.springframework.cloud.deployer.spi.core.RuntimeEnvironmentInfo;\nimport org.springframework.core.io.FileSystemResource;\nimport org.springframework.core.io.Resource;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\npublic class CloudFoundryDeployerTests {\n\n\t@Mock(answer = Answers.RETURNS_SMART_NULLS)\n\tprivate RuntimeEnvironmentInfo runtimeEnvironmentInfo;\n\n\tprivate Resource resource = new FileSystemResource(\"src/test/resources/demo-0.0.1-SNAPSHOT.jar\");\n\tprivate AppDefinition definition = new AppDefinition(\"test-application\", Collections.emptyMap());\n\n\t@Test\n\tpublic void testBuildpacksDefault() {\n\t\tCloudFoundryDeploymentProperties props = new CloudFoundryDeploymentProperties();\n\t\tTestCloudFoundryDeployer deployer = new TestCloudFoundryDeployer(props, runtimeEnvironmentInfo);\n\t\tAppDeploymentRequest request = new AppDeploymentRequest(definition, resource);\n\n\t\tSet<String> buildpacks = deployer.buildpacks(request);\n\t\tassertThat(buildpacks).hasSize(1);\n\t}\n\n\t@Test\n\tpublic void testBuildpacksSingleMultiLogic() {\n\t\tCloudFoundryDeploymentProperties props = new CloudFoundryDeploymentProperties();\n\t\tprops.setBuildpack(\"buildpack1\");\n\t\tTestCloudFoundryDeployer deployer = new TestCloudFoundryDeployer(props, runtimeEnvironmentInfo);\n\t\tMap<String, String> deploymentProperties = new HashMap<>();\n\t\tAppDeploymentRequest request = new AppDeploymentRequest(definition, resource, deploymentProperties, null);\n\n\n\t\tSet<String> buildpacks = deployer.buildpacks(request);\n\t\tassertThat(buildpacks).hasSize(1);\n\t\tassertThat(buildpacks).contains(\"buildpack1\");\n\n\t\tprops.setBuildpacks(new HashSet<>(Arrays.asList(\"buildpack2\", \"buildpack3\")));\n\t\tbuildpacks = deployer.buildpacks(request);\n\t\tassertThat(buildpacks).hasSize(2);\n\t\tassertThat(buildpacks).contains(\"buildpack2\", \"buildpack3\");\n\n\t\tdeploymentProperties.put(CloudFoundryDeploymentProperties.BUILDPACK_PROPERTY_KEY, \"buildpack4\");\n\t\tbuildpacks = deployer.buildpacks(request);\n\t\tassertThat(buildpacks).hasSize(1);\n\t\tassertThat(buildpacks).contains(\"buildpack4\");\n\n\t\tdeploymentProperties.put(CloudFoundryDeploymentProperties.BUILDPACKS_PROPERTY_KEY, \"buildpack5,buildpack6\");\n\t\tbuildpacks = deployer.buildpacks(request);\n\t\tassertThat(buildpacks).hasSize(2);\n\t\tassertThat(buildpacks).contains(\"buildpack5\", \"buildpack6\");\n\t}\n\n\tprivate static class TestCloudFoundryDeployer extends AbstractCloudFoundryDeployer {\n\n\t\tTestCloudFoundryDeployer(CloudFoundryDeploymentProperties deploymentProperties,\n\t\t\t\tRuntimeEnvironmentInfo runtimeEnvironmentInfo) {\n\t\t\tsuper(deploymentProperties, runtimeEnvironmentInfo);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "spring-cloud-deployer-cloudfoundry/src/test/java/org/springframework/cloud/deployer/spi/cloudfoundry/CloudFoundryTaskLauncherCachingTests.java",
    "content": "/*\n * Copyright 2020-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.cloudfoundry;\n\nimport java.time.LocalDateTime;\nimport java.time.format.DateTimeFormatter;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.UUID;\nimport java.util.concurrent.atomic.AtomicBoolean;\n\nimport org.cloudfoundry.client.CloudFoundryClient;\nimport org.cloudfoundry.client.v2.Metadata;\nimport org.cloudfoundry.client.v2.organizations.ListOrganizationsResponse;\nimport org.cloudfoundry.client.v2.organizations.OrganizationResource;\nimport org.cloudfoundry.client.v2.organizations.Organizations;\nimport org.cloudfoundry.client.v2.spaces.ListSpacesResponse;\nimport org.cloudfoundry.client.v2.spaces.SpaceResource;\nimport org.cloudfoundry.client.v2.spaces.Spaces;\nimport org.cloudfoundry.client.v3.Pagination;\nimport org.cloudfoundry.client.v3.tasks.ListTasksResponse;\nimport org.cloudfoundry.client.v3.tasks.TaskResource;\nimport org.cloudfoundry.client.v3.tasks.TaskState;\nimport org.cloudfoundry.client.v3.tasks.Tasks;\nimport org.cloudfoundry.logcache.v1.LogCacheClient;\nimport org.cloudfoundry.operations.CloudFoundryOperations;\nimport org.junit.jupiter.api.Test;\nimport reactor.core.publisher.Mono;\n\nimport org.springframework.cloud.deployer.spi.core.RuntimeEnvironmentInfo;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.catchThrowable;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.BDDMockito.given;\nimport static org.mockito.Mockito.mock;\n\npublic class CloudFoundryTaskLauncherCachingTests {\n\n\t@Test\n\tpublic void testOrgSpaceCachingRetries() {\n\t\tCloudFoundryClient client = mock(CloudFoundryClient.class);\n\t\tAtomicBoolean spaceError = new AtomicBoolean(true);\n\t\tAtomicBoolean orgError = new AtomicBoolean(true);\n\n\t\tSpaces spaces = mock(Spaces.class);\n\t\tgiven(client.spaces()).willReturn(spaces);\n\t\tgiven(spaces.list(any())).willReturn(listSpacesResponse(spaceError));\n\n\t\tOrganizations organizations = mock(Organizations.class);\n\t\tgiven(client.organizations()).willReturn(organizations);\n\t\tgiven(organizations.list(any())).willReturn(listOrganizationsResponse(orgError));\n\n\t\tTasks tasks = mock(Tasks.class);\n\t\tgiven(client.tasks()).willReturn(tasks);\n\t\tgiven(tasks.list(any())).willReturn(runningTasksResponse());\n\n\t\tLogCacheClient logCacheClient = mock(LogCacheClient.class);\n\n\t\tCloudFoundryDeploymentProperties deploymentProperties = new CloudFoundryDeploymentProperties();\n\t\tCloudFoundryOperations operations = mock(CloudFoundryOperations.class);\n\t\tRuntimeEnvironmentInfo runtimeEnvironmentInfo = mock(RuntimeEnvironmentInfo.class);\n\t\tMap<String, String> orgAndSpace = new HashMap<>();\n\t\torgAndSpace.put(CloudFoundryPlatformSpecificInfo.ORG, \"this-org\");\n\t\torgAndSpace.put(CloudFoundryPlatformSpecificInfo.SPACE, \"this-space\");\n\t\tgiven(runtimeEnvironmentInfo.getPlatformSpecificInfo()).willReturn(orgAndSpace);\n\n\t\tCloudFoundryTaskLauncher launcher = new CloudFoundryTaskLauncher(client, deploymentProperties, operations,\n\t\t\t\truntimeEnvironmentInfo, new ApplicationLogAccessor(logCacheClient));\n\n\t\tThrowable thrown1 = catchThrowable(() -> {\n\t\t\tlauncher.getRunningTaskExecutionCount();\n\t\t});\n\t\tassertThat(thrown1).isInstanceOf(RuntimeException.class).hasNoCause();\n\n\t\t// space should still error\n\t\torgError.set(false);\n\t\tThrowable thrown2 = catchThrowable(() -> {\n\t\t\tlauncher.getRunningTaskExecutionCount();\n\t\t});\n\t\tassertThat(thrown2).isInstanceOf(RuntimeException.class).hasNoCause();\n\n\t\t// cache should now be getting cleared as space doesn't error\n\t\tspaceError.set(false);\n\t\tThrowable thrown3 = catchThrowable(() -> {\n\t\t\tlauncher.getRunningTaskExecutionCount();\n\t\t});\n\t\tassertThat(thrown3).doesNotThrowAnyException();\n\t\tassertThat(launcher.getRunningTaskExecutionCount()).isEqualTo(1);\n\t}\n\n\tprivate Mono<ListOrganizationsResponse> listOrganizationsResponse(AtomicBoolean error) {\n\t\t// defer so that we can conditionally throw within mono\n\t\treturn Mono.defer(() -> {\n\t\t\tif (error.get()) {\n\t\t\t\tthrow new RuntimeException();\n\t\t\t}\n\t\t\tListOrganizationsResponse response = ListOrganizationsResponse.builder()\n\t\t\t\t.addAllResources(Collections.<OrganizationResource>singletonList(\n\t\t\t\t\tOrganizationResource.builder()\n\t\t\t\t\t\t\t.metadata(Metadata.builder().id(\"123\").build()).build())\n\t\t\t\t)\n\t\t\t\t.build();\n\t\t\treturn Mono.just(response);\n\t\t});\n\t}\n\n\tprivate Mono<ListSpacesResponse> listSpacesResponse(AtomicBoolean error) {\n\t\t// defer so that we can conditionally throw within mono\n\t\treturn Mono.defer(() -> {\n\t\t\tif (error.get()) {\n\t\t\t\tthrow new RuntimeException();\n\t\t\t}\n\t\t\tListSpacesResponse response = ListSpacesResponse.builder()\n\t\t\t\t.addAllResources(Collections.<SpaceResource>singletonList(\n\t\t\t\t\tSpaceResource.builder()\n\t\t\t\t\t\t\t.metadata(Metadata.builder().id(\"123\").build()).build())\n\t\t\t)\n\t\t\t.build();\n\t\t\treturn Mono.just(response);\n\t\t});\n\t}\n\n\tprivate Mono<ListTasksResponse> runningTasksResponse() {\n\t\tList<TaskResource> taskResources = new ArrayList<>();\n\t\tfor (int i = 0; i < 1; i++) {\n\t\t\ttaskResources.add(TaskResource.builder()\n\t\t\t\t.name(\"task-\" + i)\n\t\t\t\t.dropletId(UUID.randomUUID().toString())\n\t\t\t\t.id(UUID.randomUUID().toString())\n\t\t\t\t.diskInMb(2048)\n\t\t\t\t.sequenceId(i)\n\t\t\t\t.state(TaskState.RUNNING)\n\t\t\t\t.memoryInMb(2048)\n\t\t\t\t.createdAt(LocalDateTime.now().format(DateTimeFormatter.ofPattern(\"yyyy-MM-dd HH:mm:ss\")))\n\t\t\t\t.build());\n\t\t}\n\t\tListTasksResponse listTasksResponse = ListTasksResponse.builder().resources(taskResources)\n\t\t\t.pagination(Pagination.builder().totalResults(taskResources.size()).build())\n\t\t\t.build();\n\t\treturn Mono.just(listTasksResponse);\n\t}\n}\n"
  },
  {
    "path": "spring-cloud-deployer-cloudfoundry/src/test/java/org/springframework/cloud/deployer/spi/cloudfoundry/CloudFoundryTaskLauncherIntegrationIT.java",
    "content": "/*\n * Copyright 2016-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.cloudfoundry;\n\nimport com.github.zafarkhaja.semver.Version;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.Assumptions;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Disabled;\nimport org.junit.jupiter.api.Test;\n\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.boot.autoconfigure.EnableAutoConfiguration;\nimport org.springframework.boot.context.properties.EnableConfigurationProperties;\nimport org.springframework.cloud.deployer.spi.task.TaskLauncher;\nimport org.springframework.cloud.deployer.spi.test.AbstractTaskLauncherIntegrationJUnit5Tests;\nimport org.springframework.cloud.deployer.spi.test.Timeout;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.test.context.ContextConfiguration;\n\n/**\n * Runs integration tests for {@link CloudFoundryTaskLauncher}, using the production configuration,\n * that may be configured via {@link CloudFoundryConnectionProperties}.\n *\n * Tests are only run if a successful connection can be made at startup.\n *\n * @author Eric Bottard\n * @author Greg Turnquist\n * @author Michael Minella\n * @author Ben Hale\n */\n@ContextConfiguration(classes=CloudFoundryTaskLauncherIntegrationIT.Config.class)\npublic class CloudFoundryTaskLauncherIntegrationIT extends AbstractTaskLauncherIntegrationJUnit5Tests {\n\n\t@Autowired\n\tprivate TaskLauncher taskLauncher;\n\n\t@Autowired\n\tprivate Version cloudControllerAPIVersion;\n\n\t/**\n\t * Execution environments may override this default value to have tests wait longer for a deployment, for example if\n\t * running in an environment that is known to be slow.\n\t */\n\tprotected double timeoutMultiplier = 1.0D;\n\n\tprotected int maxRetries = 60;\n\n\t@BeforeEach\n\tpublic void init() {\n\t\tAssumptions.assumeTrue(cloudControllerAPIVersion.greaterThanOrEqualTo(Version.forIntegers(2, 65, 0)),\n\t\t\t\t\"Skipping TaskLauncher ITs on PCF<1.9 (2.65.0). Actual API version is \" + cloudControllerAPIVersion);\n\n\t\tString multiplier = System.getenv(\"CF_DEPLOYER_TIMEOUT_MULTIPLIER\");\n\t\tif (multiplier != null) {\n\t\t\ttimeoutMultiplier = Double.parseDouble(multiplier);\n\t\t}\n\t}\n\n\t@Override\n\tprotected TaskLauncher provideTaskLauncher() {\n\t\treturn taskLauncher;\n\t}\n\n\t/*\n\t * Allow for a small pause so that each each TL.destroy() at the end of tests actually completes,\n\t * as this is asynchronous.\n\t */\n\t@AfterEach\n\tpublic void pause() throws InterruptedException {\n\t\tThread.sleep(500);\n\t}\n\n\t@Test\n\t@Override\n\t@Disabled(\"CF Deployer incorrectly reports status as failed instead of canceled\")\n\tpublic void testSimpleCancel() throws InterruptedException {\n\t\tsuper.testSimpleCancel();\n\t}\n\n\t@Override\n\tprotected Timeout deploymentTimeout() {\n\t\treturn new Timeout(maxRetries, (int) (5000 * timeoutMultiplier));\n\t}\n\n\t@Override\n\tprotected Timeout undeploymentTimeout() {\n\t\treturn new Timeout(maxRetries, (int) (5000 * timeoutMultiplier));\n\t}\n\n\n\n\t/**\n\t * This triggers the use of {@link CloudFoundryDeployerAutoConfiguration}.\n\t *\n\t * @author Eric Bottard\n\t */\n\t@Configuration\n\t@EnableAutoConfiguration\n\t@EnableConfigurationProperties\n\tpublic static class Config {\n\n\t}\n\n}\n"
  },
  {
    "path": "spring-cloud-deployer-cloudfoundry/src/test/java/org/springframework/cloud/deployer/spi/cloudfoundry/CloudFoundryTaskLauncherTests.java",
    "content": "/*\n * Copyright 2016-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.cloudfoundry;\n\nimport java.io.IOException;\nimport java.time.Duration;\nimport java.time.LocalDateTime;\nimport java.time.format.DateTimeFormatter;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.Date;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.UUID;\n\nimport com.fasterxml.jackson.core.JsonProcessingException;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport org.cloudfoundry.client.CloudFoundryClient;\nimport org.cloudfoundry.client.v2.Metadata;\nimport org.cloudfoundry.client.v2.applications.ApplicationsV2;\nimport org.cloudfoundry.client.v2.applications.SummaryApplicationResponse;\nimport org.cloudfoundry.client.v2.organizations.ListOrganizationsResponse;\nimport org.cloudfoundry.client.v2.organizations.OrganizationResource;\nimport org.cloudfoundry.client.v2.organizations.Organizations;\nimport org.cloudfoundry.client.v2.spaces.ListSpacesResponse;\nimport org.cloudfoundry.client.v2.spaces.SpaceResource;\nimport org.cloudfoundry.client.v2.spaces.Spaces;\nimport org.cloudfoundry.client.v3.Pagination;\nimport org.cloudfoundry.client.v3.Relationship;\nimport org.cloudfoundry.client.v3.ToOneRelationship;\nimport org.cloudfoundry.client.v3.tasks.CancelTaskRequest;\nimport org.cloudfoundry.client.v3.tasks.CancelTaskResponse;\nimport org.cloudfoundry.client.v3.tasks.CreateTaskRequest;\nimport org.cloudfoundry.client.v3.tasks.CreateTaskResponse;\nimport org.cloudfoundry.client.v3.tasks.GetTaskRequest;\nimport org.cloudfoundry.client.v3.tasks.GetTaskResponse;\nimport org.cloudfoundry.client.v3.tasks.ListTasksResponse;\nimport org.cloudfoundry.client.v3.tasks.TaskRelationships;\nimport org.cloudfoundry.client.v3.tasks.TaskResource;\nimport org.cloudfoundry.client.v3.tasks.TaskState;\nimport org.cloudfoundry.client.v3.tasks.Tasks;\nimport org.cloudfoundry.doppler.Envelope;\nimport org.cloudfoundry.doppler.EventType;\nimport org.cloudfoundry.doppler.LogMessage;\nimport org.cloudfoundry.doppler.MessageType;\nimport org.cloudfoundry.logcache.v1.EnvelopeBatch;\nimport org.cloudfoundry.logcache.v1.LogCacheClient;\nimport org.cloudfoundry.logcache.v1.ReadResponse;\nimport org.cloudfoundry.operations.CloudFoundryOperations;\nimport org.cloudfoundry.operations.applications.ApplicationDetail;\nimport org.cloudfoundry.operations.applications.ApplicationHealthCheck;\nimport org.cloudfoundry.operations.applications.ApplicationManifest;\nimport org.cloudfoundry.operations.applications.ApplicationSummary;\nimport org.cloudfoundry.operations.applications.Applications;\nimport org.cloudfoundry.operations.applications.DeleteApplicationRequest;\nimport org.cloudfoundry.operations.applications.GetApplicationRequest;\nimport org.cloudfoundry.operations.applications.PushApplicationManifestRequest;\nimport org.cloudfoundry.operations.applications.StopApplicationRequest;\nimport org.cloudfoundry.operations.services.Services;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.mockito.Answers;\nimport org.mockito.Mock;\nimport org.mockito.MockitoAnnotations;\n\nimport reactor.core.publisher.Flux;\nimport reactor.core.publisher.Mono;\n\nimport org.springframework.cloud.deployer.spi.core.AppDefinition;\nimport org.springframework.cloud.deployer.spi.core.AppDeploymentRequest;\nimport org.springframework.cloud.deployer.spi.core.RuntimeEnvironmentInfo;\nimport org.springframework.cloud.deployer.spi.task.LaunchState;\nimport org.springframework.cloud.deployer.spi.task.TaskStatus;\nimport org.springframework.cloud.deployer.spi.util.ByteSizeUtils;\nimport org.springframework.core.io.FileSystemResource;\nimport org.springframework.core.io.Resource;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.assertThatThrownBy;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.BDDMockito.given;\nimport static org.mockito.Mockito.mock;\n\n/**\n * @author Michael Minella\n * @author Ben Hale\n * @author Glenn Renfro\n * @author David Turanski\n * @author David Bernard\n */\npublic class CloudFoundryTaskLauncherTests {\n\tprivate final static int TASK_EXECUTION_COUNT = 10;\n\n\tprivate final static String LOG_RESPONSE = \"Test Log Response\";\n\n\tprivate final CloudFoundryDeploymentProperties deploymentProperties = new CloudFoundryDeploymentProperties();\n\n\t@Mock(answer = Answers.RETURNS_SMART_NULLS)\n\tprivate Applications applications;\n\n\t@Mock(answer = Answers.RETURNS_SMART_NULLS)\n\tprivate ApplicationsV2 applicationsV2;\n\n\t@Mock(answer = Answers.RETURNS_SMART_NULLS)\n\tprivate CloudFoundryClient client;\n\n\tprivate CloudFoundryTaskLauncher launcher;\n\n\t@Mock(answer = Answers.RETURNS_SMART_NULLS)\n\tprivate CloudFoundryOperations operations;\n\n\t@Mock(answer = Answers.RETURNS_SMART_NULLS)\n\tprivate Services services;\n\n\t@Mock(answer = Answers.RETURNS_SMART_NULLS)\n\tprivate Spaces spaces;\n\n\t@Mock(answer = Answers.RETURNS_SMART_NULLS)\n\tprivate Organizations organizations;\n\n\t@Mock(answer = Answers.RETURNS_SMART_NULLS)\n\tprivate Tasks tasks;\n\n\tprivate Resource resource = new FileSystemResource(\"src/test/resources/demo-0.0.1-SNAPSHOT.jar\");\n\n\t@BeforeEach\n\tvoid setUp() {\n\t\tMockitoAnnotations.initMocks(this);\n\t\tgiven(this.tasks.list(any())).willReturn(this.runningTasksResponse());\n\t\tgiven(this.client.applicationsV2()).willReturn(this.applicationsV2);\n\t\tgiven(this.client.tasks()).willReturn(this.tasks);\n\n\t\tgiven(this.operations.applications()).willReturn(this.applications);\n\t\tgiven(this.operations.services()).willReturn(this.services);\n\t\tgiven(this.client.spaces()).willReturn(this.spaces);\n\t\tgiven(this.client.organizations()).willReturn(this.organizations);\n\t\tMono<GetTaskResponse> getTaskResponse = getDefaultGetTaskResponse();\n\t\tgiven(this.tasks.get(any())).willReturn(getTaskResponse);\n\n\t\tRuntimeEnvironmentInfo runtimeEnvironmentInfo = mock(RuntimeEnvironmentInfo.class);\n\t\tMap<String, String> orgAndSpace = new HashMap<>();\n\t\torgAndSpace.put(CloudFoundryPlatformSpecificInfo.ORG, \"this-org\");\n\t\torgAndSpace.put(CloudFoundryPlatformSpecificInfo.SPACE, \"this-space\");\n\t\tgiven(runtimeEnvironmentInfo.getPlatformSpecificInfo()).willReturn(orgAndSpace);\n\t\tgiven(this.organizations.list(any())).willReturn(listOrganizationsResponse());\n\t\tgiven(this.spaces.list(any())).willReturn(listSpacesResponse());\n\n\t\tApplicationLogAccessor applicationLogAccessor = mock(ApplicationLogAccessor.class);\n\t\tgiven(applicationLogAccessor.getLog(any(), any())).willReturn(LOG_RESPONSE);\n\n\t\tthis.launcher = getCloudFoundryTaskLauncher(applicationLogAccessor);\n\n\t}\n\n\tprivate CloudFoundryTaskLauncher getCloudFoundryTaskLauncher(ApplicationLogAccessor applicationLogAccessor) {\n\t\tRuntimeEnvironmentInfo runtimeEnvironmentInfo = mock(RuntimeEnvironmentInfo.class);\n\t\tMap<String, String> orgAndSpace = new HashMap<>();\n\t\torgAndSpace.put(CloudFoundryPlatformSpecificInfo.ORG, \"this-org\");\n\t\torgAndSpace.put(CloudFoundryPlatformSpecificInfo.SPACE, \"this-space\");\n\t\tgiven(runtimeEnvironmentInfo.getPlatformSpecificInfo()).willReturn(orgAndSpace);\n\t\tgiven(this.organizations.list(any())).willReturn(listOrganizationsResponse());\n\t\tgiven(this.spaces.list(any())).willReturn(listSpacesResponse());\n\n\t\tthis.deploymentProperties.setApiTimeout(1);\n\t\tthis.deploymentProperties.setStatusTimeout(1_250L);\n\t\treturn new CloudFoundryTaskLauncher(this.client,\n\t\t\t\tthis.deploymentProperties,\n\t\t\t\tthis.operations,\n\t\t\t\truntimeEnvironmentInfo,\n\t\t\t\tapplicationLogAccessor);\n\t}\n\n\t@Test\n\tvoid cancel() {\n\t\tgivenRequestCancelTask(\"test-task-id\", Mono.just(CancelTaskResponse.builder()\n\t\t\t.id(\"test-task-id\")\n\t\t\t.memoryInMb(1024)\n\t\t\t.diskInMb(1024)\n\t\t\t.dropletId(\"1\")\n\t\t\t.createdAt(new Date().toString())\n\t\t\t.updatedAt(new Date().toString())\n\t\t\t.sequenceId(1)\n\t\t\t.name(\"test-task-id\")\n\t\t\t.state(TaskState.CANCELING)\n\t\t\t.build()));\n\n\t\tthis.launcher.cancel(\"test-task-id\");\n\t}\n\n\t@Test\n\tvoid currentExecutionCount() {\n\t\tassertThat(this.launcher.getRunningTaskExecutionCount()).isEqualTo(this.TASK_EXECUTION_COUNT);\n\t}\n\n\t@Test\n\tvoid launchTaskApplicationExists() throws IOException{\n\t\tsetupExistingAppSuccessful();\n\t\tgivenRequestPushApplication(\n\t\t\t\tPushApplicationManifestRequest.builder()\n\t\t\t\t\t\t.manifest(ApplicationManifest.builder()\n\t\t\t\t\t\t\t\t.path(resource.getFile().toPath())\n\t\t\t\t\t\t\t\t.buildpack(deploymentProperties.getBuildpack())\n\t\t\t\t\t\t\t\t.command(\"echo '*** First run of container to allow droplet creation.***' && sleep 300\")\n\t\t\t\t\t\t\t\t.disk((int) ByteSizeUtils.parseToMebibytes(this.deploymentProperties.getDisk()))\n\t\t\t\t\t\t\t\t.environmentVariable(\"SPRING_APPLICATION_JSON\", \"{}\")\n\t\t\t\t\t\t\t\t.healthCheckType(ApplicationHealthCheck.NONE)\n\t\t\t\t\t\t\t\t.memory((int) ByteSizeUtils.parseToMebibytes(this.deploymentProperties.getMemory()))\n\t\t\t\t\t\t\t\t.name(\"test-application\")\n\t\t\t\t\t\t\t\t.noRoute(true)\n\t\t\t\t\t\t\t\t.services(Collections.emptySet())\n\t\t\t\t\t\t\t\t.build())\n\t\t\t\t\t\t.stagingTimeout(this.deploymentProperties.getStagingTimeout())\n\t\t\t\t\t\t.startupTimeout(this.deploymentProperties.getStartupTimeout())\n\t\t\t\t\t\t.build(), Mono.empty());\n\t\tString taskId = this.launcher.launch(defaultRequest());\n\n\t\tassertThat(taskId).isEqualTo(\"test-task-id\");\n\t}\n\n\t@Test\n\tvoid stageTaskApplicationExists() throws IOException{\n\t\tsetupExistingAppSuccessful();\n\t\tgivenRequestPushApplication(\n\t\t\t\tPushApplicationManifestRequest.builder()\n\t\t\t\t\t\t.manifest(ApplicationManifest.builder()\n\t\t\t\t\t\t\t\t.path(resource.getFile().toPath())\n\t\t\t\t\t\t\t\t.buildpack(deploymentProperties.getBuildpack())\n\t\t\t\t\t\t\t\t.command(\"echo '*** First run of container to allow droplet creation.***' && sleep 300\")\n\t\t\t\t\t\t\t\t.disk((int) ByteSizeUtils.parseToMebibytes(this.deploymentProperties.getDisk()))\n\t\t\t\t\t\t\t\t.environmentVariable(\"SPRING_APPLICATION_JSON\", \"{}\")\n\t\t\t\t\t\t\t\t.healthCheckType(ApplicationHealthCheck.NONE)\n\t\t\t\t\t\t\t\t.memory((int) ByteSizeUtils.parseToMebibytes(this.deploymentProperties.getMemory()))\n\t\t\t\t\t\t\t\t.name(\"test-application\")\n\t\t\t\t\t\t\t\t.noRoute(true)\n\t\t\t\t\t\t\t\t.services(Collections.emptySet())\n\t\t\t\t\t\t\t\t.build())\n\t\t\t\t\t\t.stagingTimeout(this.deploymentProperties.getStagingTimeout())\n\t\t\t\t\t\t.startupTimeout(this.deploymentProperties.getStartupTimeout())\n\t\t\t\t\t\t.build(), Mono.empty());\n\t\tSummaryApplicationResponse response = this.launcher.stage(defaultRequest());\n\n\t\tassertThat(response.getId()).isEqualTo(\"test-application-id\");\n\t\tassertThat(response.getDetectedStartCommand()).isEqualTo(\"test-command\");\n\t}\n\n\t@Test\n\tvoid launchTaskWithNonExistentApplication() throws IOException {\n\t\tsetupTaskWithNonExistentApplication(this.resource);\n\t\tString taskId = this.launcher.launch(defaultRequest());\n\t\tassertThat(taskId).isEqualTo(\"test-task-id\");\n\t}\n\n\t@Test\n\tvoid launchExistingTaskApplicationWithPushDisabled() {\n\t\tsetupExistingAppSuccessful();\n\t\tdeploymentProperties.setPushTaskAppsEnabled(false);\n\t\tString taskId = this.launcher.launch(defaultRequest());\n\t\tassertThat(taskId).isEqualTo(\"test-task-id\");\n\t}\n\n\t@Test\n\tvoid launchNonExistingTaskApplicationWithPushDisabled() throws IOException {\n\t\tsetupTaskWithNonExistentApplication(this.resource);\n\t\tdeploymentProperties.setPushTaskAppsEnabled(false);\n\t\tassertThatThrownBy(() -> {\n\t\t\tthis.launcher.launch(defaultRequest());\n\t\t}).isInstanceOf(IllegalStateException.class);\n\t}\n\n\t@Test\n\tvoid stageTaskWithNonExistentApplication() throws IOException {\n\t\tsetupTaskWithNonExistentApplication(this.resource);\n\n\t\tSummaryApplicationResponse response = this.launcher.stage(defaultRequest());\n\t\tassertThat(response.getId()).isEqualTo(\"test-application-id\");\n\t\tassertThat(response.getDetectedStartCommand()).isEqualTo(\"test-command\");\n\t}\n\n\t@Test\n\tvoid automaticallyConfigureForCfEnv() throws JsonProcessingException {\n\t\tResource resource = new FileSystemResource(\"src/test/resources/log-sink-rabbit-3.0.0.BUILD-SNAPSHOT.jar\");\n\t\tAppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(new AppDefinition(\"test-application\",\n\t\t\t\t\tCollections.emptyMap()), resource, Collections.emptyMap());\n\n\t\tMap<String, String> env =  launcher.mergeEnvironmentVariables(\"test-application-id\", appDeploymentRequest);\n\t\t// MatcherAssert.assertThat(env, hasEntry(CfEnvConfigurer.JBP_CONFIG_SPRING_AUTO_RECONFIGURATION, CfEnvConfigurer.ENABLED_FALSE));\n\t\t// MatcherAssert.assertThat(env, hasKey(CoreMatchers.equalTo(\"SPRING_APPLICATION_JSON\")));\n\t\t// ObjectMapper objectMapper = new ObjectMapper();\n\t\t// Map<String, String> saj = objectMapper.readValue(env.get(\"SPRING_APPLICATION_JSON\"),HashMap.class);\n\t\t// MatcherAssert.assertThat(saj, hasEntry(CfEnvConfigurer.SPRING_PROFILES_ACTIVE_FQN, CfEnvConfigurer.CLOUD_PROFILE_NAME));\n\t\tassertThat(env).containsEntry(CfEnvConfigurer.JBP_CONFIG_SPRING_AUTO_RECONFIGURATION, CfEnvConfigurer.ENABLED_FALSE);\n\t\tassertThat(env).containsKeys(\"SPRING_APPLICATION_JSON\");\n\t\tObjectMapper objectMapper = new ObjectMapper();\n\t\tMap<String, String> saj = objectMapper.readValue(env.get(\"SPRING_APPLICATION_JSON\"), HashMap.class);\n\t\tassertThat(saj).containsEntry(CfEnvConfigurer.SPRING_PROFILES_ACTIVE_FQN, CfEnvConfigurer.CLOUD_PROFILE_NAME);\n\t}\n\n\n\t@Test\n\tvoid launchTaskWithNonExistentApplicationAndApplicationListingFails() {\n\t\tgivenRequestListApplications(Flux.error(new UnsupportedOperationException()));\n\t\tgiven(this.operations.applications().pushManifest(any())).willThrow(new UnsupportedOperationException());\n\n\t\tassertThatThrownBy(() -> {\n\t\t\tthis.launcher.launch(defaultRequest());\n\t\t}).isInstanceOf(UnsupportedOperationException.class);\n\t}\n\n\t@Test\n\tvoid stageTaskWithNonExistentApplicationAndApplicationListingFails() {\n\t\tgivenRequestListApplications(Flux.error(new UnsupportedOperationException()));\n\t\tgiven(this.operations.applications().pushManifest(any())).willThrow(new UnsupportedOperationException());\n\n\t\tassertThatThrownBy(() -> {\n\t\t\tthis.launcher.stage(defaultRequest());\n\t\t}).isInstanceOf(UnsupportedOperationException.class);\n\t}\n\n\t@Test\n\tvoid launchTaskWithNonExistentApplicationAndPushFails() throws IOException {\n\t\tsetupFailedPush(this.resource);\n\n\t\tassertThatThrownBy(() -> {\n\t\t\tthis.launcher.launch(defaultRequest());\n\t\t}).isInstanceOf(UnsupportedOperationException.class);\n\t}\n\n\t@Test\n\tvoid stageTaskWithNonExistentApplicationAndPushFails() throws IOException {\n\t\tsetupFailedPush(this.resource);\n\n\t\tassertThatThrownBy(() -> {\n\t\t\tthis.launcher.stage(defaultRequest());\n\t\t}).isInstanceOf(UnsupportedOperationException.class);\n\t}\n\n\t@Test\n\tvoid launchTaskWithNonExistentApplicationAndRetrievingApplicationSummaryFails() throws IOException {\n\t\tsetupTaskWithNonExistentApplicationAndRetrievingApplicationSummaryFails(this.resource);\n\n\t\tassertThatThrownBy(() -> {\n\t\t\tthis.launcher.launch(defaultRequest());\n\t\t}).isInstanceOf(UnsupportedOperationException.class);\n\t}\n\n\t@Test\n\tvoid stageTaskWithNonExistentApplicationAndRetrievingApplicationSummaryFails() throws IOException {\n\t\tsetupTaskWithNonExistentApplicationAndRetrievingApplicationSummaryFails(this.resource);\n\n\t\tassertThatThrownBy(() -> {\n\t\t\tthis.launcher.stage(defaultRequest());\n\t\t}).isInstanceOf(UnsupportedOperationException.class);\n\t}\n\n\t@Test\n\tvoid launchTaskWithNonExistentApplicationAndStoppingApplicationFails() throws IOException {\n\t\tsetupTaskWithNonExistentApplicationAndStoppingApplicationFails(this.resource);\n\n\t\tassertThatThrownBy(() -> {\n\t\t\tthis.launcher.launch(defaultRequest());\n\t\t}).isInstanceOf(UnsupportedOperationException.class);\n\t}\n\n\t@Test\n\tvoid stageTaskWithNonExistentApplicationAndStoppingApplicationFails() throws IOException {\n\t\tsetupTaskWithNonExistentApplicationAndStoppingApplicationFails(this.resource);\n\n\t\tassertThatThrownBy(() -> {\n\t\t\tthis.launcher.stage(defaultRequest());\n\t\t}).isInstanceOf(UnsupportedOperationException.class);\n\t}\n\n\t@Test\n\tvoid launchTaskWithNonExistentApplicationAndTaskCreationFails() throws IOException {\n\t\tgivenRequestListApplications(Flux.empty());\n\n\t\tgivenRequestPushApplication(PushApplicationManifestRequest.builder()\n\t\t\t.manifest(ApplicationManifest.builder()\n\t\t\t\t.path(this.resource.getFile().toPath())\n\t\t\t\t.buildpack(deploymentProperties.getBuildpack())\n\t\t\t\t.command(\"echo '*** First run of container to allow droplet creation.***' && sleep 300\")\n\t\t\t\t.disk((int) ByteSizeUtils.parseToMebibytes(this.deploymentProperties.getDisk()))\n\t\t\t\t.environmentVariable(\"SPRING_APPLICATION_JSON\", \"{}\")\n\t\t\t\t.healthCheckType(ApplicationHealthCheck.NONE)\n\t\t\t\t.memory((int) ByteSizeUtils.parseToMebibytes(this.deploymentProperties.getMemory()))\n\t\t\t\t.name(\"test-application\")\n\t\t\t\t.noRoute(true)\n\t\t\t\t.services(Collections.emptySet())\n\t\t\t\t.build())\n\t\t\t.stagingTimeout(this.deploymentProperties.getStagingTimeout())\n\t\t\t.startupTimeout(this.deploymentProperties.getStartupTimeout())\n\t\t\t.build(), Mono.empty());\n\n\t\tgivenRequestGetApplication(\"test-application\", Mono.just(ApplicationDetail.builder()\n\t\t\t.diskQuota(0)\n\t\t\t.id(\"test-application-id\")\n\t\t\t.instances(1)\n\t\t\t.memoryLimit(0)\n\t\t\t.name(\"test-application\")\n\t\t\t.requestedState(\"RUNNING\")\n\t\t\t.runningInstances(1)\n\t\t\t.stack(\"test-stack\")\n\t\t\t.build()));\n\n\t\tgivenRequestStopApplication(\"test-application\", Mono.empty());\n\n\t\tgivenRequestGetApplicationSummary(\"test-application-id\",\n\t\t\t\tMono.just(SummaryApplicationResponse.builder()\n\t\t\t.id(\"test-application-id\")\n\t\t\t.detectedStartCommand(\"test-command\")\n\t\t\t.build()));\n\n\t\tgivenRequestCreateTask(\"test-application-id\",\n\t\t\t\t\"test-command\",\n\t\t\t\tthis.deploymentProperties.getMemory(),\n\t\t\t\tthis.deploymentProperties.getDisk(),\n\t\t\t\t\"test-application\",\n\t\t\t\tMono.error(new UnsupportedOperationException()));\n\n\t\tassertThatThrownBy(() -> {\n\t\t\tthis.launcher.launch(defaultRequest());\n\t\t}).isInstanceOf(UnsupportedOperationException.class);\n\t}\n\n\t@Test\n\tvoid launchTaskWithNonExistentApplicationBindingOneService() throws IOException {\n\t\tsetupTaskWithNonExistentApplicationBindingOneService(this.resource);\n\t\tAppDeploymentRequest request = deploymentRequest(this.resource,\n\t\t\tCollections.singletonMap(CloudFoundryDeploymentProperties.SERVICES_PROPERTY_KEY, \"test-service-instance-2\"));\n\n\t\tString taskId = this.launcher.launch(request);\n\t\tassertThat(taskId).isEqualTo(\"test-task-id\");\n\t}\n\n\t@Test\n\tvoid stageTaskWithNonExistentApplicationBindingOneService() throws IOException {\n\t\tsetupTaskWithNonExistentApplicationBindingOneService(this.resource);\n\t\tAppDeploymentRequest request = deploymentRequest(this.resource,\n\t\t\t\tCollections.singletonMap(CloudFoundryDeploymentProperties.SERVICES_PROPERTY_KEY, \"test-service-instance-2\"));\n\n\t\tSummaryApplicationResponse response = this.launcher.stage(request);\n\t\tassertThat(response.getId()).isEqualTo(\"test-application-id\");\n\t\tassertThat(response.getDetectedStartCommand()).isEqualTo(\"test-command\");\n\t}\n\n\t@Test\n\tvoid launchTaskWithNonExistentApplicationBindingThreeServices() throws IOException {\n\t\tsetupTaskWithNonExistentApplicationBindingThreeServices(this.resource);\n\t\tAppDeploymentRequest request = deploymentRequest(this.resource,\n\t\t\tCollections.singletonMap(CloudFoundryDeploymentProperties.SERVICES_PROPERTY_KEY,\n\t\t\t\t\t\"test-service-instance-1,test-service-instance-2,test-service-instance-3\"));\n\n\t\tString taskId = this.launcher.launch(request);\n\n\t\tassertThat(taskId).isEqualTo(\"test-task-id\");\n\t}\n\t@Test\n\tvoid stageTaskWithNonExistentApplicationBindingThreeServices() throws IOException {\n\t\tsetupTaskWithNonExistentApplicationBindingThreeServices(this.resource);\n\t\tAppDeploymentRequest request = deploymentRequest(this.resource,\n\t\t\t\tCollections.singletonMap(CloudFoundryDeploymentProperties.SERVICES_PROPERTY_KEY,\n\t\t\t\t\t\t\"test-service-instance-1,test-service-instance-2,test-service-instance-3\"));\n\n\t\tSummaryApplicationResponse response = this.launcher.stage(request);\n\t\tassertThat(response.getId()).isEqualTo(\"test-application-id\");\n\t\tassertThat(response.getDetectedStartCommand()).isEqualTo(\"test-command\");\n\t}\n\n\t@Test\n\tvoid launchTaskWithNonExistentApplicationRetrievalFails() throws IOException {\n\t\tsetupTaskWithNonExistentApplicationRetrievalFails(this.resource);\n\n\t\tassertThatThrownBy(() -> {\n\t\t\tthis.launcher.launch(defaultRequest());\n\t\t}).isInstanceOf(UnsupportedOperationException.class);\n\t}\n\n\t@Test\n\tvoid stageTaskWithNonExistentApplicationRetrievalFails() throws IOException {\n\t\tsetupTaskWithNonExistentApplicationRetrievalFails(this.resource);\n\n\t\tassertThatThrownBy(() -> {\n\t\t\tthis.launcher.stage(defaultRequest());\n\t\t}).isInstanceOf(UnsupportedOperationException.class);\n\t}\n\n\t@Test\n\tvoid status() {\n\t\tToOneRelationship toOneRelationship = ToOneRelationship.builder()\n\t\t\t\t.data(Relationship.builder().id(\"task-app-guid\").build()).build();\n\t\tTaskRelationships taskRelationships = TaskRelationships.builder().app(toOneRelationship).build();\n\t\tgivenRequestGetTask(\"test-task-id\", getDefaultGetTaskResponse());\n\n\t\tTaskStatus status = this.launcher.status(\"test-task-id\");\n\n\t\tassertThat(status.getState()).isEqualTo(LaunchState.complete);\n\t}\n\n\tprivate Mono<GetTaskResponse> getDefaultGetTaskResponse() {\n\t\tToOneRelationship toOneRelationship = ToOneRelationship.builder()\n\t\t\t\t.data(Relationship.builder().id(\"task-app-guid\").build()).build();\n\t\tTaskRelationships taskRelationships = TaskRelationships.builder().app(toOneRelationship).build();\n\t\treturn Mono.just(GetTaskResponse.builder()\n\t\t\t\t.id(\"test-task-id\")\n\t\t\t\t.memoryInMb(1024)\n\t\t\t\t.diskInMb(1024)\n\t\t\t\t.dropletId(\"1\")\n\t\t\t\t.createdAt(new Date().toString())\n\t\t\t\t.updatedAt(new Date().toString())\n\t\t\t\t.sequenceId(1)\n\t\t\t\t.taskRelationships(taskRelationships)\n\t\t\t\t.name(\"test-task-id\")\n\t\t\t\t.state(TaskState.SUCCEEDED)\n\t\t\t\t.build());\n\t}\n\n\t@Test\n\tvoid statusTimeout() {\n\t\t// Delay twice as much as 40% of statusTimeout, which is what the deployer uses\n\t\tlong delay = (long) (this.deploymentProperties.getStatusTimeout() * .4f * 2);\n\n\t\tgivenRequestGetTask(\"test-task-id\", Mono\n\t\t\t.delay(Duration.ofMillis(delay))\n\t\t\t.then(Mono.just(GetTaskResponse.builder()\n\t\t\t\t.id(\"test-task-id\")\n\t\t\t\t\t.memoryInMb(1024)\n\t\t\t\t\t.diskInMb(1024)\n\t\t\t\t\t.dropletId(\"1\")\n\t\t\t\t\t.createdAt(new Date().toString())\n\t\t\t\t\t.updatedAt(new Date().toString())\n\t\t\t\t\t.sequenceId(1)\n\t\t\t\t\t.name(\"test-task-id\")\n\t\t\t\t.state(TaskState.SUCCEEDED)\n\t\t\t\t.build())));\n\n\t\tassertThat(this.launcher.status(\"test-task-id\").getState()).isEqualTo(LaunchState.error);\n\t}\n\n\t@Test\n\tvoid destroyApp() {\n\t\tgivenRequestDeleteApplication(\"test-application\");\n\n\t\tthis.launcher.destroy(\"test-application\");\n\t}\n\n\t@Test\n\tvoid commandProperlyConfigured() {\n\t\tAppDeploymentRequest request = new AppDeploymentRequest(new AppDefinition(\n\t\t\t\t\"test-app-1\", null),\n\t\t\t\tthis.resource,\n\t\t\t\tCollections.singletonMap(\"test-key-1\", \"test-val-1\"),\n\t\t\t\tCollections.singletonList(\"test-command-arg-1\"));\n\t\tString command = this.launcher.getCommand(SummaryApplicationResponse\n\t\t\t\t.builder()\n\t\t\t\t.detectedStartCommand(\"command-val\")\n\t\t\t\t.build(),\n\t\t\t\trequest);\n\t\tassertThat(command).isEqualTo(\"command-val test-command-arg-1\");\n\n\t\tList<String> args = new ArrayList<>();\n\t\targs.add(\"test-command-arg-1\");\n\t\targs.add(\"a=b\");\n\t\targs.add(\"run.id=1\");\n\t\targs.add(\"run.id(long)=1\");\n\t\targs.add(\"run.id(long=1\");\n\t\targs.add(\"run.id)=1\");\n\t\trequest = new AppDeploymentRequest(new AppDefinition(\n\t\t\t\t\"test-app-1\", null),\n\t\t\t\tthis.resource,\n\t\t\t\tCollections.singletonMap(\"test-key-1\", \"test-val-1\"),\n\t\t\t\targs);\n\t\tcommand = this.launcher.getCommand(SummaryApplicationResponse\n\t\t\t\t\t\t.builder()\n\t\t\t\t\t\t.detectedStartCommand(\"command-val\")\n\t\t\t\t\t\t.build(),\n\t\t\t\trequest);\n\t\tassertThat(command).isEqualTo(\"command-val test-command-arg-1 a=b run.id=1 run.id\\\\\\\\\\\\(long\\\\\\\\\\\\)=1 run.id\\\\\\\\\\\\(long=1 run.id\\\\\\\\\\\\)=1\");\n\t}\n\n\t@Test\n\tvoid getLog() {\n\t\tassertThat(this.launcher.getLog(\"agcd\")).isEqualTo(LOG_RESPONSE);\n\t}\n\t@Test\n\tvoid getLogForUnknownId() {\n\t\tLogCacheClient logCacheClient = mock(LogCacheClient.class);\n\t\tApplicationLogAccessor applicationLogAccessor = new ApplicationLogAccessor(logCacheClient);\n\t\tEnvelope.builder().logMessage(LogMessage.builder().message(\"\").messageType(MessageType.OUT).timestamp(0l)\n\t\t\t\t.build()).eventType(EventType.LOG_MESSAGE).origin(\"foo\").build();\n\t\tEnvelopeBatch envelopeBatch = EnvelopeBatch.builder().batch().build();\n\t\tReadResponse response = ReadResponse.builder().envelopes(envelopeBatch).build();\n\t\tgiven(logCacheClient.read(any())).willReturn(Mono.just(response));\n\n\t\tthis.launcher = getCloudFoundryTaskLauncher(applicationLogAccessor);\n\t\tassertThat(this.launcher.getLog(\"agcd\")).isEmpty();\n\t}\n\n\t@Test\n\tvoid getLogWithUnknownTaskAppGuid() {\n\t\tToOneRelationship toOneRelationship = ToOneRelationship.builder()\n\t\t\t\t.data(Relationship.builder().id(\"task-app-guid\").build()).build();\n\t\tTaskRelationships taskRelationships = TaskRelationships.builder().app(toOneRelationship).build();\n\t\tMono<GetTaskResponse> getTaskResponse =  Mono.just(GetTaskResponse.builder()\n\t\t\t\t.id(\"test-task-id\")\n\t\t\t\t.memoryInMb(1024)\n\t\t\t\t.diskInMb(1024)\n\t\t\t\t.dropletId(\"1\")\n\t\t\t\t.createdAt(new Date().toString())\n\t\t\t\t.updatedAt(new Date().toString())\n\t\t\t\t.sequenceId(1)\n\t\t\t\t.state(TaskState.FAILED)\n\t\t\t\t.name(\"TaskAppNotPresent\")\n\t\t\t\t.build());\n\t\tgiven(this.tasks.get(any())).willReturn(getTaskResponse);\n\t\tassertThatThrownBy(() -> {\n\t\t\tassertThat(this.launcher.getLog(\"agcd\")).isEqualTo(LOG_RESPONSE);\n\t\t}).isInstanceOf(IllegalArgumentException.class)\n\t\t\t\t.hasMessageContaining(\"could not find a GUID app id for the task guid id agcd\");\n\t}\n\n\tprivate void givenRequestCancelTask(String taskId, Mono<CancelTaskResponse> response) {\n\t\tgiven(this.client.tasks()\n\t\t\t.cancel(CancelTaskRequest.builder()\n\t\t\t\t.taskId(taskId)\n\t\t\t\t.build()))\n\t\t\t.willReturn(response);\n\t}\n\n\tprivate void givenRequestCreateTask(String applicationId,\n\t\t\t\t\t\t\t\t\t\tString command,\n\t\t\t\t\t\t\t\t\t\tString memory,\n\t\t\t\t\t\t\t\t\t\tString disk,\n\t\t\t\t\t\t\t\t\t\tString name,\n\t\t\t\t\t\t\t\t\t\tMono<CreateTaskResponse> response) {\n\n\t\tgiven(this.client.tasks()\n\t\t\t.create(CreateTaskRequest.builder()\n\t\t\t\t.applicationId(applicationId)\n\t\t\t\t.command(command)\n\t\t\t\t.memoryInMb((int) ByteSizeUtils.parseToMebibytes(memory))\n\t\t\t\t.diskInMb((int) ByteSizeUtils.parseToMebibytes(disk))\n\t\t\t\t.name(name)\n\t\t\t\t.build()))\n\t\t\t.willReturn(response);\n\t}\n\n\tprivate void givenRequestDeleteApplication(String appName) {\n\t\tgiven(this.operations.applications()\n\t\t\t.delete(DeleteApplicationRequest.builder()\n\t\t\t\t.name(appName)\n\t\t\t\t.deleteRoutes(true)\n\t\t\t\t.build()))\n\t\t\t.willReturn(Mono.empty());\n\t}\n\n\tprivate void givenRequestGetApplication(String name, Mono<ApplicationDetail> response) {\n\t\tgiven(this.operations.applications()\n\t\t\t.get(GetApplicationRequest.builder()\n\t\t\t\t.name(name)\n\t\t\t\t.build()))\n\t\t\t.willReturn(response);\n\t}\n\n\tprivate void givenRequestGetApplicationSummary(String applicationId, Mono<SummaryApplicationResponse> response) {\n\t\tgiven(this.client.applicationsV2()\n\t\t\t.summary(org.cloudfoundry.client.v2.applications.SummaryApplicationRequest.builder()\n\t\t\t\t.applicationId(applicationId)\n\t\t\t\t.build()))\n\t\t\t.willReturn(response);\n\t}\n\n\tprivate void givenRequestGetTask(String taskId, Mono<GetTaskResponse> response) {\n\t\tgiven(this.client.tasks()\n\t\t\t.get(GetTaskRequest.builder()\n\t\t\t\t.taskId(taskId)\n\t\t\t\t.build()))\n\t\t\t.willReturn(response);\n\t}\n\n\tprivate void givenRequestListApplications(Flux<ApplicationSummary> response) {\n\t\tgiven(this.operations.applications()\n\t\t\t.list())\n\t\t\t.willReturn(response);\n\t}\n\n\tprivate void givenRequestPushApplication(PushApplicationManifestRequest request, Mono<Void> response) {\n\t\tgiven(this.operations.applications()\n\t\t\t.pushManifest(any(PushApplicationManifestRequest.class)))\n\t\t\t.willReturn(response);\n\t}\n\n\tprivate void givenRequestStopApplication(String name, Mono<Void> response) {\n\t\tgiven(this.operations.applications()\n\t\t\t.stop(StopApplicationRequest.builder()\n\t\t\t\t.name(name)\n\t\t\t\t.build()))\n\t\t\t.willReturn(response);\n\t}\n\n\tprivate void setupExistingAppSuccessful() {\n\t\t\tgivenRequestListApplications(Flux.just(ApplicationSummary.builder()\n\t\t\t\t\t.diskQuota(0)\n\t\t\t\t\t.id(\"test-application-id\")\n\t\t\t\t\t.instances(1)\n\t\t\t\t\t.memoryLimit(0)\n\t\t\t\t\t.name(\"test-application\")\n\t\t\t\t\t.requestedState(\"RUNNING\")\n\t\t\t\t\t.runningInstances(1)\n\t\t\t\t\t.build()));\n\n\t\t\tgivenRequestGetApplicationSummary(\"test-application-id\", Mono.just(SummaryApplicationResponse.builder()\n\t\t\t\t\t.id(\"test-application-id\")\n\t\t\t\t\t.detectedStartCommand(\"test-command\")\n\t\t\t\t\t.build()));\n\n\t\t\tgivenRequestCreateTask(\"test-application-id\",\n\t\t\t\t\t\t\"test-command\",\n\t\t\t\t\t\tthis.deploymentProperties.getMemory(),\n\t\t\t\t\tthis.deploymentProperties.getDisk(),\n\t\t\t\t\t\"test-application\",\n\t\t\t\t\t\tMono.just(CreateTaskResponse.builder()\n\t\t\t\t\t.id(\"test-task-id\")\n\t\t\t\t\t.memoryInMb(1024)\n\t\t\t\t\t.diskInMb(1024)\n\t\t\t\t\t.dropletId(\"1\")\n\t\t\t\t\t.createdAt(new Date().toString())\n\t\t\t\t\t.updatedAt(new Date().toString())\n\t\t\t\t\t.sequenceId(1)\n\t\t\t\t\t.name(\"test-task-id\")\n\t\t\t\t\t.state(TaskState.FAILED)\n\t\t\t\t\t.build()));\n\t}\n\n\tprivate void setupTaskWithNonExistentApplication(Resource resource) throws IOException{\n\t\tgivenRequestListApplications(Flux.empty());\n\n\t\tgivenRequestPushApplication(\n\t\t\t\tPushApplicationManifestRequest.builder()\n\t\t\t\t\t\t.manifest(ApplicationManifest.builder()\n\t\t\t\t\t\t\t\t.path(resource.getFile().toPath())\n\t\t\t\t\t\t\t\t.buildpack(deploymentProperties.getBuildpack())\n\t\t\t\t\t\t\t\t.command(\"echo '*** First run of container to allow droplet creation.***' && sleep 300\")\n\t\t\t\t\t\t\t\t.disk((int) ByteSizeUtils.parseToMebibytes(this.deploymentProperties.getDisk()))\n\t\t\t\t\t\t\t\t.environmentVariable(\"SPRING_APPLICATION_JSON\", \"{}\")\n\t\t\t\t\t\t\t\t.healthCheckType(ApplicationHealthCheck.NONE)\n\t\t\t\t\t\t\t\t.memory((int) ByteSizeUtils.parseToMebibytes(this.deploymentProperties.getMemory()))\n\t\t\t\t\t\t\t\t.name(\"test-application\")\n\t\t\t\t\t\t\t\t.noRoute(true)\n\t\t\t\t\t\t\t\t.services(Collections.emptySet())\n\t\t\t\t\t\t\t\t.build())\n\t\t\t\t\t\t.stagingTimeout(this.deploymentProperties.getStagingTimeout())\n\t\t\t\t\t\t.startupTimeout(this.deploymentProperties.getStartupTimeout())\n\t\t\t\t\t\t.build(), Mono.empty());\n\n\t\tgivenRequestGetApplication(\"test-application\", Mono.just(ApplicationDetail.builder()\n\t\t\t\t.diskQuota(0)\n\t\t\t\t.id(\"test-application-id\")\n\t\t\t\t.instances(1)\n\t\t\t\t.memoryLimit(0)\n\t\t\t\t.name(\"test-application\")\n\t\t\t\t.requestedState(\"RUNNING\")\n\t\t\t\t.runningInstances(1)\n\t\t\t\t.stack(\"test-stack\")\n\t\t\t\t.build()));\n\n\t\tgivenRequestStopApplication(\"test-application\", Mono.empty());\n\n\t\tgivenRequestGetApplicationSummary(\"test-application-id\", Mono.just(SummaryApplicationResponse.builder()\n\t\t\t\t.id(\"test-application-id\")\n\t\t\t\t.detectedStartCommand(\"test-command\")\n\t\t\t\t.build()));\n\n\t\tgivenRequestCreateTask(\"test-application-id\",\n\t\t\t\t\t\"test-command\",\n\t\t\t\t\tthis.deploymentProperties.getMemory(),\n\t\t\t\tthis.deploymentProperties.getDisk(),\n\t\t\t\t\"test-application\",\n\t\t\t\t\tMono.just(CreateTaskResponse.builder()\n\t\t\t\t.id(\"test-task-id\")\n\t\t\t\t.memoryInMb(1024)\n\t\t\t\t.diskInMb(1024)\n\t\t\t\t.dropletId(\"1\")\n\t\t\t\t.createdAt(new Date().toString())\n\t\t\t\t.updatedAt(new Date().toString())\n\t\t\t\t.sequenceId(1)\n\t\t\t\t.name(\"test-task-id\")\n\t\t\t\t.state(TaskState.SUCCEEDED)\n\t\t\t\t.build()));\n\t}\n\n\tprivate void setupFailedPush(Resource resource) throws IOException{\n\t\tgivenRequestListApplications(Flux.empty());\n\n\t\tgivenRequestPushApplication(\n\t\t\t\tPushApplicationManifestRequest.builder()\n\t\t\t\t\t\t.manifest(ApplicationManifest.builder()\n\t\t\t\t\t\t\t\t.path(resource.getFile().toPath())\n\t\t\t\t\t\t\t\t.buildpack(deploymentProperties.getBuildpack())\n\t\t\t\t\t\t\t\t.command(\"echo '*** First run of container to allow droplet creation.***' && sleep 300\")\n\t\t\t\t\t\t\t\t.disk((int) ByteSizeUtils.parseToMebibytes(this.deploymentProperties.getDisk()))\n\t\t\t\t\t\t\t\t.environmentVariable(\"SPRING_APPLICATION_JSON\", \"{}\")\n\t\t\t\t\t\t\t\t.healthCheckType(ApplicationHealthCheck.NONE)\n\t\t\t\t\t\t\t\t.memory((int) ByteSizeUtils.parseToMebibytes(this.deploymentProperties.getMemory()))\n\t\t\t\t\t\t\t\t.name(\"test-application\")\n\t\t\t\t\t\t\t\t.noRoute(true)\n\t\t\t\t\t\t\t\t.services(Collections.emptySet())\n\t\t\t\t\t\t\t\t.build())\n\t\t\t\t\t\t.stagingTimeout(this.deploymentProperties.getStagingTimeout())\n\t\t\t\t\t\t.startupTimeout(this.deploymentProperties.getStartupTimeout())\n\t\t\t\t\t\t.build(), Mono.error(new UnsupportedOperationException()));\n\t}\n\n\tprivate void setupTaskWithNonExistentApplicationAndRetrievingApplicationSummaryFails(Resource resource) throws IOException {\n\t\tgivenRequestListApplications(Flux.empty());\n\n\t\tgivenRequestPushApplication(\n\t\t\t\tPushApplicationManifestRequest.builder()\n\t\t\t\t\t\t.manifest(ApplicationManifest.builder()\n\t\t\t\t\t\t\t\t.path(resource.getFile().toPath())\n\t\t\t\t\t\t\t\t.buildpack(deploymentProperties.getBuildpack())\n\t\t\t\t\t\t\t\t.command(\"echo '*** First run of container to allow droplet creation.***' && sleep 300\")\n\t\t\t\t\t\t\t\t.disk((int) ByteSizeUtils.parseToMebibytes(this.deploymentProperties.getDisk()))\n\t\t\t\t\t\t\t\t.environmentVariable(\"SPRING_APPLICATION_JSON\", \"{}\")\n\t\t\t\t\t\t\t\t.healthCheckType(ApplicationHealthCheck.NONE)\n\t\t\t\t\t\t\t\t.memory((int) ByteSizeUtils.parseToMebibytes(this.deploymentProperties.getMemory()))\n\t\t\t\t\t\t\t\t.name(\"test-application\")\n\t\t\t\t\t\t\t\t.noRoute(true)\n\t\t\t\t\t\t\t\t.services(Collections.emptySet())\n\t\t\t\t\t\t\t\t.build())\n\t\t\t\t\t\t.stagingTimeout(this.deploymentProperties.getStagingTimeout())\n\t\t\t\t\t\t.startupTimeout(this.deploymentProperties.getStartupTimeout())\n\t\t\t\t\t\t.build(), Mono.empty());\n\n\t\tgivenRequestGetApplication(\"test-application\", Mono.just(ApplicationDetail.builder()\n\t\t\t\t.diskQuota(0)\n\t\t\t\t.id(\"test-application-id\")\n\t\t\t\t.instances(1)\n\t\t\t\t.memoryLimit(0)\n\t\t\t\t.name(\"test-application\")\n\t\t\t\t.requestedState(\"RUNNING\")\n\t\t\t\t.runningInstances(1)\n\t\t\t\t.stack(\"test-stack\")\n\t\t\t\t.build()));\n\n\t\tgivenRequestStopApplication(\"test-application\", Mono.empty());\n\n\t\tgivenRequestGetApplicationSummary(\"test-application-id\", Mono.error(new UnsupportedOperationException()));\n\n\t}\n\n\tprivate void setupTaskWithNonExistentApplicationAndStoppingApplicationFails(Resource resource) throws IOException {\n\t\tgivenRequestListApplications(Flux.empty());\n\n\t\tgivenRequestPushApplication(\n\t\t\t\tPushApplicationManifestRequest.builder()\n\t\t\t\t\t\t.manifest(ApplicationManifest.builder()\n\t\t\t\t\t\t\t\t.path(resource.getFile().toPath())\n\t\t\t\t\t\t\t\t.buildpack(deploymentProperties.getBuildpack())\n\t\t\t\t\t\t\t\t.command(\"echo '*** First run of container to allow droplet creation.***' && sleep 300\")\n\t\t\t\t\t\t\t\t.disk((int) ByteSizeUtils.parseToMebibytes(this.deploymentProperties.getDisk()))\n\t\t\t\t\t\t\t\t.environmentVariable(\"SPRING_APPLICATION_JSON\", \"{}\")\n\t\t\t\t\t\t\t\t.healthCheckType(ApplicationHealthCheck.NONE)\n\t\t\t\t\t\t\t\t.memory((int) ByteSizeUtils.parseToMebibytes(this.deploymentProperties.getMemory()))\n\t\t\t\t\t\t\t\t.name(\"test-application\")\n\t\t\t\t\t\t\t\t.noRoute(true)\n\t\t\t\t\t\t\t\t.services(Collections.emptySet())\n\t\t\t\t\t\t\t\t.build())\n\t\t\t\t\t\t.stagingTimeout(this.deploymentProperties.getStagingTimeout())\n\t\t\t\t\t\t.startupTimeout(this.deploymentProperties.getStartupTimeout())\n\t\t\t\t\t\t.build(), Mono.empty());\n\n\t\tgivenRequestGetApplication(\"test-application\", Mono.just(ApplicationDetail.builder()\n\t\t\t\t.diskQuota(0)\n\t\t\t\t.id(\"test-application-id\")\n\t\t\t\t.instances(1)\n\t\t\t\t.memoryLimit(0)\n\t\t\t\t.name(\"test-application\")\n\t\t\t\t.requestedState(\"RUNNING\")\n\t\t\t\t.runningInstances(1)\n\t\t\t\t.stack(\"test-stack\")\n\t\t\t\t.build()));\n\n\t\tgivenRequestStopApplication(\"test-application\", Mono.error(new UnsupportedOperationException()));\n\t}\n\n\n\tprivate void setupTaskWithNonExistentApplicationBindingOneService(Resource resource) throws IOException {\n\t\tgivenRequestListApplications(Flux.empty());\n\n\t\tgivenRequestPushApplication(PushApplicationManifestRequest.builder()\n\t\t\t\t.manifest(ApplicationManifest.builder()\n\t\t\t\t\t\t.path(resource.getFile().toPath())\n\t\t\t\t\t\t.buildpack(deploymentProperties.getBuildpack())\n\t\t\t\t\t\t.command(\"echo '*** First run of container to allow droplet creation.***' && sleep 300\")\n\t\t\t\t\t\t.disk((int) ByteSizeUtils.parseToMebibytes(this.deploymentProperties.getDisk()))\n\t\t\t\t\t\t.environmentVariable(\"SPRING_APPLICATION_JSON\", \"{}\")\n\t\t\t\t\t\t.healthCheckType(ApplicationHealthCheck.NONE)\n\t\t\t\t\t\t.memory((int) ByteSizeUtils.parseToMebibytes(this.deploymentProperties.getMemory()))\n\t\t\t\t\t\t.name(\"test-application\")\n\t\t\t\t\t\t.noRoute(true)\n\t\t\t\t\t\t.service(\"test-service-instance-2\")\n\t\t\t\t\t\t.build())\n\t\t\t\t.stagingTimeout(this.deploymentProperties.getStagingTimeout())\n\t\t\t\t.startupTimeout(this.deploymentProperties.getStartupTimeout())\n\t\t\t\t.build(), Mono.empty());\n\n\t\tgivenRequestGetApplication(\"test-application\", Mono.just(ApplicationDetail.builder()\n\t\t\t\t.diskQuota(0)\n\t\t\t\t.id(\"test-application-id\")\n\t\t\t\t.instances(1)\n\t\t\t\t.memoryLimit(0)\n\t\t\t\t.name(\"test-application\")\n\t\t\t\t.requestedState(\"RUNNING\")\n\t\t\t\t.runningInstances(1)\n\t\t\t\t.stack(\"test-stack\")\n\t\t\t\t.build()));\n\n\t\tgivenRequestStopApplication(\"test-application\", Mono.empty());\n\n\t\tgivenRequestGetApplicationSummary(\"test-application-id\", Mono.just(SummaryApplicationResponse.builder()\n\t\t\t\t.id(\"test-application-id\")\n\n\t\t\t\t.detectedStartCommand(\"test-command\")\n\t\t\t\t.build()));\n\n\t\tgivenRequestCreateTask(\"test-application-id\",\n\t\t\t\t\t\"test-command\",\n\t\t\t\t\tthis.deploymentProperties.getMemory(),\n\t\t\t\tthis.deploymentProperties.getDisk(),\n\t\t\t\t\"test-application\",\n\t\t\t\t\tMono.just(CreateTaskResponse.builder()\n\t\t\t\t.id(\"test-task-id\")\n\t\t\t\t.memoryInMb(1024)\n\t\t\t\t.diskInMb(1024)\n\t\t\t\t.dropletId(\"1\")\n\t\t\t\t.createdAt(new Date().toString())\n\t\t\t\t.updatedAt(new Date().toString())\n\t\t\t\t.sequenceId(1)\n\t\t\t\t.name(\"test-task-id\")\n\t\t\t\t.state(TaskState.SUCCEEDED)\n\t\t\t\t.build()));\n\t}\n\n\tprivate void setupTaskWithNonExistentApplicationBindingThreeServices(Resource resource) throws IOException {\n\t\tgivenRequestListApplications(Flux.empty());\n\n\t\tgivenRequestPushApplication(PushApplicationManifestRequest.builder()\n\t\t\t\t.manifest(ApplicationManifest.builder()\n\t\t\t\t\t\t.path(resource.getFile().toPath())\n\t\t\t\t\t\t.buildpack(deploymentProperties.getBuildpack())\n\t\t\t\t\t\t.command(\"echo '*** First run of container to allow droplet creation.***' && sleep 300\")\n\t\t\t\t\t\t.disk((int) ByteSizeUtils.parseToMebibytes(this.deploymentProperties.getDisk()))\n\t\t\t\t\t\t.environmentVariable(\"SPRING_APPLICATION_JSON\", \"{}\")\n\t\t\t\t\t\t.healthCheckType(ApplicationHealthCheck.NONE)\n\t\t\t\t\t\t.memory((int) ByteSizeUtils.parseToMebibytes(this.deploymentProperties.getMemory()))\n\t\t\t\t\t\t.name(\"test-application\")\n\t\t\t\t\t\t.noRoute(true)\n\t\t\t\t\t\t.service(\"test-service-instance-1\")\n\t\t\t\t\t\t.service(\"test-service-instance-2\")\n\t\t\t\t\t\t.service(\"test-service-instance-3\")\n\t\t\t\t\t\t.build())\n\t\t\t\t.stagingTimeout(this.deploymentProperties.getStagingTimeout())\n\t\t\t\t.startupTimeout(this.deploymentProperties.getStartupTimeout())\n\t\t\t\t.build(), Mono.empty());\n\n\t\tgivenRequestGetApplication(\"test-application\", Mono.just(ApplicationDetail.builder()\n\t\t\t\t.diskQuota(0)\n\t\t\t\t.id(\"test-application-id\")\n\t\t\t\t.instances(1)\n\t\t\t\t.memoryLimit(0)\n\t\t\t\t.name(\"test-application\")\n\t\t\t\t.requestedState(\"RUNNING\")\n\t\t\t\t.runningInstances(1)\n\t\t\t\t.stack(\"test-stack\")\n\t\t\t\t.build()));\n\n\t\tgivenRequestStopApplication(\"test-application\", Mono.empty());\n\n\t\tgivenRequestGetApplicationSummary(\"test-application-id\", Mono.just(SummaryApplicationResponse.builder()\n\t\t\t\t.id(\"test-application-id\")\n\t\t\t\t.detectedStartCommand(\"test-command\")\n\t\t\t\t.build()));\n\n\t\tgivenRequestCreateTask(\"test-application-id\",\n\t\t\t\t\t\"test-command\",\n\t\t\t\t\tthis.deploymentProperties.getMemory(),\n\t\t\t\tthis.deploymentProperties.getDisk(),\n\t\t\t\t\"test-application\",\n\t\t\t\t\tMono.just(CreateTaskResponse.builder()\n\t\t\t\t.id(\"test-task-id\")\n\t\t\t\t.memoryInMb(1024)\n\t\t\t\t.diskInMb(1024)\n\t\t\t\t.dropletId(\"1\")\n\t\t\t\t.createdAt(new Date().toString())\n\t\t\t\t.updatedAt(new Date().toString())\n\t\t\t\t.sequenceId(1)\n\t\t\t\t.name(\"test-task-id\")\n\t\t\t\t.state(TaskState.SUCCEEDED)\n\t\t\t\t.build()));\n\t}\n\n\tpublic void setupTaskWithNonExistentApplicationRetrievalFails(Resource resource) throws IOException {\n\t\tgivenRequestListApplications(Flux.empty());\n\n\t\tgivenRequestPushApplication(PushApplicationManifestRequest.builder()\n\t\t\t\t.manifest(ApplicationManifest.builder()\n\t\t\t\t\t\t.path(resource.getFile().toPath())\n\t\t\t\t\t\t.buildpack(deploymentProperties.getBuildpack())\n\t\t\t\t\t\t.command(\"echo '*** First run of container to allow droplet creation.***' && sleep 300\")\n\t\t\t\t\t\t.disk((int) ByteSizeUtils.parseToMebibytes(this.deploymentProperties.getDisk()))\n\t\t\t\t\t\t.environmentVariable(\"SPRING_APPLICATION_JSON\", \"{}\")\n\t\t\t\t\t\t.healthCheckType(ApplicationHealthCheck.NONE)\n\t\t\t\t\t\t.memory((int) ByteSizeUtils.parseToMebibytes(this.deploymentProperties.getMemory()))\n\t\t\t\t\t\t.name(\"test-application\")\n\t\t\t\t\t\t.noRoute(true)\n\t\t\t\t\t\t.services(Collections.emptySet())\n\t\t\t\t\t\t.build())\n\t\t\t\t.stagingTimeout(this.deploymentProperties.getStagingTimeout())\n\t\t\t\t.startupTimeout(this.deploymentProperties.getStartupTimeout())\n\t\t\t\t.build(), Mono.empty());\n\n\t\tgivenRequestStopApplication(\"test-application\", Mono.empty());\n\n\t\tgivenRequestGetApplication(\"test-application\", Mono.error(new UnsupportedOperationException()));\n\t}\n\n\tprivate AppDeploymentRequest defaultRequest() {\n\t\treturn deploymentRequest(this.resource, Collections.emptyMap());\n\t}\n\tprivate AppDeploymentRequest deploymentRequest(Resource resource, Map<String,String> deploymentProperties) {\n\t\tAppDefinition definition = new AppDefinition(\"test-application\", null);\n\t\treturn new AppDeploymentRequest(definition, resource, deploymentProperties);\n\t}\n\n\tprivate Mono<ListTasksResponse> runningTasksResponse() {\n\t\tList<TaskResource> taskResources = new ArrayList<>();\n\t\tfor (int i=0; i< TASK_EXECUTION_COUNT; i++) {\n\t\t\ttaskResources.add(TaskResource.builder()\n\t\t\t\t.name(\"task-\" + i)\n\t\t\t\t.dropletId(UUID.randomUUID().toString())\n\t\t\t\t.id(UUID.randomUUID().toString())\n\t\t\t\t.diskInMb(2048)\n\t\t\t\t.sequenceId(i)\n\t\t\t\t.state(TaskState.RUNNING)\n\t\t\t\t.memoryInMb(2048)\n\t\t\t\t.createdAt(LocalDateTime.now().format(DateTimeFormatter.ofPattern(\"yyyy-MM-dd HH:mm:ss\")))\n\t\t\t\t.build());\n\t\t}\n\t\tListTasksResponse listTasksResponse = ListTasksResponse.builder().resources(taskResources)\n\t\t\t.pagination(Pagination.builder().totalResults(taskResources.size()).build())\n\t\t\t.build();\n\t\treturn Mono.just(listTasksResponse);\n\t}\n\n\tprivate Mono<ListOrganizationsResponse> listOrganizationsResponse() {\n\t\tListOrganizationsResponse response = ListOrganizationsResponse.builder()\n\t\t.addAllResources(Collections.<OrganizationResource>singletonList(\n\t\t\t\tOrganizationResource.builder()\n\t\t\t\t\t\t.metadata(Metadata.builder().id(\"123\").build()).build())\n\t\t).build();\n\t\treturn Mono.just(response);\n\t}\n\n\tprivate Mono<ListSpacesResponse> listSpacesResponse() {\n\t\tListSpacesResponse response = ListSpacesResponse.builder()\n\t\t\t\t.addAllResources(Collections.<SpaceResource>singletonList(\n\t\t\t\t\t\tSpaceResource.builder()\n\t\t\t\t\t\t\t\t.metadata(Metadata.builder().id(\"123\").build()).build())\n\t\t\t\t).build();\n\t\treturn Mono.just(response);\n\t}\n}\n"
  },
  {
    "path": "spring-cloud-deployer-cloudfoundry/src/test/java/org/springframework/cloud/deployer/spi/cloudfoundry/ServiceParserTests.java",
    "content": "/*\n * Copyright 2019-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.cloudfoundry;\n\nimport java.util.Collections;\nimport java.util.Map;\n\nimport org.junit.jupiter.api.Test;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.assertThatThrownBy;\nimport static org.assertj.core.api.Assertions.entry;\n\n/** @author David Turanski */\npublic class ServiceParserTests {\n\n  @Test\n  public void plainService() {\n    assertThat(ServiceParser.getServiceParameters(\"test-service\").isPresent()).isFalse();\n    assertThat(ServiceParser.getServiceInstanceName(\"test-service\")).isEqualTo(\"test-service\");\n  }\n\n  @Test\n  public void plainServiceWithSpecialCharacters() {\n    assertThat(ServiceParser.getServiceParameters(\"test.service.$$\").isPresent()).isFalse();\n  }\n\n  @Test\n  public void serviceWithParameters() {\n    String serviceSpec = \"test-service foo:bar\";\n    assertThat(ServiceParser.getServiceParameters(serviceSpec).get())\n        .isEqualTo(Collections.singletonMap(\"foo\", \"bar\"));\n  }\n\n  @Test\n  public void getServiceInstanceName() {\n    String serviceSpec = \"test-service foo:bar\";\n    assertThat(ServiceParser.getServiceInstanceName(serviceSpec)).isEqualTo(\"test-service\");\n  }\n\n  @Test\n  public void serviceWithSpacesParameters() {\n    assertThat(ServiceParser.getServiceParameters(\"test-service foo : bar\").get())\n        .isEqualTo(Collections.singletonMap(\"foo\", \"bar\"));\n  }\n\n  @Test\n  public void serviceWithEqualsInParameters() {\n    assertThat(ServiceParser.getServiceParameters(\"test-service foo=bar\").get())\n        .isEqualTo(Collections.singletonMap(\"foo\", \"bar\"));\n  }\n\n  @Test\n  public void realWorldExample() {\n\n    Map<String,String> params = ServiceParser.getServiceParameters(\n        \"nfs share:1.2.3.4/export, uid:65534, gid:65534, mount:/var/scdf\")\n        .get();\n\n    assertThat(params).containsOnly(\n        entry(\"share\",\"1.2.3.4/export\"),\n        entry(\"uid\",\"65534\"),\n        entry(\"gid\",\"65534\"),\n        entry(\"mount\",\"/var/scdf\")\n    );\n  }\n\n  @Test\n  public void anotherRealWorldExample() {\n    Map<String,String> params = ServiceParser.getServiceParameters(\n        \"nfs share=1.2.3.4/export, uid=65534, gid=65534, mount=/var/scdf\")\n        .get();\n\n    assertThat(params).containsOnly(\n        entry(\"share\",\"1.2.3.4/export\"),\n        entry(\"uid\",\"65534\"),\n        entry(\"gid\",\"65534\"),\n        entry(\"mount\",\"/var/scdf\")\n    );\n  }\n\n  @Test\n  public void serviceWithInvalidParameters() {\n\t\tassertThatThrownBy(() -> {\n\t\t\tServiceParser.getServiceParameters(\"test-service foo bar\");\n\t\t}).isInstanceOf(IllegalArgumentException.class).hasMessageContaining(\n\t\t\t\t\"invalid service specification: test-service foo bar\");\n  }\n\n  @Test\n  public void splitServiceProperties() {\n\n     assertThat(ServiceParser.splitServiceProperties(\n        \"'nfs share:10.194.2.6/export,uid:65534,gid:65534,mount:/var/scdf',mysql,'foo bar:baz'\"))\n         .containsExactlyInAnyOrder(\n             \"nfs share:10.194.2.6/export,uid:65534,gid:65534,mount:/var/scdf\",\n             \"mysql\",\n             \"foo bar:baz\");\n\n    assertThat(ServiceParser.splitServiceProperties(\"mysql,rabbit,redis\"))\n        .containsExactlyInAnyOrder(\n            \"mysql\",\n            \"rabbit\",\n            \"redis\");\n\n    assertThat(ServiceParser.splitServiceProperties(\n        \"redis,  'nfs share:10.194.2.6/export,uid:65534,gid:65534,mount:/var/scdf', mysql ,  'foo bar:baz'\"))\n        .containsExactlyInAnyOrder(\n            \"redis\",\n            \"nfs share:10.194.2.6/export,uid:65534,gid:65534,mount:/var/scdf\",\n            \"mysql\",\n            \"foo bar:baz\");\n  }\n\n}\n"
  },
  {
    "path": "spring-cloud-deployer-cloudfoundry/src/test/java/org/springframework/cloud/deployer/spi/scheduler/cloudfoundry/CloudFoundryAppSchedulerTests.java",
    "content": "/*\n * Copyright 2018-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.scheduler.cloudfoundry;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.stream.Collectors;\n\nimport io.pivotal.scheduler.SchedulerClient;\nimport io.pivotal.scheduler.v1.Pagination;\nimport io.pivotal.scheduler.v1.calls.Calls;\nimport io.pivotal.scheduler.v1.jobs.CreateJobRequest;\nimport io.pivotal.scheduler.v1.jobs.CreateJobResponse;\nimport io.pivotal.scheduler.v1.jobs.DeleteJobRequest;\nimport io.pivotal.scheduler.v1.jobs.DeleteJobScheduleRequest;\nimport io.pivotal.scheduler.v1.jobs.ExecuteJobRequest;\nimport io.pivotal.scheduler.v1.jobs.ExecuteJobResponse;\nimport io.pivotal.scheduler.v1.jobs.GetJobRequest;\nimport io.pivotal.scheduler.v1.jobs.GetJobResponse;\nimport io.pivotal.scheduler.v1.jobs.Job;\nimport io.pivotal.scheduler.v1.jobs.JobSchedule;\nimport io.pivotal.scheduler.v1.jobs.Jobs;\nimport io.pivotal.scheduler.v1.jobs.ListJobHistoriesRequest;\nimport io.pivotal.scheduler.v1.jobs.ListJobHistoriesResponse;\nimport io.pivotal.scheduler.v1.jobs.ListJobScheduleHistoriesRequest;\nimport io.pivotal.scheduler.v1.jobs.ListJobScheduleHistoriesResponse;\nimport io.pivotal.scheduler.v1.jobs.ListJobSchedulesRequest;\nimport io.pivotal.scheduler.v1.jobs.ListJobSchedulesResponse;\nimport io.pivotal.scheduler.v1.jobs.ListJobsRequest;\nimport io.pivotal.scheduler.v1.jobs.ListJobsResponse;\nimport io.pivotal.scheduler.v1.jobs.ScheduleJobRequest;\nimport io.pivotal.scheduler.v1.jobs.ScheduleJobResponse;\nimport io.pivotal.scheduler.v1.schedules.ExpressionType;\nimport org.cloudfoundry.client.CloudFoundryClient;\nimport org.cloudfoundry.client.v3.applications.ApplicationsV3;\nimport org.cloudfoundry.client.v3.tasks.Tasks;\nimport org.cloudfoundry.operations.CloudFoundryOperations;\nimport org.cloudfoundry.operations.applications.ApplicationSummary;\nimport org.cloudfoundry.operations.applications.Applications;\nimport org.cloudfoundry.operations.spaces.SpaceSummary;\nimport org.cloudfoundry.operations.spaces.Spaces;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.ValueSource;\nimport org.mockito.Answers;\nimport org.mockito.ArgumentCaptor;\nimport org.mockito.Mock;\nimport org.mockito.MockitoAnnotations;\nimport reactor.core.publisher.Flux;\nimport reactor.core.publisher.Mono;\n\nimport org.springframework.cloud.deployer.spi.cloudfoundry.CloudFoundryConnectionProperties;\nimport org.springframework.cloud.deployer.spi.cloudfoundry.CloudFoundryDeploymentProperties;\nimport org.springframework.cloud.deployer.spi.cloudfoundry.CloudFoundryTaskLauncher;\nimport org.springframework.cloud.deployer.spi.core.AppDefinition;\nimport org.springframework.cloud.deployer.spi.core.AppDeploymentRequest;\nimport org.springframework.cloud.deployer.spi.scheduler.CreateScheduleException;\nimport org.springframework.cloud.deployer.spi.scheduler.ScheduleInfo;\nimport org.springframework.cloud.deployer.spi.scheduler.ScheduleRequest;\nimport org.springframework.cloud.deployer.spi.scheduler.SchedulerException;\nimport org.springframework.cloud.deployer.spi.scheduler.SchedulerPropertyKeys;\nimport org.springframework.core.io.FileSystemResource;\nimport org.springframework.core.io.Resource;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.assertThatThrownBy;\nimport static org.mockito.BDDMockito.given;\nimport static org.mockito.Mockito.verify;\n\n/**\n * Test the core features of the Spring Cloud Scheduler implementation.\n *\n * @author Glenn Renfro\n * @author Ilayaperumal Gopinathan\n */\npublic class CloudFoundryAppSchedulerTests {\n\n\tpublic static final String DEFAULT_CRON_EXPRESSION = \"0/5 * ? * *\";\n\n\tpublic static final String CRON_EXPRESSION_FOR_SIX_MIN = \"0/6 * ? * *\";\n\n\tpublic static final String BAD_CRON_EXPRESSION = \"FOOBAD\";\n\n\t@Mock(answer = Answers.RETURNS_SMART_NULLS)\n\tprivate Applications applications;\n\n\t@Mock(answer = Answers.RETURNS_SMART_NULLS)\n\tprivate CloudFoundryOperations operations;\n\n\t@Mock(answer = Answers.RETURNS_SMART_NULLS)\n\tprivate Spaces spaces;\n\n\t@Mock(answer = Answers.RETURNS_SMART_NULLS)\n\tprivate ApplicationsV3 applicationsV3;\n\n\t@Mock(answer = Answers.RETURNS_SMART_NULLS)\n\tprivate CloudFoundryClient cloudFoundryClient;\n\n\t@Mock(answer = Answers.RETURNS_SMART_NULLS)\n\tprivate Tasks tasks;\n\n\t@Mock(answer = Answers.RETURNS_SMART_NULLS)\n\tprivate CloudFoundryTaskLauncher taskLauncher;\n\n\tprivate CloudFoundryAppScheduler deprecatedCloudFoundryAppScheduler;\n\n\tprivate CloudFoundryAppScheduler deprecatedNoServiceCloudFoundryAppScheduler;\n\n\tprivate CloudFoundryAppScheduler cloudFoundryAppScheduler;\n\n\tprivate CloudFoundryAppScheduler noServiceCloudFoundryAppScheduler;\n\n\tprivate SchedulerClient client;\n\n\tprivate SchedulerClient noServiceClient;\n\n\tprivate CloudFoundryConnectionProperties properties = new CloudFoundryConnectionProperties();\n\n\tprivate CloudFoundrySchedulerProperties schedulerProperties = new CloudFoundrySchedulerProperties();\n\n\tprivate CloudFoundryDeploymentProperties deploymentProperties = new CloudFoundryDeploymentProperties();\n\n\t@BeforeEach\n\tpublic void setUp() {\n\t\tMockitoAnnotations.initMocks(this);\n\t\tgiven(this.cloudFoundryClient.applicationsV3()).willReturn(this.applicationsV3);\n\t\tgiven(this.cloudFoundryClient.tasks()).willReturn(this.tasks);\n\t\tgiven(this.spaces.list()).willReturn(getTestSpaces());\n\n\t\tthis.properties.setSpace(\"test-space\");\n\n\t\tgiven(this.operations.applications()).willReturn(this.applications);\n\t\tgiven(this.operations.spaces()).willReturn(this.spaces);\n\n\t\tthis.client = new TestSchedulerClient();\n\t\tthis.noServiceClient = new NoServiceTestSchedulerClient();\n\n\t\tthis.deprecatedCloudFoundryAppScheduler = new CloudFoundryAppScheduler(this.client, this.operations,\n\t\t\t\tthis.properties, taskLauncher, schedulerProperties);\n\t\tthis.deprecatedNoServiceCloudFoundryAppScheduler = new CloudFoundryAppScheduler(this.noServiceClient, this.operations,\n\t\t\t\tthis.properties, taskLauncher, schedulerProperties);\n\t\tthis.cloudFoundryAppScheduler = new CloudFoundryAppScheduler(this.client, this.operations,\n\t\t\t\tthis.properties, taskLauncher, deploymentProperties);\n\t\tthis.noServiceCloudFoundryAppScheduler = new CloudFoundryAppScheduler(this.noServiceClient, this.operations,\n\t\t\t\tthis.properties, taskLauncher, deploymentProperties);\n\n\t}\n\n\t@ParameterizedTest\n\t@ValueSource(booleans = {true, false})\n\tpublic void testEmptySchedulerProperties(boolean isDeprecated) {\n\t\tResource resource = new FileSystemResource(\"src/test/resources/demo-0.0.1-SNAPSHOT.jar\");\n\t\tAppDefinition definition = new AppDefinition(\"bar\", null);\n\t\tScheduleRequest request = (isDeprecated) ? new ScheduleRequest(definition, null, null, null, \"testschedule\", resource)\n\t\t: new ScheduleRequest(definition, null, (List<String>) null, \"testschedule\", resource);\n\t\tassertThatThrownBy(() -> {\n\t\t\tgetCloudFoundryAppScheduler(isDeprecated).schedule(request);\n\t\t}).isInstanceOf(IllegalArgumentException.class);\n\t}\n\n\t@ParameterizedTest\n\t@ValueSource(booleans = {true, false})\n\tpublic void testCreateNoCommandLineArgs(boolean isDeprecated) {\n\t\tResource resource = new FileSystemResource(\"src/test/resources/demo-0.0.1-SNAPSHOT.jar\");\n\n\t\tmockAppResultsInAppList();\n\t\tAppDefinition definition = new AppDefinition(\"test-application-1\", null);\n\t\tScheduleRequest request = (isDeprecated) ? new ScheduleRequest(definition, getDefaultScheduleProperties(),null, null, \"test-schedule\", resource)\n\t\t\t\t: new ScheduleRequest(definition, getDefaultDeploymentProperties(), (List<String>) null, \"test-schedule\", resource);\n\n\t\tgetCloudFoundryAppScheduler(isDeprecated).schedule(request);\n\t\tassertThat(((TestJobs) this.client.jobs()).getCreateJobResponse().getId()).isEqualTo(\"test-job-id-1\");\n\t\tassertThat(((TestJobs) this.client.jobs()).getCreateJobResponse().getApplicationId()).isEqualTo(\"test-application-id-1\");\n\t\tassertThat(((TestJobs) this.client.jobs()).getCreateJobResponse().getCommand()).isEmpty();\n\t}\n\n\t@ParameterizedTest\n\t@ValueSource(booleans = {true, false})\n\tpublic void testInvalidCron(boolean isDeprecated) {\n\t\tResource resource = new FileSystemResource(\"src/test/resources/demo-0.0.1-SNAPSHOT.jar\");\n\n\t\tmockAppResultsInAppList();\n\t\tAppDefinition definition = new AppDefinition(\"test-application-1\", null);\n\t\tMap<String, String> badCronMap = new HashMap<>();\n\t\tbadCronMap.put(SchedulerPropertyKeys.CRON_EXPRESSION, BAD_CRON_EXPRESSION);\n\n\t\tScheduleRequest request = (isDeprecated) ? new ScheduleRequest(definition,\n\t\t\t\tCollections.singletonMap(SchedulerPropertyKeys.CRON_EXPRESSION, BAD_CRON_EXPRESSION), null, null, \"test-schedule\", resource)\n\t\t: new ScheduleRequest(definition, Collections.singletonMap(CloudFoundryAppScheduler.CRON_EXPRESSION_KEY, BAD_CRON_EXPRESSION),\n\t\t\t\t(List<String>) null, \"test-schedule\", resource);\n\n\t\tassertThatThrownBy(() -> {\n\t\t\tgetCloudFoundryAppScheduler(isDeprecated).schedule(request);\n\t\t}).isInstanceOf(CreateScheduleException.class).hasMessageContaining(\n\t\t\t\t\"Illegal characters for this position: 'FOO'\");\n\t\tassertThat(((TestJobs) this.client.jobs()).getCreateJobResponse()).isNull();\n\t}\n\n\t@ParameterizedTest\n\t@ValueSource(booleans = {true, false})\n\tpublic void testNameTooLong(boolean isDeprecated) {\n\t\tResource resource = new FileSystemResource(\"src/test/resources/demo-0.0.1-SNAPSHOT.jar\");\n\n\t\tmockAppResultsInAppList();\n\t\tAppDefinition definition = new AppDefinition(\"test-application-1\", null);\n\t\tMap<String, String> cronMap = new HashMap<>();\n\t\tcronMap.put(SchedulerPropertyKeys.CRON_EXPRESSION, DEFAULT_CRON_EXPRESSION);\n\n\t\tScheduleRequest request = new ScheduleRequest(definition, cronMap, (List<String>) null,\n\t\t\t\t\"j1-scdf-itcouldbesaidthatthisislongtoowaytoo-oopsitcouldbesaidthatthisis\" +\n\t\t\t\t\t\t\"longtoowaytoo-oopsitcouldbesaidthatthisislongtoowaytoo-oopsitcouldbe\" +\n\t\t\t\t\t\t\"saidthatthisislongtoowaytoo-oopsitcouldbesaidthatthisislongtoowaytoo-\" +\n\t\t\t\t\t\t\"oopsitcouldbesaidthatthisislongtoowaytoo-oops12\", resource);\n\n\t\tassertThatThrownBy(() -> {\n\t\t\tgetCloudFoundryAppScheduler(isDeprecated).schedule(request);\n\t\t}).isInstanceOf(CreateScheduleException.class).hasMessageContaining(\n\t\t\t\"Schedule can not be created because its name \" +\n\t\t\t\"'j1-scdf-itcouldbesaidthatthisislongtoowaytoo-oopsitcouldbesaidthatthisis\" +\n\t\t\t\"longtoowaytoo-oopsitcouldbesaidthatthisislongtoowaytoo-oopsitcouldbe\" +\n\t\t\t\"saidthatthisislongtoowaytoo-oopsitcouldbesaidthatthisislongtoowaytoo-\" +\n\t\t\t\"oopsitcouldbesaidthatthisislongtoowaytoo-oops12' has too many characters.  \" +\n\t\t\t\"Schedule name length must be 255 characters or less\");\n\n\t\tassertThat(((TestJobs) this.client.jobs()).getCreateJobResponse()).isNull();\n\t}\n\n\t@ParameterizedTest\n\t@ValueSource(booleans = {true, false})\n\tpublic void testSuccessJobCreateFailedSchedule(boolean isDeprecated) {\n\t\tResource resource = new FileSystemResource(\"src/test/resources/demo-0.0.1-SNAPSHOT.jar\");\n\n\t\tmockAppResultsInAppList();\n\t\tAppDefinition definition = new AppDefinition(\"test-application-1\", null);\n\t\tScheduleRequest request = (isDeprecated) ? new ScheduleRequest(definition,\n\t\t\t\tCollections.singletonMap(SchedulerPropertyKeys.CRON_EXPRESSION, CRON_EXPRESSION_FOR_SIX_MIN), null, null, \"test-schedule\", resource) :\n\t\t\t\tnew ScheduleRequest(definition, Collections.singletonMap(CloudFoundryAppScheduler.CRON_EXPRESSION_KEY, CRON_EXPRESSION_FOR_SIX_MIN),\n\t\t\t\t\t\t(List<String>) null, \"test-schedule\", resource);\n\n\t\tassertThatThrownBy(() -> {\n\t\t\tgetCloudFoundryAppScheduler(isDeprecated).schedule(request);\n\t\t}).isInstanceOf(CreateScheduleException.class);\n\n\t\tassertThat(((TestJobs) this.client.jobs()).getCreateJobResponse()).isNull();\n\t}\n\n\n\t@ParameterizedTest\n\t@ValueSource(booleans = {true, false})\n\tpublic void testCreateWithCommandLineArgs(boolean isDeprecated) {\n\t\tResource resource = new FileSystemResource(\"src/test/resources/demo-0.0.1-SNAPSHOT.jar\");\n\n\t\tmockAppResultsInAppList();\n\t\tAppDefinition definition = new AppDefinition(\"test-application-1\", null);\n\t\tScheduleRequest request = isDeprecated ? new ScheduleRequest(definition,\n\t\t\t\tgetDefaultScheduleProperties(), null,\n\t\t\t\tCollections.singletonList(\"TestArg\"), \"test-schedule\", resource) :\n\t\t\t\tnew ScheduleRequest(definition,\n\t\t\t\t\t\tgetDefaultDeploymentProperties(),\n\t\t\t\t\t\tCollections.singletonList(\"TestArg\"), \"test-schedule\", resource);\n\t\tgetCloudFoundryAppScheduler(isDeprecated).schedule(request);\n\t\tArgumentCaptor<AppDeploymentRequest> argumentCaptor = ArgumentCaptor.forClass(AppDeploymentRequest.class);\n\t\tverify(this.taskLauncher).stage(argumentCaptor.capture());\n\t\tassertThat(argumentCaptor.getValue().getCommandlineArguments().get(0)).isEqualTo(\"TestArg\");\n\t}\n\n\t@ParameterizedTest\n\t@ValueSource(booleans = {true, false})\n\tpublic void testList(boolean isDeprecated) {\n\t\tsetupMockResults();\n\t\tList<ScheduleInfo> result = getCloudFoundryAppScheduler(isDeprecated).list();\n\t\tassertThat(result.size()).isEqualTo(2);\n\t\tverifyScheduleInfo(result.get(0), \"test-application-1\", \"test-job-name-1\", DEFAULT_CRON_EXPRESSION);\n\t\tverifyScheduleInfo(result.get(1), \"test-application-2\", \"test-job-name-2\", DEFAULT_CRON_EXPRESSION);\n\t}\n\n\t@ParameterizedTest\n\t@ValueSource(booleans = {true, false})\n\tpublic void testListWithJobsNoAssociatedSchedule(boolean isDeprecated) {\n\t\tsetupMockResultsNoScheduleForJobs();\n\t\tList<ScheduleInfo> result = getCloudFoundryAppScheduler(isDeprecated).list();\n\t\tassertThat(result.size()).isEqualTo(2);\n\t\tverifyScheduleInfo(result.get(0), \"test-application-1\", \"test-job-name-1\", null);\n\t\tverifyScheduleInfo(result.get(1), \"test-application-2\", \"test-job-name-2\", null);\n\t}\n\n\t@ParameterizedTest\n\t@ValueSource(booleans = {true, false})\n\tpublic void testListWithNoSchedules(boolean isDeprecated) {\n\t\tgiven(this.operations.applications()\n\t\t\t\t.list())\n\t\t\t\t.willReturn(Flux.empty());\n\t\tList<ScheduleInfo> result = getCloudFoundryAppScheduler(isDeprecated).list();\n\t\tassertThat(result.size()).isEqualTo(0);\n\t}\n\n\t@ParameterizedTest\n\t@ValueSource(booleans = {true, false})\n\tpublic void testListSchedulesWithAppName(boolean isDeprecated) {\n\t\tsetupMockResults();\n\t\tList<ScheduleInfo> result = getCloudFoundryAppScheduler(isDeprecated).list(\"test-application-2\");\n\t\tassertThat(result.size()).isEqualTo(1);\n\t\tverifyScheduleInfo(result.get(0), \"test-application-2\", \"test-job-name-2\", DEFAULT_CRON_EXPRESSION);\n\t}\n\n\t@ParameterizedTest\n\t@ValueSource(booleans = {true, false})\n\tpublic void testListSchedulesWithInvalidAppName(boolean isDeprecated) {\n\t\tsetupMockResults();\n\t\tList<ScheduleInfo> result = getCloudFoundryAppScheduler(isDeprecated).list(\"not-here\");\n\t\tassertThat(result.size()).isEqualTo(0);\n\t}\n\n\t@ParameterizedTest\n\t@ValueSource(booleans = {true, false})\n\tpublic void testUnschedule(boolean isDeprecated) {\n\t\tsetupMockResults();\n\t\tList<ScheduleInfo> result = getCloudFoundryAppScheduler(isDeprecated).list();\n\t\tassertThat(result.size()).isEqualTo(2);\n\t\tgetCloudFoundryAppScheduler(isDeprecated).unschedule(\"test-job-name-1\");\n\t\tresult = getCloudFoundryAppScheduler(isDeprecated).list();\n\t\tassertThat(result.size()).isEqualTo(1);\n\t\tassertThat(result.get(0).getScheduleName()).isEqualTo(\"test-job-name-2\");\n\t\tassertThat(result.get(0).getTaskDefinitionName()).isEqualTo(\"test-application-2\");\n\t}\n\n\t@ParameterizedTest\n\t@ValueSource(booleans = {true, false})\n\tpublic void testMissingScheduleDelete(boolean isDeprecated) {\n\t\tboolean exceptionFired = false;\n\t\tsetupMockResults();\n\t\ttry {\n\t\t\tgetCloudFoundryAppScheduler(isDeprecated).unschedule(\"test-job-name-3\");\n\t\t}\n\t\tcatch (SchedulerException se) {\n\t\t\tassertThat(se.getMessage()).isEqualTo(\"Failed to unschedule schedule test-job-name-3 does not exist.\");\n\t\t\texceptionFired = true;\n\t\t}\n\t\tassertThat(exceptionFired).isTrue();\n\t}\n\n\t@ParameterizedTest\n\t@ValueSource(booleans = {true, false})\n\tpublic void testNoServiceList(boolean isDeprecated) {\n\t\tassertThatThrownBy(() -> {\n\t\t\tgetNoServiceCloudFoundryAppScheduler(isDeprecated).list();\n\t\t}).isInstanceOf(SchedulerException.class).hasMessageContaining(\n\t\t\t\t\"Scheduler Service returned a null response.\");\n\t}\n\n\t@ParameterizedTest\n\t@ValueSource(booleans = {true, false})\n\tpublic void testNoServiceListSchedulesWithAppName(boolean isDeprecated) {\n\t\tassertThatThrownBy(() -> {\n\t\t\tgetNoServiceCloudFoundryAppScheduler(isDeprecated).list(\"test-application-2\");\n\t\t}).isInstanceOf(SchedulerException.class).hasMessageContaining(\n\t\t\t\t\"Scheduler Service returned a null response.\");\n\t}\n\n\t@ParameterizedTest\n\t@ValueSource(booleans = {true, false})\n\tpublic void testNoServiceCreate(boolean isDeprecated) {\n\t\tResource resource = new FileSystemResource(\"src/test/resources/demo-0.0.1-SNAPSHOT.jar\");\n\n\t\tmockAppResultsInAppList();\n\t\tAppDefinition definition = new AppDefinition(\"test-application-1\", null);\n\t\tScheduleRequest request = (isDeprecated) ? new ScheduleRequest(definition, getDefaultScheduleProperties(), null, null, \"test-schedule\", resource) :\n\t\tnew ScheduleRequest(definition, getDefaultDeploymentProperties(), (List<String>) null, \"test-schedule\", resource);\n\n\t\tassertThatThrownBy(() -> {\n\t\t\tgetNoServiceCloudFoundryAppScheduler(isDeprecated).schedule(request);\n\t\t}).isInstanceOf(SchedulerException.class).hasMessageContaining(\n\t\t\t\t\"Scheduler Service returned a null response.\");\n\t}\n\n\tprivate void givenRequestListApplications(Flux<ApplicationSummary> response) {\n\t\tgiven(this.operations.applications()\n\t\t\t\t.list())\n\t\t\t\t.willReturn(response);\n\t}\n\n\tprivate void verifyScheduleInfo(ScheduleInfo scheduleInfo, String taskDefinitionName, String scheduleName, String expression) {\n\t\tassertThat(scheduleInfo.getTaskDefinitionName()).isEqualTo(taskDefinitionName);\n\t\tassertThat(scheduleInfo.getScheduleName()).isEqualTo(scheduleName);\n\t\tif (expression != null) {\n\t\t\tassertThat(scheduleInfo.getScheduleProperties().size()).isEqualTo(1);\n\t\t\tassertThat(scheduleInfo.getScheduleProperties().get(SchedulerPropertyKeys.CRON_EXPRESSION)).isEqualTo(expression);\n\t\t}\n\t\telse {\n\t\t\tassertThat(scheduleInfo.getScheduleProperties().size()).isEqualTo(0);\n\t\t}\n\t}\n\n\tprivate static class TestSchedulerClient implements SchedulerClient {\n\t\tprivate Jobs jobs;\n\n\t\tpublic TestSchedulerClient() {\n\t\t\tjobs = new TestJobs();\n\t\t}\n\n\t\t@Override\n\t\tpublic Calls calls() {\n\t\t\treturn null;\n\t\t}\n\n\t\t@Override\n\t\tpublic Jobs jobs() {\n\t\t\treturn jobs;\n\t\t}\n\t}\n\n\tprivate static class NoServiceTestSchedulerClient implements SchedulerClient {\n\t\tprivate Jobs jobs;\n\n\t\tpublic NoServiceTestSchedulerClient() {\n\t\t\tjobs = new NoServiceTestJobs();\n\t\t}\n\n\t\t@Override\n\t\tpublic Calls calls() {\n\t\t\treturn null;\n\t\t}\n\n\t\t@Override\n\t\tpublic Jobs jobs() {\n\t\t\treturn jobs;\n\t\t}\n\t}\n\n\tprivate static class NoServiceTestJobs extends TestJobs {\n\t\t@Override\n\t\tpublic Mono<ListJobsResponse> list(ListJobsRequest request) {\n\t\t\treturn Mono.justOrEmpty(null);\n\t\t}\n\n\t\t@Override\n\t\tpublic Mono<CreateJobResponse> create(CreateJobRequest request) {\n\t\t\treturn Mono.justOrEmpty(null);\n\t\t}\n\t}\n\n\tprivate static class TestJobs implements Jobs {\n\t\tprivate CreateJobResponse createJobResponse;\n\n\t\tprivate List<Job> jobResources = new ArrayList<>();\n\n\t\tprivate List<JobSchedule> jobScheduleResources = new ArrayList<>();\n\n\t\t@Override\n\t\tpublic Mono<CreateJobResponse> create(CreateJobRequest request) {\n\t\t\tthis.createJobResponse = CreateJobResponse.builder()\n\t\t\t\t\t.applicationId(request.getApplicationId())\n\t\t\t\t\t.name(request.getName())\n\t\t\t\t\t.id(\"test-job-id-1\")\n\t\t\t\t\t.command(request.getCommand())\n\t\t\t\t\t.build();\n\t\t\tthis.jobResources.add(Job.builder().applicationId(request.getApplicationId())\n\t\t\t\t\t.command(request.getCommand())\n\t\t\t\t\t.id(\"test-job-1\")\n\t\t\t\t\t.name(request.getName())\n\t\t\t\t\t.build());\n\t\t\treturn Mono.just(createJobResponse);\n\t\t}\n\n\t\t@Override\n\t\tpublic Mono<Void> delete(DeleteJobRequest request) {\n\t\t\tfor (int i = 0; i < this.jobResources.size(); i++) {\n\t\t\t\tif (this.jobResources.get(i).getId().equals(request.getJobId())) {\n\t\t\t\t\tjobResources.remove(i);\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn Mono.justOrEmpty(null);\n\t\t}\n\n\t\t@Override\n\t\tpublic Mono<Void> deleteSchedule(DeleteJobScheduleRequest request) {\n\t\t\treturn null;\n\t\t}\n\n\t\t@Override\n\t\tpublic Mono<ExecuteJobResponse> execute(ExecuteJobRequest request) {\n\t\t\treturn null;\n\t\t}\n\n\t\t@Override\n\t\tpublic Mono<GetJobResponse> get(GetJobRequest request) {\n\t\t\treturn null;\n\t\t}\n\n\t\t@Override\n\t\tpublic Mono<ListJobsResponse> list(ListJobsRequest request) {\n\t\t\tListJobsResponse response = ListJobsResponse.builder()\n\t\t\t\t\t.addAllResources(jobResources)\n\t\t\t\t\t.pagination(Pagination.builder().totalPages(1).build())\n\t\t\t\t\t.build();\n\t\t\treturn Mono.just(response);\n\t\t}\n\n\t\t@Override\n\t\tpublic Mono<ListJobHistoriesResponse> listHistories(ListJobHistoriesRequest request) {\n\t\t\treturn null;\n\t\t}\n\n\t\t@Override\n\t\tpublic Mono<ListJobScheduleHistoriesResponse> listScheduleHistories(ListJobScheduleHistoriesRequest request) {\n\t\t\treturn null;\n\t\t}\n\n\t\t@Override\n\t\tpublic Mono<ListJobSchedulesResponse> listSchedules(ListJobSchedulesRequest request) {\n\n\t\t\tListJobSchedulesResponse response = ListJobSchedulesResponse.builder()\n\t\t\t\t\t.addAllResources(jobScheduleResources.stream().filter(jobScheduleResource -> jobScheduleResource.getJobId().equals(request.getJobId())).collect(Collectors.toList()))\n\t\t\t\t\t.build();\n\t\t\treturn Mono.just(response);\n\t\t}\n\n\t\t@Override\n\t\tpublic Mono<ScheduleJobResponse> schedule(ScheduleJobRequest request) {\n\t\t\tif(request.getExpression().equals(CRON_EXPRESSION_FOR_SIX_MIN)) {\n\t\t\t\tthrow new IllegalStateException();\n\t\t\t}\n\t\t\treturn Mono.just(ScheduleJobResponse.builder().expression(request.getExpression())\n\t\t\t\t\t.expressionType(request.getExpressionType())\n\t\t\t\t\t.enabled(true)\n\t\t\t\t\t.jobId(request.getJobId())\n\t\t\t\t\t.id(\"schedule-1234\")\n\t\t\t\t\t.build());\n\t\t}\n\n\t\tpublic CreateJobResponse getCreateJobResponse() {\n\t\t\tif(this.jobResources.size() == 0) {\n\t\t\t\tthis.createJobResponse = null;\n\t\t\t}\n\t\t\treturn createJobResponse;\n\t\t}\n\t}\n\n\tprivate Flux<SpaceSummary> getTestSpaces() {\n\t\treturn Flux.just(SpaceSummary.builder().id(\"test-space-1\")\n\t\t\t\t.name(\"test-space\")\n\t\t\t\t.build());\n\t}\n\n\tprivate void setupMockResults() {\n\t\tmockJobsInJobList();\n\t\tmockAppResultsInAppList();\n\t}\n\n\tprivate void setupMockResultsNoScheduleForJobs() {\n\t\tmockJobsInJobListNoSchedule();\n\t\tmockAppResultsInAppList();\n\t}\n\n\tprivate void mockAppResultsInAppList() {\n\t\tgivenRequestListApplications(Flux.just(ApplicationSummary.builder()\n\t\t\t\t\t\t.diskQuota(0)\n\t\t\t\t\t\t.id(\"test-application-id-1\")\n\t\t\t\t\t\t.instances(1)\n\t\t\t\t\t\t.memoryLimit(0)\n\t\t\t\t\t\t.name(\"test-application-1\")\n\t\t\t\t\t\t.requestedState(\"RUNNING\")\n\t\t\t\t\t\t.runningInstances(1)\n\t\t\t\t\t\t.build(),\n\t\t\t\tApplicationSummary.builder()\n\t\t\t\t\t\t.diskQuota(0)\n\t\t\t\t\t\t.id(\"test-application-id-2\")\n\t\t\t\t\t\t.instances(1)\n\t\t\t\t\t\t.memoryLimit(0)\n\t\t\t\t\t\t.name(\"test-application-2\")\n\t\t\t\t\t\t.requestedState(\"RUNNING\")\n\t\t\t\t\t\t.runningInstances(1)\n\t\t\t\t\t\t.build()));\n\t}\n\n\tprivate void mockJobsInJobListNoSchedule() {\n\t\tTestJobs localJobs = (TestJobs) client.jobs();\n\t\tlocalJobs.jobResources.add(Job.builder().applicationId(\"test-application-id-1\")\n\t\t\t\t.command(\"test-command\")\n\t\t\t\t.id(\"test-job-1\")\n\t\t\t\t.name(\"test-job-name-1\")\n\t\t\t\t.build());\n\t\tlocalJobs.jobResources.add(Job.builder().applicationId(\"test-application-id-2\")\n\t\t\t\t.command(\"test-command\")\n\t\t\t\t.id(\"test-job-2\")\n\t\t\t\t.name(\"test-job-name-2\")\n\t\t\t\t.build());\n\t}\n\n\tprivate void mockJobsInJobList() {\n\t\tTestJobs localJobs = (TestJobs) client.jobs();\n\t\tlocalJobs.jobResources.add(Job.builder().applicationId(\"test-application-id-1\")\n\t\t\t\t.command(\"test-command\")\n\t\t\t\t.id(\"test-job-1\")\n\t\t\t\t.name(\"test-job-name-1\")\n\t\t\t\t.jobSchedules(createJobScheduleList(\"test-job-1\", \"test-schedule-1\"))\n\t\t\t\t.build());\n\t\tlocalJobs.jobResources.add(Job.builder().applicationId(\"test-application-id-2\")\n\t\t\t\t.command(\"test-command\")\n\t\t\t\t.id(\"test-job-2\")\n\t\t\t\t.name(\"test-job-name-2\")\n\t\t\t\t.jobSchedules(createJobScheduleList(\"test-job-2\", \"test-schedule-2\"))\n\t\t\t\t.build());\n\t}\n\n\tprivate List<JobSchedule> createJobScheduleList(String jobId, String scheduleId) {\n\t\tList<JobSchedule> jobSchedules = new ArrayList<>();\n\t\tjobSchedules.add(JobSchedule.builder()\n\t\t\t\t.enabled(true)\n\t\t\t\t.expression(DEFAULT_CRON_EXPRESSION)\n\t\t\t\t.expressionType(ExpressionType.CRON)\n\t\t\t\t.id(scheduleId)\n\t\t\t\t.jobId(jobId)\n\t\t\t\t.build());\n\t\treturn jobSchedules;\n\t}\n\n\tprivate Map<String, String> getDefaultScheduleProperties() {\n\t\tMap<String, String> result = new HashMap<>();\n\t\tresult.put(SchedulerPropertyKeys.CRON_EXPRESSION, DEFAULT_CRON_EXPRESSION);\n\t\treturn result;\n\t}\n\n\tprivate Map<String, String> getDefaultDeploymentProperties() {\n\t\tMap<String, String> result = new HashMap<>();\n\t\tresult.put(CloudFoundryAppScheduler.CRON_EXPRESSION_KEY, DEFAULT_CRON_EXPRESSION);\n\t\treturn result;\n\t}\n\n\tprivate CloudFoundryAppScheduler getCloudFoundryAppScheduler(boolean isDeprecated) {\n\t\treturn isDeprecated ? this.deprecatedCloudFoundryAppScheduler : this.cloudFoundryAppScheduler;\n\t}\n\n\tprivate CloudFoundryAppScheduler getNoServiceCloudFoundryAppScheduler(boolean isDeprecated) {\n\t\treturn isDeprecated ? this.deprecatedNoServiceCloudFoundryAppScheduler : this.noServiceCloudFoundryAppScheduler;\n\t}\n}\n"
  },
  {
    "path": "spring-cloud-deployer-cloudfoundry/src/test/java/org/springframework/cloud/deployer/spi/scheduler/cloudfoundry/CloudFoundryScheduleSSLExceptionTests.java",
    "content": "/*\n * Copyright 2018-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.scheduler.cloudfoundry;\n\n\nimport org.junit.jupiter.api.Test;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * Verify that {@linkCloudFoundryScheduleSSLException} has the expected behavior.\n *\n * @author Glenn Renfro\n */\npublic class CloudFoundryScheduleSSLExceptionTests {\n\n\t@Test\n\tpublic void testExceptionMessageOnly() {\n\t\ttry {\n\t\t\tthrow new CloudFoundryScheduleSSLException(\"oops\");\n\t\t}\n\t\tcatch (CloudFoundryScheduleSSLException cfe) {\n\t\t\tassertThat(cfe.getMessage()).isEqualTo(\"oops\");\n\t\t}\n\t}\n\n\t@Test\n\tpublic void testExceptionMessageWithException() {\n\t\tRuntimeException rte = new RuntimeException(\"RTE\");\n\t\ttry {\n\t\t\tthrow new CloudFoundryScheduleSSLException(\"oops\", rte);\n\t\t}\n\t\tcatch (CloudFoundryScheduleSSLException cfe) {\n\t\t\tassertThat(cfe.getMessage()).isEqualTo(\"oops\");\n\t\t\tassertThat(cfe.getCause()).isEqualTo(rte);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "spring-cloud-deployer-cloudfoundry/src/test/java/org/springframework/cloud/deployer/spi/scheduler/cloudfoundry/CloudFoundrySchedulerPropertiesTest.java",
    "content": "/*\n * Copyright 2018-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.scheduler.cloudfoundry;\n\nimport org.junit.jupiter.api.Test;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * Validate the basic behavior of the {@link CloudFoundrySchedulerProperties}.\n *\n * @author Glenn Renfro\n */\npublic class CloudFoundrySchedulerPropertiesTest {\n\n\t@Test\n\tpublic void testProperties() {\n\t\tCloudFoundrySchedulerProperties props = new CloudFoundrySchedulerProperties();\n\t\tprops.setSchedulerUrl(\"testProperty\");\n\t\tprops.setScheduleSSLRetryCount(10);\n\t\tprops.setListTimeoutInSeconds(5);\n\t\tprops.setUnScheduleTimeoutInSeconds(10);\n\t\tprops.setScheduleTimeoutInSeconds(15);\n\t\tassertThat(props.getSchedulerUrl()).isEqualTo(\"testProperty\");\n\t\tassertThat(props.getScheduleSSLRetryCount()).isEqualTo(10);\n\t\tassertThat(props.getScheduleTimeoutInSeconds()).isEqualTo(15);\n\t\tassertThat(props.getUnScheduleTimeoutInSeconds()).isEqualTo(10);\n\t\tassertThat(props.getListTimeoutInSeconds()).isEqualTo(5);\n\t}\n\n\t@Test\n\tpublic void testEmptyProperties() {\n\t\tCloudFoundrySchedulerProperties props = new CloudFoundrySchedulerProperties();\n\t\tassertThat(props.getSchedulerUrl()).isNull();\n\t\tassertThat(props.getScheduleSSLRetryCount()).isEqualTo(5);\n\t\tassertThat(props.getListTimeoutInSeconds()).isEqualTo(60);\n\t\tassertThat(props.getScheduleTimeoutInSeconds()).isEqualTo(30);\n\t\tassertThat(props.getUnScheduleTimeoutInSeconds()).isEqualTo(30);\n\t}\n\n}\n"
  },
  {
    "path": "spring-cloud-deployer-cloudfoundry/src/test/java/org/springframework/cloud/deployer/spi/scheduler/cloudfoundry/SpringCloudSchedulerIntegrationIT.java",
    "content": "/*\n * Copyright 2018-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.scheduler.cloudfoundry;\n\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\nimport io.pivotal.reactor.scheduler.ReactorSchedulerClient;\nimport org.cloudfoundry.operations.CloudFoundryOperations;\nimport org.cloudfoundry.operations.applications.DeleteApplicationRequest;\nimport org.cloudfoundry.reactor.ConnectionContext;\nimport org.cloudfoundry.reactor.TokenProvider;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport reactor.core.publisher.Mono;\n\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.boot.autoconfigure.EnableAutoConfiguration;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;\nimport org.springframework.boot.context.properties.EnableConfigurationProperties;\nimport org.springframework.boot.test.context.SpringBootTest;\nimport org.springframework.boot.test.context.SpringBootTest.WebEnvironment;\nimport org.springframework.cloud.deployer.resource.maven.MavenProperties;\nimport org.springframework.cloud.deployer.spi.cloudfoundry.CloudFoundryConnectionProperties;\nimport org.springframework.cloud.deployer.spi.cloudfoundry.CloudFoundryDeploymentProperties;\nimport org.springframework.cloud.deployer.spi.cloudfoundry.CloudFoundryTaskLauncher;\nimport org.springframework.cloud.deployer.spi.scheduler.Scheduler;\nimport org.springframework.cloud.deployer.spi.scheduler.SchedulerPropertyKeys;\nimport org.springframework.cloud.deployer.spi.scheduler.test.AbstractSchedulerIntegrationJUnit5Tests;\nimport org.springframework.cloud.deployer.spi.task.TaskLauncher;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.test.context.ContextConfiguration;\nimport org.springframework.test.context.junit.jupiter.SpringExtension;\n\n/**\n * Integration tests for CloudFoundryAppScheduler.\n *\n * @author Glenn Renfro\n */\n@ExtendWith(SpringExtension.class)\n@SpringBootTest(webEnvironment = WebEnvironment.NONE)\n@ContextConfiguration(classes = {SpringCloudSchedulerIntegrationIT.Config.class})\npublic class SpringCloudSchedulerIntegrationIT extends AbstractSchedulerIntegrationJUnit5Tests {\n\n\t@Autowired\n\tprotected MavenProperties mavenProperties;\n\n\t@Autowired\n\tprivate Scheduler scheduler;\n\n\t@Value(\"${spring.cloud.deployer.cloudfoundry.services}\")\n\tprivate String deployerProps;\n\n\t@Override\n\tprotected Scheduler provideScheduler() {\n\t\treturn this.scheduler;\n\t}\n\n\t@Autowired\n\tprivate CloudFoundryOperations operations;\n\n\t@Override\n\tprotected List<String> getCommandLineArgs() {\n\t\treturn null;\n\t}\n\n\t@Override\n\tprotected Map<String, String> getSchedulerProperties() {\n\t\treturn Collections.singletonMap(SchedulerPropertyKeys.CRON_EXPRESSION,\"41 17 ? * *\");\n\t}\n\n\t@Override\n\tprotected Map<String, String> getDeploymentProperties() {\n\t\tMap<String, String> deploymentProperties = new HashMap<>();\n\t\tdeploymentProperties.put(CloudFoundryDeploymentProperties.SERVICES_PROPERTY_KEY, deployerProps);\n\t\tdeploymentProperties.put(CloudFoundryAppScheduler.CRON_EXPRESSION_KEY, \"57 13 ? * *\");\n\t\treturn deploymentProperties;\n\t}\n\n\t@Override\n\tprotected Map<String, String> getAppProperties() {\n\t\treturn null;\n\t}\n\n\t/**\n\t * Remove all pushed apps.  This in turn removes the associated schedules.\n\t */\n\t@AfterEach\n\tpublic void tearDown() {\n\t\ttry {\n\t\t\toperations.applications().list().flatMap(applicationSummary -> {\n\t\t\t\tif (applicationSummary.getName().startsWith(\"testList\") ||\n\t\t\t\t\t\tapplicationSummary.getName().startsWith(\"testDuplicateSchedule\") ||\n\t\t\t\t\t\tapplicationSummary.getName().startsWith(\"testUnschedule\") ||\n\t\t\t\t\t\tapplicationSummary.getName().startsWith(\"testMultiple\") ||\n\t\t\t\t\t\tapplicationSummary.getName().startsWith(\"testSimpleSchedule\")) {\n\n\t\t\t\t\treturn operations.applications().delete(DeleteApplicationRequest\n\t\t\t\t\t\t\t.builder()\n\t\t\t\t\t\t\t.name(applicationSummary.getName())\n\t\t\t\t\t\t\t.build());\n\t\t\t\t}\n\t\t\t\treturn Mono.justOrEmpty(applicationSummary);\n\t\t\t}).blockLast();\n\t\t} catch (Exception ex) {\n\t\t\tlog.warn(\"Attempted cleanup and exception occured: \" + ex.getMessage());\n\t\t}\n\t}\n\n\t@Configuration\n\t@EnableAutoConfiguration\n\t@EnableConfigurationProperties\n\tpublic static class Config {\n\t\t@Bean\n\t\t@ConditionalOnMissingBean\n\t\tpublic ReactorSchedulerClient reactorSchedulerClient(ConnectionContext context,\n\t\t\t\tTokenProvider passwordGrantTokenProvider,\n\t\t\t\tCloudFoundryDeploymentProperties taskDeploymentProperties) {\n\t\t\treturn ReactorSchedulerClient.builder()\n\t\t\t\t\t.connectionContext(context)\n\t\t\t\t\t.tokenProvider(passwordGrantTokenProvider)\n\t\t\t\t\t.root(Mono.just(taskDeploymentProperties.getSchedulerUrl()))\n\t\t\t\t\t.build();\n\t\t}\n\n\t\t@Bean\n\t\t@ConditionalOnMissingBean\n\t\tpublic Scheduler scheduler(ReactorSchedulerClient client,\n\t\t\t\tCloudFoundryOperations operations,\n\t\t\t\tCloudFoundryConnectionProperties properties,\n\t\t\t\tTaskLauncher taskLauncher,\n\t\t\t\tCloudFoundryDeploymentProperties taskDeploymentProperties) {\n\t\t\treturn new CloudFoundryAppScheduler(client, operations, properties,\n\t\t\t\t\t(CloudFoundryTaskLauncher) taskLauncher,\n\t\t\t\t\ttaskDeploymentProperties);\n\t\t}\n\n\t}\n}\n"
  },
  {
    "path": "spring-cloud-deployer-cloudfoundry/src/test/java/org/springframework/cloud/deployer/spi/scheduler/cloudfoundry/expression/QuartzCronExpressionTests.java",
    "content": "/*\n * Copyright 2018-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.scheduler.cloudfoundry.expression;\n\nimport java.text.ParseException;\n\nimport org.junit.jupiter.api.Test;\n\nimport static org.assertj.core.api.Assertions.assertThatCode;\nimport static org.assertj.core.api.Assertions.assertThatThrownBy;\n\npublic class QuartzCronExpressionTests {\n\n\t/*\n\t * Verifies that storeExpressionVals correctly calculates the month number\n\t */\n\t@Test\n\tpublic void testStoreExpressionVal() {\n\t\tassertExpression(\"* * * * Foo ? \", \"Invalid Month value:\",\n\t\t\t\t\"Expected ParseException did not fire for non-existent month\");\n\t\tassertExpression(\"* * * * Jan-Foo ? \", \"Invalid Month value:\",\n\t\t\t\t\"Expected ParseException did not fire for non-existent month\");\n\t}\n\n\t@Test\n\tpublic void testWildCard() {\n\t\tassertExpression(\"0 0 * * * *\",\n\t\t\t\t\"Support for specifying both a day-of-week AND a day-of-month parameter is not implemented.\",\n\t\t\t\t\"Expected ParseException did not fire for wildcard day-of-month and day-of-week\");\n\t\tassertExpression(\"0 0 * 4 * *\",\n\t\t\t\t\"Support for specifying both a day-of-week AND a day-of-month parameter is not implemented.\",\n\t\t\t\t\"Expected ParseException did not fire for specified day-of-month and wildcard day-of-week\");\n\t\tassertExpression(\"0 0 * * * 4\",\n\t\t\t\t\"Support for specifying both a day-of-week AND a day-of-month parameter is not implemented.\",\n\t\t\t\t\"Expected ParseException did not fire for wildcard day-of-month and specified day-of-week\");\n\t}\n\n\t@Test\n\tpublic void testForInvalidLInCronExpression() {\n\t\tassertExpression(\"0 43 9 1,5,29,L * ?\",\n\t\t\t\t\"Support for specifying 'L' and 'LW' with other days of the month is not implemented\",\n\t\t\t\t\"Expected ParseException did not fire for L combined with other days of the month\");\n\t\tassertExpression(\"0 43 9 ? * SAT,SUN,L\",\n\t\t\t\t\"Support for specifying 'L' with other days of the week is not implemented\",\n\t\t\t\t\"Expected ParseException did not fire for L combined with other days of the week\");\n\t\tassertExpression(\"0 43 9 ? * 6,7,L\",\n\t\t\t\t\"Support for specifying 'L' with other days of the week is not implemented\",\n\t\t\t\t\"Expected ParseException did not fire for L combined with other days of the week\");\n\t\tassertThatCode(() -> {\n\t\t\tnew QuartzCronExpression(\"0 43 9 ? * 5L\");\n\t\t}).as(\"Unexpected ParseException thrown for supported '5L' expression.\").doesNotThrowAnyException();\n\t}\n\n\t@Test\n\tpublic void testForLargeWVal() {\n\t\tassertExpression(\"0/5 * * 32W 1 ?\", \"The 'W' option does not make sense with values larger than\",\n\t\t\t\t\"Expected ParseException did not fire for W with value larger than 31\");\n\t}\n\n\t@Test\n\tpublic void testSecRangeIntervalAfterSlash() {\n\t\t// Test case 1\n\t\tassertExpression(\"/120 0 8-18 ? * 2-6\", \"Increment > 60 : 120\",\n\t\t\t\t\"Cron did not validate bad range interval in '_blank/xxx' form\");\n\t\t// Test case 2\n\t\tassertExpression(\"0/120 0 8-18 ? * 2-6\", \"Increment > 60 : 120\",\n\t\t\t\t\"Cron did not validate bad range interval in in '0/xxx' form\");\n\t\t// Test case 3\n\t\tassertExpression(\"/ 0 8-18 ? * 2-6\", \"'/' must be followed by an integer.\",\n\t\t\t\t\"Cron did not validate bad range interval in '_blank/_blank'\");\n\t\t// Test case 4\n\t\tassertExpression(\"0/ 0 8-18 ? * 2-6\", \"'/' must be followed by an integer.\",\n\t\t\t\t\"Cron did not validate bad range interval in '0/_blank'\");\n\t}\n\n\t@Test\n\tpublic void testMinRangeIntervalAfterSlash() {\n\t\t// Test case 1\n\t\tassertExpression(\"0 /120 8-18 ? * 2-6\", \"Increment > 60 : 120\",\n\t\t\t\t\"Cron did not validate bad range interval in '_blank/xxx' form\");\n\t\t// Test case 2\n\t\tassertExpression(\"0 0/120 8-18 ? * 2-6\", \"Increment > 60 : 120\",\n\t\t\t\t\"Cron did not validate bad range interval in in '0/xxx' form\");\n\t\t// Test case 3\n\t\tassertExpression(\"0 / 8-18 ? * 2-6\", \"'/' must be followed by an integer.\",\n\t\t\t\t\"Cron did not validate bad range interval in '_blank/_blank'\");\n\t\t// Test case 4\n\t\tassertExpression(\"0 0/ 8-18 ? * 2-6\", \"'/' must be followed by an integer.\",\n\t\t\t\t\"Cron did not validate bad range interval in '0/_blank'\");\n\t}\n\n\t@Test\n\tpublic void testHourRangeIntervalAfterSlash() {\n\t\t// Test case 1\n\t\tassertExpression(\"0 0 /120 ? * 2-6\", \"Increment > 24 : 120\",\n\t\t\t\t\"Cron did not validate bad range interval in '_blank/xxx' form\");\n\t\t// Test case 2\n\t\tassertExpression(\"0 0 0/120 ? * 2-6\", \"Increment > 24 : 120\",\n\t\t\t\t\"Cron did not validate bad range interval in in '0/xxx' form\");\n\t\t// Test case 3\n\t\tassertExpression(\"0 0 / ? * 2-6\", \"'/' must be followed by an integer.\",\n\t\t\t\t\"Cron did not validate bad range interval in '_blank/_blank'\");\n\t\t// Test case 4\n\t\tassertExpression(\"0 0 0/ ? * 2-6\", \"'/' must be followed by an integer.\",\n\t\t\t\t\"Cron did not validate bad range interval in '0/_blank'\");\n\t}\n\n\t@Test\n\tpublic void testDayOfMonthRangeIntervalAfterSlash() {\n\t\t// Test case 1\n\t\tassertExpression(\"0 0 0 /120 * 2-6\", \"Increment > 31 : 120\",\n\t\t\t\t\"Cron did not validate bad range interval in '_blank/xxx' form\");\n\t\t// Test case 2\n\t\tassertExpression(\"0 0 0 0/120 * 2-6\", \"Increment > 31 : 120\",\n\t\t\t\t\"Cron did not validate bad range interval in in '0/xxx' form\");\n\t\t// Test case 3\n\t\tassertExpression(\"0 0 0 / * 2-6\", \"'/' must be followed by an integer.\",\n\t\t\t\t\"Cron did not validate bad range interval in '_blank/_blank'\");\n\t\t// Test case 4\n\t\tassertExpression(\"0 0 0 0/ * 2-6\", \"'/' must be followed by an integer.\",\n\t\t\t\t\"Cron did not validate bad range interval in '0/_blank'\");\n\t}\n\n\t@Test\n\tpublic void testMonthRangeIntervalAfterSlash() {\n\t\t// Test case 1\n\t\tassertExpression(\"0 0 0 ? /120 2-6\", \"Increment > 12 : 120\",\n\t\t\t\t\"Cron did not validate bad range interval in '_blank/xxx' form\");\n\t\t// Test case 2\n\t\tassertExpression(\"0 0 0 ? 0/120 2-6\", \"Increment > 12 : 120\",\n\t\t\t\t\"Cron did not validate bad range interval in in '0/xxx' form\");\n\t\t// Test case 3\n\t\tassertExpression(\"0 0 0 ? / 2-6\", \"'/' must be followed by an integer.\",\n\t\t\t\t\"Cron did not validate bad range interval in '_blank/_blank'\");\n\t\t// Test case 4\n\t\tassertExpression(\"0 0 0 ? 0/ 2-6\", \"'/' must be followed by an integer.\",\n\t\t\t\t\"Cron did not validate bad range interval in '0/_blank'\");\n\t}\n\n\t@Test\n\tpublic void testDayOfWeekRangeIntervalAfterSlash() {\n\t\t// Test case 1\n\t\tassertExpression(\"0 0 0 ? * /120\", \"Increment > 7 : 120\",\n\t\t\t\t\"Cron did not validate bad range interval in '_blank/xxx' form\");\n\t\t// Test case 2\n\t\tassertExpression(\"0 0 0 ? * 0/120\", \"Increment > 7 : 120\",\n\t\t\t\t\"Cron did not validate bad range interval in in '0/xxx' form\");\n\t\t// Test case 3\n\t\tassertExpression(\"0 0 0 ? * /\", \"'/' must be followed by an integer.\",\n\t\t\t\t\"Cron did not validate bad range interval in '_blank/_blank'\");\n\t\t// Test case 4\n\t\tassertExpression(\"0 0 0 ? * 0/\", \"'/' must be followed by an integer.\",\n\t\t\t\t\"Cron did not validate bad range interval in '0/_blank'\");\n\t}\n\n\tprivate static void assertExpression(String expression, String messageContains, String as) {\n\t\tassertThatThrownBy(() -> {\n\t\t\tnew QuartzCronExpression(expression);\n\t\t}).isInstanceOf(ParseException.class).hasMessageContaining(messageContains).as(as);\n\t}\n}\n"
  },
  {
    "path": "spring-cloud-deployer-cloudfoundry/src/test/resources/logback-test.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<configuration>\n\n    <appender name=\"STDOUT\" class=\"ch.qos.logback.core.ConsoleAppender\">\n        <encoder>\n            <pattern>%date{HH:mm:ss.SSS} %-25thread %-37logger %msg%n</pattern>\n        </encoder>\n    </appender>\n\n    <logger name=\"cloudfoundry-client\" level=\"DEBUG\"/>\n    <logger name=\"org.springframework.cloud.deployer\" level=\"DEBUG\"/>\n    <!--\n    <logger name=\"reactor.ipc.netty\"   level=\"DEBUG\"/>\n    <logger name=\"stream\"              level=\"INFO\"/>\n    -->\n\n    <root level=\"WARN\">\n        <appender-ref ref=\"STDOUT\"/>\n    </root>\n\n</configuration>\n"
  },
  {
    "path": "spring-cloud-deployer-dependencies/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd\">\n\t<modelVersion>4.0.0</modelVersion>\n\n\t<groupId>org.springframework.cloud</groupId>\n\t<artifactId>spring-cloud-deployer-dependencies</artifactId>\n\t<version>3.0.0-SNAPSHOT</version>\n\n\t<packaging>pom</packaging>\n\t<name>Spring Cloud Deployer Dependencies</name>\n\t<description>Spring Cloud Deployer Dependencies</description>\n\t<organization>\n\t\t<name>Pivotal Software, Inc.</name>\n\t\t<url>https://www.spring.io</url>\n\t</organization>\n\t<url>https://github.com/spring-cloud/spring-cloud-deployer</url>\n\t<licenses>\n\t\t<license>\n\t\t\t<name>Apache License, Version 2.0</name>\n\t\t\t<url>https://www.apache.org/licenses/LICENSE-2.0</url>\n\t\t\t<comments>\n\t\t\t\tCopyright 2014-2021 the original author or authors.\n\n\t\t\t\tLicensed under the Apache License, Version 2.0 (the \"License\");\n\t\t\t\tyou may not use this file except in compliance with the License.\n\t\t\t\tYou may obtain a copy of the License at\n\n\t\t\t\thttps://www.apache.org/licenses/LICENSE-2.0\n\n\t\t\t\tUnless required by applicable law or agreed to in writing, software\n\t\t\t\tdistributed under the License is distributed on an \"AS IS\" BASIS,\n\t\t\t\tWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n\t\t\t\timplied.\n\n\t\t\t\tSee the License for the specific language governing permissions and\n\t\t\t\tlimitations under the License.\n\t\t\t</comments>\n\t\t</license>\n\t</licenses>\n\t<scm>\n\t\t<url>https://github.com/spring-cloud/spring-cloud-deployer</url>\n\t\t<connection>scm:git:git://github.com/spring-cloud/spring-cloud-deployer.git</connection>\n\t\t<developerConnection>scm:git:ssh://git@github.com/spring-cloud/spring-cloud-deployer.git</developerConnection>\n\t\t<tag>HEAD</tag>\n\t</scm>\n\t<developers>\n\t\t<developer>\n\t\t\t<id>scdf-team</id>\n\t\t\t<name>Data Flow Team</name>\n\t\t\t<organizationUrl>https://github.com/spring-cloud/spring-cloud-deployer/graphs/contributors</organizationUrl>\n\t\t</developer>\n\t</developers>\n\t<dependencyManagement>\n\t\t<dependencies>\n\t\t\t<dependency>\n\t\t\t\t<groupId>org.springframework.cloud</groupId>\n\t\t\t\t<artifactId>spring-cloud-deployer-resource-docker</artifactId>\n\t\t\t\t<version>3.0.0-SNAPSHOT</version>\n\t\t\t</dependency>\n\t\t\t<dependency>\n\t\t\t\t<groupId>org.springframework.cloud</groupId>\n\t\t\t\t<artifactId>spring-cloud-deployer-resource-maven</artifactId>\n\t\t\t\t<version>3.0.0-SNAPSHOT</version>\n\t\t\t</dependency>\n\t\t\t<dependency>\n\t\t\t\t<groupId>org.springframework.cloud</groupId>\n\t\t\t\t<artifactId>spring-cloud-deployer-resource-support</artifactId>\n\t\t\t\t<version>3.0.0-SNAPSHOT</version>\n\t\t\t</dependency>\n\t\t\t<dependency>\n\t\t\t\t<groupId>org.springframework.cloud</groupId>\n\t\t\t\t<artifactId>spring-cloud-deployer-spi</artifactId>\n\t\t\t\t<version>3.0.0-SNAPSHOT</version>\n\t\t\t</dependency>\n\t\t\t<dependency>\n\t\t\t\t<groupId>org.springframework.cloud</groupId>\n\t\t\t\t<artifactId>spring-cloud-deployer-autoconfigure</artifactId>\n\t\t\t\t<version>3.0.0-SNAPSHOT</version>\n\t\t\t</dependency>\n\t\t</dependencies>\n\t</dependencyManagement>\n\n\t<profiles>\n\t\t<profile>\n\t\t\t<id>spring</id>\n\t\t\t<activation><activeByDefault>true</activeByDefault></activation>\n\t\t\t<repositories>\n\t\t\t\t<repository>\n\t\t\t\t\t<id>maven-central</id>\n\t\t\t\t\t<name>Maven Central</name>\n\t\t\t\t\t<url>https://repo.maven.apache.org/maven2</url>\n\t\t\t\t\t<snapshots>\n\t\t\t\t\t\t<enabled>false</enabled>\n\t\t\t\t\t</snapshots>\n\t\t\t\t</repository>\n\t\t\t\t<repository>\n\t\t\t\t\t<id>spring-snapshots</id>\n\t\t\t\t\t<name>Spring Snapshots</name>\n\t\t\t\t\t<url>https://repo.spring.io/libs-snapshot</url>\n\t\t\t\t\t<snapshots>\n\t\t\t\t\t\t<enabled>true</enabled>\n\t\t\t\t\t</snapshots>\n\t\t\t\t</repository>\n\t\t\t\t<repository>\n\t\t\t\t\t<id>spring-milestones</id>\n\t\t\t\t\t<name>Spring Milestones</name>\n\t\t\t\t\t<url>https://repo.spring.io/libs-milestone</url>\n\t\t\t\t\t<snapshots>\n\t\t\t\t\t\t<enabled>false</enabled>\n\t\t\t\t\t</snapshots>\n\t\t\t\t</repository>\n\t\t\t</repositories>\n\t\t\t<pluginRepositories>\n\t\t\t\t<pluginRepository>\n\t\t\t\t\t<id>maven-central</id>\n\t\t\t\t\t<name>Maven Central</name>\n\t\t\t\t\t<url>https://repo.maven.apache.org/maven2</url>\n\t\t\t\t\t<snapshots>\n\t\t\t\t\t\t<enabled>false</enabled>\n\t\t\t\t\t</snapshots>\n\t\t\t\t</pluginRepository>\n\t\t\t\t<pluginRepository>\n\t\t\t\t\t<id>spring-snapshots</id>\n\t\t\t\t\t<name>Spring Snapshots</name>\n\t\t\t\t\t<url>https://repo.spring.io/libs-snapshot</url>\n\t\t\t\t\t<snapshots>\n\t\t\t\t\t\t<enabled>true</enabled>\n\t\t\t\t\t</snapshots>\n\t\t\t\t</pluginRepository>\n\t\t\t\t<pluginRepository>\n\t\t\t\t\t<id>spring-milestones</id>\n\t\t\t\t\t<name>Spring Milestones</name>\n\t\t\t\t\t<url>https://repo.spring.io/libs-milestone</url>\n\t\t\t\t\t<snapshots>\n\t\t\t\t\t\t<enabled>false</enabled>\n\t\t\t\t\t</snapshots>\n\t\t\t\t</pluginRepository>\n\t\t\t</pluginRepositories>\n\t\t</profile>\n\t</profiles>\n</project>\n"
  },
  {
    "path": "spring-cloud-deployer-kubernetes/README.md",
    "content": "# Spring Cloud Deployer Kubernetes\nA [Spring Cloud Deployer](https://github.com/spring-cloud/spring-cloud-deployer) implementation for deploying long-lived streaming applications and short-lived tasks to Kubernetes.\n\n## Kubernetes Compatibility\n\n| Deployer \\ Kubernetes | 1.20 | 1.21 | 1.22 | 1.23 | 1.24  | 1.25  | 1.26 | 1.27 | 1.28 | 1.29 | 1.30 |  \n|-----------------------|------|------|------|------|-------|-------|------|------|------|------|------|\n| **2.6.x**             | `✕`  | `✕`  | `✕`   | `✕` | `✕`   | `✕`   |`✕`  | `✕`   | `✕`  | `✕`  | `✕`  |\n| **2.7.x**             | `✓`  | `✓`  | `?`   | `?` | `?`   | `✕`   |`✕`  | `✕`   | `✕`  | `✕`  | `✕`  |\n| **2.8.x**             | `✓`  | `✓`  | `✓`   | `✓` | `✓`   | `✕`   |`✕`  | `✕`   | `✕`  | `✕`  | `✕`  |\n| **2.9.x**             | `✓`  | `✓`  | `✓`   | `✓` | `✓`   | `✓`   |`✓`  | `✓`   | `✓`  | `✓`  | `✓`  |\n| **3.0.x**             | `✓`  | `✓`  | `✓`   | `✓` | `✓`   | `✓`   |`✓`  | `✓`   | `✓`  | `✓`  | `✓`  |\n\n- `✓` Fully supported version\n- `?` Due to breaking changes might not work _(e.g., ABAC vs RBAC)_. Also, we haven't thoroughly tested against this version.\n- `✕` Unsupported version.\n\n## Building\n\nBuild the project without running tests using:\n\n```\n./mvnw clean install -DskipTests\n```\n\n## Integration tests\n\nThe integration tests require a running Kubernetes cluster. A couple of options are listed below.\n\n### Minkube\n[Minikube](https://github.com/kubernetes/minikube) is a tool that makes it easy to run Kubernetes locally. It runs a single-node Kubernetes cluster inside a VM on your laptop for users looking to try out Kubernetes or develop with it day-to-day. \n\nFollow the [getting started](https://minikube.sigs.k8s.io/docs/start/) guide to install Minikube.\n\n1. Start Minikube\n   ```shell\n   minkube start\n   ```\n2. Run the tests\n   ```shell\n   ./mvnw clean test\n   ```\n3. Stop Minikube\n   ```shell\n   minkube stop\n   ```\n\n\n### Google Container Engine\nWhile Minikube is very easy to run and test against, it is preferred to test against a GKE cluster. Minikube is not as useful since we test some parts of the external IP features that a LoadBalancer service provides.\n\nCreate a test cluster and target it using something like (use your own project name, substitute --zone if needed):\n\n```\ngcloud container --project {your-project-name} clusters create \"spring-test\" --zone \"us-central1-b\" --machine-type \"n1-highcpu-2\" --scopes \"https://www.googleapis.com/auth/compute\",\"https://www.googleapis.com/auth/devstorage.read_only\",\"https://www.googleapis.com/auth/logging.write\" --network \"default\" --enable-cloud-logging --enable-cloud-monitoring\ngcloud config set container/cluster spring-test\ngcloud config set compute/zone us-central1-b\ngcloud container clusters get-credentials spring-test\nkubectl version\n```\n> :information_source: the last command causes the access token to be generated and saved to the kubeconfig file - it can be any valid kubectl command\n\n#### Running the tests\n\nOnce the test cluster has been created, you can run all integration tests.\n\nAs long as your `kubectl` config files are set to point to your cluster, you should be able to just run the tests. Verify your config using `kubectl config get-contexts` and check that your test cluster is the current context.\n\nNow run the tests:\n\n```\n$ ./mvnw test\n```\n\nNOTE: if you get authentication errors, try setting basic auth credentials:\n\nNavigate to your project and cluster on https://console.cloud.google.com/  and click on `show credentials`\n\n```bash\n$export KUBERNETES_AUTH_BASIC_PASSWORD=\n$export KUBERNETES_AUTH_BASIC_USERNAME=\n```\n\n\n"
  },
  {
    "path": "spring-cloud-deployer-kubernetes/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd\">\n\t<modelVersion>4.0.0</modelVersion>\n\n\t<artifactId>spring-cloud-deployer-kubernetes</artifactId>\n\t<version>3.0.0-SNAPSHOT</version>\n\t<packaging>jar</packaging>\n\t<name>Spring Cloud Deployer Kubernetes</name>\n\n\t<parent>\n\t\t<groupId>org.springframework.cloud</groupId>\n\t\t<artifactId>spring-cloud-deployer-parent</artifactId>\n\t\t<version>3.0.0-SNAPSHOT</version>\n\t\t<relativePath>..</relativePath>\n\t</parent>\n\n\t<properties>\n\t\t<!-- TODO remove this when spring-cloud-deployer/issues/484 is complete -->\n\t\t<powermock.version>2.0.2</powermock.version>\n\t</properties>\n\n\t<dependencies>\n\t\t<dependency>\n\t\t\t<groupId>org.springframework.cloud</groupId>\n\t\t\t<artifactId>spring-cloud-deployer-spi</artifactId>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.springframework.cloud</groupId>\n\t\t\t<artifactId>spring-cloud-deployer-resource-docker</artifactId>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.springframework.boot</groupId>\n\t\t\t<artifactId>spring-boot-autoconfigure</artifactId>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.springframework</groupId>\n\t\t\t<artifactId>spring-web</artifactId>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>io.fabric8</groupId>\n\t\t\t<artifactId>kubernetes-client</artifactId>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.hashids</groupId>\n\t\t\t<artifactId>hashids</artifactId>\n\t\t\t<version>${hashids.version}</version>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.springframework.boot</groupId>\n\t\t\t<artifactId>spring-boot-configuration-processor</artifactId>\n\t\t\t<optional>true</optional>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.springframework.cloud</groupId>\n\t\t\t<artifactId>spring-cloud-deployer-spi-test</artifactId>\n\t\t\t<version>${project.version}</version>\n\t\t\t<scope>test</scope>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.powermock</groupId>\n\t\t\t<artifactId>powermock-module-junit4</artifactId>\n\t\t\t<version>${powermock.version}</version>\n\t\t\t<scope>test</scope>\n\t\t\t<exclusions>\n\t\t\t\t<exclusion>\n\t\t\t\t\t<groupId>junit</groupId>\n\t\t\t\t\t<artifactId>junit</artifactId>\n\t\t\t\t</exclusion>\n\t\t\t</exclusions>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.mockito</groupId>\n\t\t\t<artifactId>mockito-core</artifactId>\n\t\t\t<scope>test</scope>\n\t\t</dependency>\n\t</dependencies>\n\n\t<profiles>\n\t\t<profile>\n\t\t\t<id>spring</id>\n\t\t\t<repositories>\n\t\t\t\t<repository>\n\t\t\t\t\t<id>spring-snapshots</id>\n\t\t\t\t\t<name>Spring Snapshots</name>\n\t\t\t\t\t<url>https://repo.spring.io/snapshot</url>\n\t\t\t\t\t<snapshots>\n\t\t\t\t\t\t<enabled>true</enabled>\n\t\t\t\t\t</snapshots>\n\t\t\t\t</repository>\n\t\t\t\t<repository>\n\t\t\t\t\t<id>spring-milestones</id>\n\t\t\t\t\t<name>Spring Milestones</name>\n\t\t\t\t\t<url>https://repo.spring.io/milestone</url>\n\t\t\t\t\t<snapshots>\n\t\t\t\t\t\t<enabled>false</enabled>\n\t\t\t\t\t</snapshots>\n\t\t\t\t</repository>\n\t\t\t\t<repository>\n\t\t\t\t\t<id>maven-central</id>\n\t\t\t\t\t<name>Maven Central</name>\n\t\t\t\t\t<url>https://repo.maven.apache.org/maven2</url>\n\t\t\t\t\t<snapshots>\n\t\t\t\t\t\t<enabled>false</enabled>\n\t\t\t\t\t</snapshots>\n\t\t\t\t</repository>\n\t\t\t</repositories>\n\t\t\t<pluginRepositories>\n\t\t\t\t<pluginRepository>\n\t\t\t\t\t<id>spring-snapshots</id>\n\t\t\t\t\t<name>Spring Snapshots</name>\n\t\t\t\t\t<url>https://repo.spring.io/snapshot</url>\n\t\t\t\t\t<snapshots>\n\t\t\t\t\t\t<enabled>true</enabled>\n\t\t\t\t\t</snapshots>\n\t\t\t\t</pluginRepository>\n\t\t\t\t<pluginRepository>\n\t\t\t\t\t<id>spring-milestones</id>\n\t\t\t\t\t<name>Spring Milestones</name>\n\t\t\t\t\t<url>https://repo.spring.io/milestone</url>\n\t\t\t\t\t<snapshots>\n\t\t\t\t\t\t<enabled>false</enabled>\n\t\t\t\t\t</snapshots>\n\t\t\t\t</pluginRepository>\n\t\t\t\t<pluginRepository>\n\t\t\t\t\t<id>maven-central</id>\n\t\t\t\t\t<name>Maven Central</name>\n\t\t\t\t\t<url>https://repo.maven.apache.org/maven2</url>\n\t\t\t\t\t<snapshots>\n\t\t\t\t\t\t<enabled>false</enabled>\n\t\t\t\t\t</snapshots>\n\t\t\t\t</pluginRepository>\n\t\t\t</pluginRepositories>\n\t\t</profile>\n\t\t<profile>\n\t\t\t<id>failsafe</id>\n\t\t\t<build>\n\t\t\t\t<plugins>\n\t\t\t\t\t<plugin>\n\t\t\t\t\t\t<groupId>org.apache.maven.plugins</groupId>\n\t\t\t\t\t\t<artifactId>maven-failsafe-plugin</artifactId>\n\t\t\t\t\t\t<version>3.5.0</version>\n\t\t\t\t\t\t<configuration>\n\t\t\t\t\t\t\t<includes>\n\t\t\t\t\t\t\t\t<include>**/*IT.*</include>\n\t\t\t\t\t\t\t</includes>\n\t\t\t\t\t\t</configuration>\n\t\t\t\t\t\t<executions>\n\t\t\t\t\t\t\t<execution>\n\t\t\t\t\t\t\t\t<goals>\n\t\t\t\t\t\t\t\t\t<goal>verify</goal>\n\t\t\t\t\t\t\t\t\t<goal>integration-test</goal>\n\t\t\t\t\t\t\t\t</goals>\n\t\t\t\t\t\t\t</execution>\n\t\t\t\t\t\t</executions>\n\t\t\t\t\t</plugin>\n\t\t\t\t</plugins>\n\t\t\t</build>\n\t\t</profile>\n\t</profiles>\n\n\t<build>\n\t\t<plugins>\n\t\t\t<plugin>\n\t\t\t\t<groupId>org.apache.maven.plugins</groupId>\n\t\t\t\t<artifactId>maven-checkstyle-plugin</artifactId>\n\t\t\t</plugin>\n\t\t</plugins>\n\t</build>\n</project>\n"
  },
  {
    "path": "spring-cloud-deployer-kubernetes/src/main/java/org/springframework/cloud/deployer/spi/kubernetes/AbstractKubernetesDeployer.java",
    "content": "/*\n * Copyright 2015-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.kubernetes;\n\n\nimport java.util.Collection;\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Locale;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.stream.Collectors;\n\nimport io.fabric8.kubernetes.api.model.Affinity;\nimport io.fabric8.kubernetes.api.model.Container;\nimport io.fabric8.kubernetes.api.model.ContainerStatus;\nimport io.fabric8.kubernetes.api.model.Lifecycle;\nimport io.fabric8.kubernetes.api.model.LifecycleHandlerBuilder;\nimport io.fabric8.kubernetes.api.model.Pod;\nimport io.fabric8.kubernetes.api.model.PodList;\nimport io.fabric8.kubernetes.api.model.PodSecurityContext;\nimport io.fabric8.kubernetes.api.model.PodSpec;\nimport io.fabric8.kubernetes.api.model.PodSpecBuilder;\nimport io.fabric8.kubernetes.api.model.ResourceRequirements;\nimport io.fabric8.kubernetes.api.model.Secret;\nimport io.fabric8.kubernetes.api.model.SecurityContext;\nimport io.fabric8.kubernetes.api.model.Service;\nimport io.fabric8.kubernetes.api.model.ServiceList;\nimport io.fabric8.kubernetes.client.KubernetesClient;\nimport org.apache.commons.logging.Log;\nimport org.apache.commons.logging.LogFactory;\n\nimport org.springframework.cloud.deployer.spi.app.AppDeployer;\nimport org.springframework.cloud.deployer.spi.app.AppStatus;\nimport org.springframework.cloud.deployer.spi.core.AppDeploymentRequest;\nimport org.springframework.cloud.deployer.spi.core.RuntimeEnvironmentInfo;\nimport org.springframework.cloud.deployer.spi.kubernetes.support.PropertyParserUtils;\nimport org.springframework.cloud.deployer.spi.util.RuntimeVersionUtils;\nimport org.springframework.core.io.Resource;\nimport org.springframework.util.CollectionUtils;\nimport org.springframework.util.StringUtils;\n\n/**\n * Abstract base class for a deployer that targets Kubernetes.\n *\n * @author Florian Rosenberg\n * @author Thomas Risberg\n * @author Mark Fisher\n * @author Donovan Muller\n * @author David Turanski\n * @author Chris Schaefer\n * @author Enrique Medina Montenegro\n * @author Ilayaperumal Gopinathan\n * @author Chris Bono\n * @author Corneil du Plessis\n */\npublic class AbstractKubernetesDeployer {\n\n\tprotected static final String SPRING_DEPLOYMENT_KEY = \"spring-deployment-id\";\n\tprotected static final String SPRING_GROUP_KEY = \"spring-group-id\";\n\tprotected static final String SPRING_APP_KEY = \"spring-app-id\";\n\tprotected static final String SPRING_MARKER_KEY = \"role\";\n\tprotected static final String SPRING_MARKER_VALUE = \"spring-app\";\n\tprotected static final String APP_NAME_PROPERTY_KEY = AppDeployer.PREFIX + \"appName\";\n\tprotected static final String APP_NAME_KEY = \"spring-application-name\";\n\n\tprivate static final String SERVER_PORT_KEY = \"server.port\";\n\n\tprotected final Log logger = LogFactory.getLog(getClass().getName());\n\n\tprotected ContainerFactory containerFactory;\n\n\tprotected KubernetesClient client;\n\n\tprotected KubernetesDeployerProperties properties;\n\n\tprotected DeploymentPropertiesResolver deploymentPropertiesResolver;\n\n\t/**\n\t * Create the RuntimeEnvironmentInfo.\n\t *\n\t * @param spiClass the SPI interface class\n\t * @param implementationClass the SPI implementation class\n\t * @return the Kubernetes runtime environment info\n\t */\n\tprotected RuntimeEnvironmentInfo createRuntimeEnvironmentInfo(Class spiClass, Class implementationClass) {\n\t\treturn new RuntimeEnvironmentInfo.Builder()\n\t\t\t\t.spiClass(spiClass)\n\t\t\t\t.implementationName(implementationClass.getSimpleName())\n\t\t\t\t.implementationVersion(RuntimeVersionUtils.getVersion(implementationClass))\n\t\t\t\t.platformType(\"Kubernetes\")\n\t\t\t\t.platformApiVersion(client.getApiVersion())\n\t\t\t\t.platformClientVersion(RuntimeVersionUtils.getVersion(client.getClass()))\n\t\t\t\t.platformHostVersion(\"unknown\")\n\t\t\t\t.addPlatformSpecificInfo(\"master-url\", String.valueOf(client.getMasterUrl()))\n\t\t\t\t.addPlatformSpecificInfo(\"namespace\", client.getNamespace())\n\t\t\t\t.build();\n\t}\n\n\t/**\n\t * Creates a map of labels for a given application ID.\n\t *\n\t * @param appId the application id\n\t * @param request The {@link AppDeploymentRequest}\n\t * @return the built id map of labels\n\t */\n\tMap<String, String> createIdMap(String appId, AppDeploymentRequest request) {\n\t\tMap<String, String> map = new HashMap<>();\n\t\tmap.put(SPRING_APP_KEY, appId);\n\t\tString groupId = request.getDeploymentProperties().get(AppDeployer.GROUP_PROPERTY_KEY);\n\t\tif (groupId != null) {\n\t\t\tmap.put(SPRING_GROUP_KEY, groupId);\n\t\t}\n\t\tmap.put(SPRING_DEPLOYMENT_KEY, appId);\n\n\t\t// un-versioned app name provided by skipper\n\t\tString appName = request.getDeploymentProperties().get(APP_NAME_PROPERTY_KEY);\n\n\t\tif (StringUtils.hasText(appName)) {\n\t\t\tmap.put(APP_NAME_KEY, appName);\n\t\t}\n\n\t\treturn map;\n\t}\n\n\tprotected AppStatus buildAppStatus(String id, PodList podList, ServiceList services) {\n\t\tAppStatus.Builder statusBuilder = AppStatus.of(id);\n\t\tService service = null;\n\t\tif (podList != null && podList.getItems() != null) {\n\t\t\tfor (Pod pod : podList.getItems()) {\n\t\t\t\tString deploymentKey = pod.getMetadata().getLabels().get(SPRING_DEPLOYMENT_KEY);\n\t\t\t\tfor (Service svc : services.getItems()) {\n\t\t\t\t\t// handle case of when the version provided by skipper has been removed\n\t\t\t\t\tif(deploymentKey.startsWith(svc.getMetadata().getName())) {\n\t\t\t\t\t\tservice = svc;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t//find the container with the correct env var\n\t\t\t\tfor(Container container : pod.getSpec().getContainers()) {\n\t\t\t\t\tif(container.getEnv().stream().anyMatch(envVar -> \"SPRING_CLOUD_APPLICATION_GUID\".equals(envVar.getName()))) {\n\t\t\t\t\t\t//find container status for this container\n\t\t\t\t\t\tOptional<ContainerStatus> containerStatusOptional =\n\t\t\t\t\t\t\tpod.getStatus().getContainerStatuses()\n\t\t\t\t\t\t\t   .stream().filter(containerStatus -> container.getName().equals(containerStatus.getName()))\n\t\t\t\t\t\t\t   .findFirst();\n\n\t\t\t\t\t\tstatusBuilder.with(new KubernetesAppInstanceStatus(pod, service, properties, containerStatusOptional.orElse(null)));\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn statusBuilder.build();\n\t}\n\n\tprotected void logPossibleDownloadResourceMessage(Resource resource) {\n\t\tif (logger.isInfoEnabled()) {\n\t\t\tlogger.info(\"Preparing to run a container from  \" + resource\n\t\t\t\t\t+ \". This may take some time if the image must be downloaded from a remote container registry.\");\n\t\t}\n\t}\n\n\t/**\n\t * Create PodSpec for the given {@link AppDeploymentRequest}\n\n\t * @param appDeploymentRequest the app deployment request to use to create the PodSpec\n\t * @return the PodSpec\n\t */\n\tPodSpec createPodSpec(AppDeploymentRequest appDeploymentRequest) {\n\n\t\tString appId = createDeploymentId(appDeploymentRequest);\n\n\t\tMap<String, String>  deploymentProperties = appDeploymentRequest.getDeploymentProperties();\n\n\t\tPodSpecBuilder podSpec = new PodSpecBuilder();\n\n\t\tString imagePullSecret = this.deploymentPropertiesResolver.getImagePullSecret(deploymentProperties);\n\n\t\tif (imagePullSecret != null) {\n\t\t\tpodSpec.addNewImagePullSecret(imagePullSecret);\n\t\t}\n\n\t\tList<String> imagePullSecrets = this.deploymentPropertiesResolver.getImagePullSecrets(deploymentProperties);\n\n\t\tif (imagePullSecrets != null) {\n\t\t\timagePullSecrets.forEach(imgPullsecret -> podSpec.addNewImagePullSecret(imgPullsecret));\n\t\t}\n\n\t\tboolean hostNetwork = this.deploymentPropertiesResolver.getHostNetwork(deploymentProperties);\n\n\t\tContainerConfiguration containerConfiguration = new ContainerConfiguration(appId, appDeploymentRequest)\n\t\t\t\t.withProbeCredentialsSecret(getProbeCredentialsSecret(deploymentProperties))\n\t\t\t\t.withHostNetwork(hostNetwork);\n\n\t\tif (KubernetesAppDeployer.class.isAssignableFrom(this.getClass())) {\n\t\t\tcontainerConfiguration.withExternalPort(getExternalPort(appDeploymentRequest));\n\t\t}\n\n\t\tContainer container = containerFactory.create(containerConfiguration);\n\n\t\t// add memory and cpu resource limits\n\t\tResourceRequirements req = new ResourceRequirements();\n\t\treq.setLimits(this.deploymentPropertiesResolver.deduceResourceLimits(deploymentProperties));\n\t\treq.setRequests(this.deploymentPropertiesResolver.deduceResourceRequests(deploymentProperties));\n\t\tcontainer.setResources(req);\n\t\tImagePullPolicy pullPolicy = this.deploymentPropertiesResolver.deduceImagePullPolicy(deploymentProperties);\n\t\tcontainer.setImagePullPolicy(pullPolicy.name());\n\n\t\tKubernetesDeployerProperties.Lifecycle lifecycle =\n\t\t\t\tthis.deploymentPropertiesResolver.getLifeCycle(deploymentProperties);\n\n\t\tLifecycle f8Lifecycle = new Lifecycle();\n\t\tif (lifecycle.getPostStart() != null) {\n\t\t\tf8Lifecycle.setPostStart(new LifecycleHandlerBuilder()\n\t\t\t\t\t.withNewExec()\n\t\t\t\t\t.addAllToCommand(lifecycle.getPostStart().getExec().getCommand()).and().build());\n\t\t}\n\t\tif (lifecycle.getPreStop() != null) {\n\t\t\tf8Lifecycle.setPreStop(new LifecycleHandlerBuilder()\n\t\t\t\t\t.withNewExec()\n\t\t\t\t\t.addAllToCommand(lifecycle.getPreStop().getExec().getCommand()).and().build());\n\t\t}\n\n\t\tif (f8Lifecycle.getPostStart() != null || f8Lifecycle.getPreStop() != null) {\n\t\t\tcontainer.setLifecycle(f8Lifecycle);\n\t\t}\n\n\t\tLong termGracePeriod = this.deploymentPropertiesResolver.determineTerminationGracePeriodSeconds(deploymentProperties);\n\t\tif (termGracePeriod != null) {\n\t\t\tpodSpec.withTerminationGracePeriodSeconds(termGracePeriod);\n\t\t}\n\n\t\tMap<String, String> nodeSelectors = this.deploymentPropertiesResolver.getNodeSelectors(deploymentProperties);\n\t\tif (!nodeSelectors.isEmpty()) {\n\t\t\tpodSpec.withNodeSelector(nodeSelectors);\n\t\t}\n\n\t\tpodSpec.withTolerations(this.deploymentPropertiesResolver.getTolerations(deploymentProperties));\n\n\n\t\tif (hostNetwork) {\n\t\t\tpodSpec.withHostNetwork(true);\n\t\t}\n\n\t\tSecurityContext containerSecurityContext = this.deploymentPropertiesResolver.getContainerSecurityContext(deploymentProperties);\n\t\tif (containerSecurityContext != null) {\n\t\t\tcontainer.setSecurityContext(containerSecurityContext);\n\t\t}\n\n\t\tpodSpec.addToContainers(container);\n\n\t\tpodSpec.withRestartPolicy(this.deploymentPropertiesResolver.getRestartPolicy(deploymentProperties).name());\n\n\t\tString deploymentServiceAccountName = this.deploymentPropertiesResolver.getDeploymentServiceAccountName(deploymentProperties);\n\n\t\tif (deploymentServiceAccountName != null) {\n\t\t\tpodSpec.withServiceAccountName(deploymentServiceAccountName);\n\t\t}\n\n\t\tPodSecurityContext podSecurityContext = this.deploymentPropertiesResolver.getPodSecurityContext(deploymentProperties);\n\t\tif (podSecurityContext != null) {\n\t\t\tpodSpec.withSecurityContext(podSecurityContext);\n\t\t}\n\n\t\tAffinity affinity = this.deploymentPropertiesResolver.getAffinityRules(deploymentProperties);\n\t\t// Make sure there is at least some rule.\n\t\tif (affinity.getNodeAffinity() != null\n\t\t\t\t|| affinity.getPodAffinity() != null\n\t\t\t\t|| affinity.getPodAntiAffinity() != null) {\n\t\t\tpodSpec.withAffinity(affinity);\n\t\t}\n\n\t\tCollection<Container> initContainers = this.deploymentPropertiesResolver.getInitContainers(deploymentProperties);\n\t\tif (initContainers != null && !initContainers.isEmpty()) {\n\t\t\tfor (Container initContainer : initContainers) {\n\t\t\t\tif (initContainer.getSecurityContext() == null && containerSecurityContext != null) {\n\t\t\t\t\tinitContainer.setSecurityContext(containerSecurityContext);\n\t\t\t\t}\n\t\t\t\tpodSpec.addToInitContainers(initContainer);\n\t\t\t}\n\t\t}\n\n\t\tBoolean shareProcessNamespace = this.deploymentPropertiesResolver.getShareProcessNamespace(deploymentProperties);\n\t\tif (shareProcessNamespace != null) {\n\t\t\tpodSpec.withShareProcessNamespace(shareProcessNamespace);\n\t\t}\n\n\t\tString priorityClassName = this.deploymentPropertiesResolver.getPriorityClassName(deploymentProperties);\n\t\tif (StringUtils.hasText(priorityClassName)) {\n\t\t\tpodSpec.withPriorityClassName(priorityClassName);\n\t\t}\n\n\t\tList<Container> additionalContainers = this.deploymentPropertiesResolver.getAdditionalContainers(deploymentProperties);\n\t\tif (containerSecurityContext != null && !CollectionUtils.isEmpty(additionalContainers)) {\n\t\t\tadditionalContainers.stream().filter((c) -> c.getSecurityContext() == null)\n\t\t\t\t\t.forEach((c) -> c.setSecurityContext(containerSecurityContext));\n\t\t}\n\t\tpodSpec.addAllToContainers(additionalContainers);\n\n\t\tList<Container> allContainers = new ArrayList<>();\n\t\tallContainers.add(container);\n\n\t\tallContainers.addAll(additionalContainers);\n\t\t// only add volumes with corresponding volume mounts in any container.\n\t\tpodSpec.withVolumes(this.deploymentPropertiesResolver.getVolumes(deploymentProperties).stream()\n\t\t\t\t.filter(volume -> allContainers.stream()\n\t\t\t\t\t\t.anyMatch(c -> c.getVolumeMounts()\n\t\t\t\t\t\t\t\t.stream()\n\t\t\t\t\t\t\t\t.anyMatch(volumeMount -> volumeMount.getName().equals(volume.getName()))\n\t\t\t\t\t\t)\n\t\t\t\t)\n\t\t\t\t.collect(Collectors.toList()));\n\t\treturn podSpec.build();\n\t}\n\n\tint getExternalPort(final AppDeploymentRequest request) {\n\t\tint externalPort = 8080;\n\t\tMap<String, String> parameters = request.getDefinition().getProperties();\n\t\tif (parameters.containsKey(SERVER_PORT_KEY)) {\n\t\t\texternalPort = Integer.valueOf(parameters.get(SERVER_PORT_KEY));\n\t\t}\n\n\t\treturn externalPort;\n\t}\n\n\tString createDeploymentId(AppDeploymentRequest request) {\n\t\tString groupId = request.getDeploymentProperties().get(AppDeployer.GROUP_PROPERTY_KEY);\n\t\tString deploymentId;\n\t\tif (groupId == null) {\n\t\t\tdeploymentId = String.format(\"%s\", request.getDefinition().getName());\n\t\t}\n\t\telse {\n\t\t\tdeploymentId = String.format(\"%s-%s\", groupId, request.getDefinition().getName());\n\t\t}\n\t\t// Kubernetes does not allow . in the name and does not allow uppercase in the name\n\t\treturn deploymentId.replace('.', '-').toLowerCase(Locale.ROOT);\n\t}\n\n\t/**\n\t * Return the Secret corresponds to the name of ProbeCredentialSecret\n\t * @param kubernetesDeployerProperties the kubernetes deployer properties\n\t * @return the Secret Object\n\t */\n\tSecret getProbeCredentialsSecret(Map<String, String> kubernetesDeployerProperties) {\n\t\tString secretName = PropertyParserUtils.getDeploymentPropertyValue(kubernetesDeployerProperties,\n\t\t\t\tthis.deploymentPropertiesResolver.getPropertyPrefix() + \".probeCredentialsSecret\");\n\n\t\tif (StringUtils.hasText(secretName)) {\n\t\t\treturn this.client.secrets().withName(secretName).get();\n\t\t}\n\n\t\treturn null;\n\t}\n\n}\n"
  },
  {
    "path": "spring-cloud-deployer-kubernetes/src/main/java/org/springframework/cloud/deployer/spi/kubernetes/CommandProbeCreator.java",
    "content": "/*\n * Copyright 2020 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.kubernetes;\n\nimport io.fabric8.kubernetes.api.model.ExecActionBuilder;\nimport io.fabric8.kubernetes.api.model.Probe;\nimport io.fabric8.kubernetes.api.model.ProbeBuilder;\n\n/**\n * Base class for command based probe creators\n *\n * @author Chris Schaefer\n * @author Corneil du Plessis\n * @since 2.5\n */\nabstract class CommandProbeCreator extends ProbeCreator {\n    CommandProbeCreator(KubernetesDeployerProperties kubernetesDeployerProperties,\n                        ContainerConfiguration containerConfiguration) {\n        super(kubernetesDeployerProperties, containerConfiguration);\n    }\n\n    abstract String[] getCommand();\n\n    protected Probe create() {\n        ExecActionBuilder execActionBuilder = new ExecActionBuilder()\n                .withCommand(getCommand());\n\n        return new ProbeBuilder()\n                .withExec(execActionBuilder.build())\n                .withInitialDelaySeconds(getInitialDelay())\n                .withPeriodSeconds(getPeriod())\n                .withSuccessThreshold(getSuccess())\n                .withFailureThreshold(getFailure())\n                .build();\n    }\n}\n"
  },
  {
    "path": "spring-cloud-deployer-kubernetes/src/main/java/org/springframework/cloud/deployer/spi/kubernetes/CompositeDeploymentStateResolver.java",
    "content": "/*\n * Copyright 2017 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.kubernetes;\n\nimport io.fabric8.kubernetes.api.model.ContainerStatus;\n\nimport org.springframework.cloud.deployer.spi.app.DeploymentState;\n\n/**\n * @author David Turanski\n **/\nclass CompositeDeploymentStateResolver implements RunningPhaseDeploymentStateResolver {\n\tprivate final RunningPhaseDeploymentStateResolver[] delegates;\n\n\tCompositeDeploymentStateResolver(RunningPhaseDeploymentStateResolver... delegates) {\n\t\tthis.delegates = delegates;\n\t}\n\n\t@Override\n\tpublic DeploymentState resolve(ContainerStatus containerStatus) {\n\t\tfor (RunningPhaseDeploymentStateResolver resolver: delegates) {\n\t\t\tDeploymentState deploymentState = resolver.resolve(containerStatus);\n\t\t\tif (deploymentState != null) {\n\t\t\t\treturn deploymentState;\n\t\t\t}\n\t\t}\n\t\treturn null;\n\t}\n}\n"
  },
  {
    "path": "spring-cloud-deployer-kubernetes/src/main/java/org/springframework/cloud/deployer/spi/kubernetes/ContainerConfiguration.java",
    "content": "/*\n * Copyright 2018 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.kubernetes;\n\nimport io.fabric8.kubernetes.api.model.Secret;\n\nimport org.springframework.cloud.deployer.spi.core.AppDeploymentRequest;\n\n/**\n * Encapsulates parameters used to configure a container.\n *\n * @author Chris Schaefer\n */\npublic class ContainerConfiguration {\n\tprivate String appId;\n\tprivate Integer externalPort;\n\tprivate boolean isHostNetwork;\n\tprivate Secret probeCredentialsSecret;\n\tprivate AppDeploymentRequest appDeploymentRequest;\n\n\tpublic ContainerConfiguration(String appId, AppDeploymentRequest appDeploymentRequest) {\n\t\tthis.appId = appId;\n\t\tthis.appDeploymentRequest = appDeploymentRequest;\n\t}\n\n\tpublic AppDeploymentRequest getAppDeploymentRequest() {\n\t\treturn appDeploymentRequest;\n\t}\n\n\tpublic String getAppId() {\n\t\treturn appId;\n\t}\n\n\tpublic boolean isHostNetwork() {\n\t\treturn isHostNetwork;\n\t}\n\n\tpublic ContainerConfiguration withHostNetwork(boolean isHostNetwork) {\n\t\tthis.isHostNetwork = isHostNetwork;\n\t\treturn this;\n\t}\n\n\tpublic ContainerConfiguration withExternalPort(Integer externalPort) {\n\t\tthis.externalPort = externalPort;\n\t\treturn this;\n\t}\n\n\tpublic Integer getExternalPort() {\n\t\treturn externalPort;\n\t}\n\n\tpublic ContainerConfiguration withProbeCredentialsSecret(Secret probeCredentialsSecret) {\n\t\tthis.probeCredentialsSecret = probeCredentialsSecret;\n\t\treturn this;\n\t}\n\n\tpublic Secret getProbeCredentialsSecret() {\n\t\treturn probeCredentialsSecret;\n\t}\n}\n"
  },
  {
    "path": "spring-cloud-deployer-kubernetes/src/main/java/org/springframework/cloud/deployer/spi/kubernetes/ContainerFactory.java",
    "content": "/*\n * Copyright 2015-2019 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.kubernetes;\n\nimport io.fabric8.kubernetes.api.model.Container;\n\n/**\n * Defines how a Kubernetes {@link Container} is created.\n *\n * @author Florian Rosenberg\n * @author Thomas Risberg\n * @author David Turanski\n * @author Chris Schaefer\n */\npublic interface ContainerFactory {\n\t/**\n\t * Creates a {@link Container} using configuration from the provided {@link ContainerConfiguration}.\n\t *\n\t * @param containerConfiguration the {@link ContainerConfiguration}\n\t * @return a {@link Container}\n\t */\n\tContainer create(ContainerConfiguration containerConfiguration);\n}\n"
  },
  {
    "path": "spring-cloud-deployer-kubernetes/src/main/java/org/springframework/cloud/deployer/spi/kubernetes/DefaultContainerFactory.java",
    "content": "/*\n * Copyright 2015-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.kubernetes;\n\nimport java.io.IOException;\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.LinkedList;\nimport java.util.List;\nimport java.util.Locale;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.stream.Collectors;\n\nimport com.fasterxml.jackson.core.JsonProcessingException;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport io.fabric8.kubernetes.api.model.Container;\nimport io.fabric8.kubernetes.api.model.ContainerBuilder;\nimport io.fabric8.kubernetes.api.model.EnvFromSource;\nimport io.fabric8.kubernetes.api.model.EnvVar;\nimport io.fabric8.kubernetes.api.model.EnvVarSource;\nimport io.fabric8.kubernetes.api.model.ObjectFieldSelector;\nimport io.fabric8.kubernetes.api.model.Probe;\nimport org.apache.commons.logging.Log;\nimport org.apache.commons.logging.LogFactory;\n\nimport org.springframework.cloud.deployer.spi.app.AppDeployer;\nimport org.springframework.cloud.deployer.spi.core.AppDeploymentRequest;\nimport org.springframework.cloud.deployer.spi.scheduler.ScheduleRequest;\nimport org.springframework.util.Assert;\nimport org.springframework.util.StringUtils;\n\n/**\n * Create a Kubernetes {@link Container} that will be started as part of a\n * Kubernetes Pod by launching the specified Docker image.\n *\n * @author Florian Rosenberg\n * @author Thomas Risberg\n * @author Donovan Muller\n * @author David Turanski\n * @author Chris Schaefer\n * @author Ilayaperumal Gopinathan\n * @author Glenn Renfro\n * @author Corneil du Plessis\n */\npublic class DefaultContainerFactory implements ContainerFactory {\n    private static Log logger = LogFactory.getLog(DefaultContainerFactory.class);\n    private static final String SPRING_APPLICATION_JSON = \"SPRING_APPLICATION_JSON\";\n    private static final String SPRING_CLOUD_APPLICATION_GUID = \"SPRING_CLOUD_APPLICATION_GUID\";\n\n    private final KubernetesDeployerProperties properties;\n\n    public DefaultContainerFactory(KubernetesDeployerProperties properties) {\n        this.properties = properties;\n    }\n\n    @Override\n    public Container create(ContainerConfiguration containerConfiguration) {\n        AppDeploymentRequest request = containerConfiguration.getAppDeploymentRequest();\n        Map<String, String> deploymentProperties = getDeploymentProperties(request);\n        DeploymentPropertiesResolver deploymentPropertiesResolver = getDeploymentPropertiesResolver(request);\n\n        String image;\n        try {\n            image = request.getResource().getURI().getSchemeSpecificPart();\n        } catch (IOException e) {\n            throw new IllegalArgumentException(\"Unable to get URI for \" + request.getResource(), e);\n        }\n        logger.info(\"Using Docker image: \" + image);\n\n        EntryPointStyle entryPointStyle = deploymentPropertiesResolver.determineEntryPointStyle(deploymentProperties);\n        logger.info(\"Using Docker entry point style: \" + entryPointStyle);\n\n        Map<String, String> envVarsMap = new HashMap<>();\n        for (String envVar : this.properties.getEnvironmentVariables()) {\n            String[] strings = envVar.split(\"=\", 2);\n            Assert.isTrue(strings.length == 2, \"Invalid environment variable declared: \" + envVar);\n            envVarsMap.put(strings[0], strings[1]);\n        }\n        //Create EnvVar entries for additional variables set at the app level\n        //For instance, this may be used to set JAVA_OPTS independently for each app if the base container\n        //image supports it.\n        envVarsMap.putAll(deploymentPropertiesResolver.getAppEnvironmentVariables(deploymentProperties));\n\n        List<String> appArgs = new ArrayList<>();\n\n        Map<String, String> appAdminCredentials = new HashMap<>();\n        properties.getAppAdmin().addCredentialsToAppEnvironmentAsProperties(appAdminCredentials);\n\n        switch (entryPointStyle) {\n            case exec:\n                appArgs = createCommandArgs(request);\n                List<String> finalAppArgs = appArgs;\n                appAdminCredentials.forEach((k, v) -> finalAppArgs.add(String.format(\"--%s=%s\", k, v)));\n\n\n                break;\n            case boot:\n                if (envVarsMap.containsKey(SPRING_APPLICATION_JSON)) {\n                    throw new IllegalStateException(\n                            \"You can't use boot entry point style and also set SPRING_APPLICATION_JSON for the app\");\n                }\n                try {\n                    envVarsMap.put(SPRING_APPLICATION_JSON,\n                            new ObjectMapper().writeValueAsString(request.getDefinition().getProperties()));\n                } catch (JsonProcessingException e) {\n                    throw new IllegalStateException(\"Unable to create SPRING_APPLICATION_JSON\", e);\n                }\n\n                appArgs = request.getCommandlineArguments();\n\n                break;\n            case shell:\n                for (String key : request.getDefinition().getProperties().keySet()) {\n                    String envVar = key.replace('.', '_').toUpperCase(Locale.ROOT);\n                    envVarsMap.put(envVar, request.getDefinition().getProperties().get(key));\n                    envVarsMap.putAll(appAdminCredentials);\n\n                }\n                // Push all the command line arguments as environment properties\n                // The task app name(in case of Composed Task), platform_name and executionId are expected to be updated.\n                // This will also override any of the existing app properties that match the provided cmdline args.\n                for (String cmdLineArg : request.getCommandlineArguments()) {\n                    String cmdLineArgKey;\n\n                    if (cmdLineArg.startsWith(\"--\")) {\n                        cmdLineArgKey = cmdLineArg.substring(2, cmdLineArg.indexOf(\"=\"));\n                    } else {\n                        cmdLineArgKey = cmdLineArg.substring(0, cmdLineArg.indexOf(\"=\"));\n                    }\n\n                    String cmdLineArgValue = cmdLineArg.substring(cmdLineArg.indexOf(\"=\") + 1);\n                    envVarsMap.put(cmdLineArgKey.replace('.', '_').toUpperCase(Locale.ROOT), cmdLineArgValue);\n                }\n                break;\n        }\n\n        List<EnvVar> envVars = new ArrayList<>();\n        for (Map.Entry<String, String> e : envVarsMap.entrySet()) {\n            envVars.add(new EnvVar(e.getKey(), e.getValue(), null));\n        }\n\n        envVars.addAll(deploymentPropertiesResolver.getSecretKeyRefs(deploymentProperties));\n        envVars.addAll(deploymentPropertiesResolver.getConfigMapKeyRefs(deploymentProperties));\n        envVars.add(getGUIDEnvVar());\n\n        if (request.getDeploymentProperties().get(AppDeployer.GROUP_PROPERTY_KEY) != null) {\n            envVars.add(new EnvVar(\"SPRING_CLOUD_APPLICATION_GROUP\",\n                    request.getDeploymentProperties().get(AppDeployer.GROUP_PROPERTY_KEY), null));\n        }\n\n        List<EnvFromSource> envFromSources = new ArrayList<>();\n        envFromSources.addAll(deploymentPropertiesResolver.getConfigMapRefs(deploymentProperties));\n        envFromSources.addAll(deploymentPropertiesResolver.getSecretRefs(deploymentProperties));\n\n        ContainerBuilder container = new ContainerBuilder();\n        container.withName(containerConfiguration.getAppId()).withImage(image).withEnv(envVars).withEnvFrom(envFromSources)\n\t\t\t.withArgs(appArgs).withImagePullPolicy(deploymentPropertiesResolver.getImagePullPolicy(deploymentProperties))\n\t\t\t.withVolumeMounts(deploymentPropertiesResolver.getVolumeMounts(deploymentProperties));\n\n        Set<Integer> ports = new HashSet<>();\n\n        Integer defaultPort = containerConfiguration.getExternalPort();\n\n        if (defaultPort != null) {\n            ports.add(defaultPort);\n        }\n\n        ports.addAll(deploymentPropertiesResolver.getContainerPorts(deploymentProperties));\n        configureStartupProbe(containerConfiguration, container, ports);\n        configureReadinessProbe(containerConfiguration, container, ports);\n        configureLivenessProbe(containerConfiguration, container, ports);\n\n        if (!ports.isEmpty()) {\n            for (Integer containerPort : ports) {\n                if (containerConfiguration.isHostNetwork()) {\n                    container.addNewPort().withContainerPort(containerPort).withHostPort(containerPort).endPort();\n                } else {\n                    container.addNewPort().withContainerPort(containerPort).endPort();\n                }\n            }\n        }\n\n        //Override the containers default entry point with one specified during the app deployment\n        List<String> containerCommand = deploymentPropertiesResolver.getContainerCommand(deploymentProperties);\n        if (!containerCommand.isEmpty()) {\n            container.withCommand(containerCommand);\n        }\n        return container.build();\n    }\n\n    private EnvVar getGUIDEnvVar() {\n        ObjectFieldSelector objectFieldSelector = new ObjectFieldSelector();\n        objectFieldSelector.setFieldPath(\"metadata.uid\");\n\n        EnvVarSource envVarSource = new EnvVarSource();\n        envVarSource.setFieldRef(objectFieldSelector);\n\n        EnvVar guidEnvVar = new EnvVar();\n        guidEnvVar.setValueFrom(envVarSource);\n        guidEnvVar.setName(SPRING_CLOUD_APPLICATION_GUID);\n\n        return guidEnvVar;\n    }\n\n    private void configureReadinessProbe(ContainerConfiguration containerConfiguration,\n                                         ContainerBuilder containerBuilder, Set<Integer> ports) {\n        Probe readinessProbe = ProbeCreatorFactory.createReadinessProbe(containerConfiguration, properties,\n                getProbeType(containerConfiguration));\n\n        Integer probePort = null;\n\n        if (readinessProbe.getHttpGet() != null) {\n            probePort = readinessProbe.getHttpGet().getPort().getIntVal();\n        }\n\n        if (readinessProbe.getTcpSocket() != null) {\n            probePort = readinessProbe.getTcpSocket().getPort().getIntVal();\n        }\n\n        if (probePort != null || (containerConfiguration.getExternalPort() != null && readinessProbe.getExec() != null)) {\n            containerBuilder.withReadinessProbe(readinessProbe);\n        }\n\n        if (probePort != null) {\n            ports.add(probePort);\n        }\n    }\n\n    private void configureStartupProbe(ContainerConfiguration containerConfiguration,\n                                       ContainerBuilder containerBuilder, Set<Integer> ports) {\n        Probe startupProbe = ProbeCreatorFactory.createStartupProbe(containerConfiguration, properties,\n                getProbeType(containerConfiguration));\n\n        Integer probePort = null;\n\n        if (startupProbe.getHttpGet() != null) {\n            probePort = startupProbe.getHttpGet().getPort().getIntVal();\n        }\n\n        if (startupProbe.getTcpSocket() != null) {\n            probePort = startupProbe.getTcpSocket().getPort().getIntVal();\n        }\n\n        if (probePort != null || (containerConfiguration.getExternalPort() != null && startupProbe.getExec() != null)) {\n            containerBuilder.withStartupProbe(startupProbe);\n        }\n\n        if (probePort != null) {\n            ports.add(probePort);\n        }\n    }\n\n    private void configureLivenessProbe(ContainerConfiguration containerConfiguration,\n                                        ContainerBuilder containerBuilder, Set<Integer> ports) {\n        Probe livenessProbe = ProbeCreatorFactory.createLivenessProbe(containerConfiguration, properties,\n                getProbeType(containerConfiguration));\n\n        Integer probePort = null;\n\n        if (livenessProbe.getHttpGet() != null) {\n            probePort = livenessProbe.getHttpGet().getPort().getIntVal();\n        }\n\n        if (livenessProbe.getTcpSocket() != null) {\n            probePort = livenessProbe.getTcpSocket().getPort().getIntVal();\n        }\n\n        if (probePort != null || (containerConfiguration.getExternalPort() != null && livenessProbe.getExec() != null)) {\n            containerBuilder.withLivenessProbe(livenessProbe);\n        }\n\n        if (probePort != null) {\n            ports.add(probePort);\n        }\n    }\n\n    private ProbeType getProbeType(ContainerConfiguration containerConfiguration) {\n        AppDeploymentRequest appDeploymentRequest = containerConfiguration.getAppDeploymentRequest();\n        Map<String, String> deploymentProperties = getDeploymentProperties(appDeploymentRequest);\n        DeploymentPropertiesResolver deploymentPropertiesResolver = getDeploymentPropertiesResolver(appDeploymentRequest);\n\n        return deploymentPropertiesResolver.determineProbeType(deploymentProperties);\n    }\n\n    /**\n     * Create command arguments\n     *\n     * @param request the {@link AppDeploymentRequest}\n     * @return the command line arguments to use\n     */\n    List<String> createCommandArgs(AppDeploymentRequest request) {\n        List<String> cmdArgs = new LinkedList<>();\n\n        List<String> commandArgOptions = request.getCommandlineArguments().stream()\n                .map(this::getArgOption)\n                .collect(Collectors.toList());\n\n        // add properties from deployment request\n        Map<String, String> args = request.getDefinition().getProperties();\n        for (Map.Entry<String, String> entry : args.entrySet()) {\n            if (!StringUtils.hasText(entry.getValue())) {\n                logger.warn(\n                        \"Excluding request property with missing value from command args: \" + entry.getKey());\n            } else if (commandArgOptions.contains(entry.getKey())) {\n                logger.warn(\n                        String.format(\n                                \"Excluding request property [--%s=%s] as a command arg. Existing command line argument takes precedence.\"\n                                , entry.getKey(), entry.getValue()));\n            } else {\n                cmdArgs.add(String.format(\"--%s=%s\", entry.getKey(), entry.getValue()));\n            }\n        }\n        // add provided command line args\n        cmdArgs.addAll(request.getCommandlineArguments());\n        logger.debug(\"Using command args: \" + cmdArgs);\n        return cmdArgs;\n    }\n\n    private String getArgOption(String arg) {\n        int indexOfAssignment = arg.indexOf(\"=\");\n        String argOption = (indexOfAssignment < 0) ? arg : arg.substring(0, indexOfAssignment);\n        return argOption.trim().replaceAll(\"^--\", \"\");\n    }\n\n    private DeploymentPropertiesResolver getDeploymentPropertiesResolver(AppDeploymentRequest request) {\n        String propertiesPrefix = (request instanceof ScheduleRequest &&\n                ((ScheduleRequest) request).getSchedulerProperties() != null &&\n                ((ScheduleRequest) request).getSchedulerProperties().size() > 0) ? KubernetesSchedulerProperties.KUBERNETES_SCHEDULER_PROPERTIES_PREFIX :\n                KubernetesDeployerProperties.KUBERNETES_DEPLOYER_PROPERTIES_PREFIX;\n        return new DeploymentPropertiesResolver(propertiesPrefix, this.properties);\n    }\n\n    private Map<String, String> getDeploymentProperties(AppDeploymentRequest request) {\n        return request.getDeploymentProperties();\n    }\n}\n"
  },
  {
    "path": "spring-cloud-deployer-kubernetes/src/main/java/org/springframework/cloud/deployer/spi/kubernetes/DefaultRunningPhaseDeploymentStateResolver.java",
    "content": "/*\n * Copyright 2017 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.kubernetes;\n\nimport org.springframework.cloud.deployer.spi.app.DeploymentState;\n\n/**\n * @author David Turanski\n **/\npublic class DefaultRunningPhaseDeploymentStateResolver extends CompositeDeploymentStateResolver {\n\n\tpublic DefaultRunningPhaseDeploymentStateResolver(KubernetesDeployerProperties properties) {\n\t\tsuper(\n\t\t\tnew PredicateRunningPhaseDeploymentStateResolver.ContainerReady(properties),\n\t\t\tnew PredicateRunningPhaseDeploymentStateResolver.ContainerCrashed(properties),\n\t\t\tnew PredicateRunningPhaseDeploymentStateResolver.RestartsDueToTheSameError(properties),\n\t\t\tnew PredicateRunningPhaseDeploymentStateResolver.CrashLoopBackOffRestarts(properties),\n\t\t\tnew PredicateRunningPhaseDeploymentStateResolver.ContainerTerminated(properties),\n\t\t\t//default\n\t\t\tcontainerStatus -> DeploymentState.deploying);\n\t}\n}\n"
  },
  {
    "path": "spring-cloud-deployer-kubernetes/src/main/java/org/springframework/cloud/deployer/spi/kubernetes/DeploymentPropertiesResolver.java",
    "content": "/*\n * Copyright 2020-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage org.springframework.cloud.deployer.spi.kubernetes;\n\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Locale;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.Properties;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\nimport java.util.stream.Collectors;\n\nimport io.fabric8.kubernetes.api.model.Affinity;\nimport io.fabric8.kubernetes.api.model.AffinityBuilder;\nimport io.fabric8.kubernetes.api.model.CapabilitiesBuilder;\nimport io.fabric8.kubernetes.api.model.ConfigMapEnvSource;\nimport io.fabric8.kubernetes.api.model.ConfigMapKeySelector;\nimport io.fabric8.kubernetes.api.model.Container;\nimport io.fabric8.kubernetes.api.model.ContainerBuilder;\nimport io.fabric8.kubernetes.api.model.EnvFromSource;\nimport io.fabric8.kubernetes.api.model.EnvVar;\nimport io.fabric8.kubernetes.api.model.EnvVarBuilder;\nimport io.fabric8.kubernetes.api.model.EnvVarSource;\nimport io.fabric8.kubernetes.api.model.EnvVarSourceBuilder;\nimport io.fabric8.kubernetes.api.model.ObjectFieldSelector;\nimport io.fabric8.kubernetes.api.model.ObjectFieldSelectorBuilder;\nimport io.fabric8.kubernetes.api.model.PodSecurityContext;\nimport io.fabric8.kubernetes.api.model.PodSecurityContextBuilder;\nimport io.fabric8.kubernetes.api.model.Quantity;\nimport io.fabric8.kubernetes.api.model.SecretEnvSource;\nimport io.fabric8.kubernetes.api.model.SecretKeySelector;\nimport io.fabric8.kubernetes.api.model.SecurityContext;\nimport io.fabric8.kubernetes.api.model.SecurityContextBuilder;\nimport io.fabric8.kubernetes.api.model.Sysctl;\nimport io.fabric8.kubernetes.api.model.SysctlBuilder;\nimport io.fabric8.kubernetes.api.model.Toleration;\nimport io.fabric8.kubernetes.api.model.Volume;\nimport io.fabric8.kubernetes.api.model.VolumeMount;\nimport org.apache.commons.logging.Log;\nimport org.apache.commons.logging.LogFactory;\n\nimport org.springframework.beans.factory.config.YamlPropertiesFactoryBean;\nimport org.springframework.boot.context.properties.bind.Bindable;\nimport org.springframework.boot.context.properties.bind.Binder;\nimport org.springframework.boot.context.properties.source.MapConfigurationPropertySource;\nimport org.springframework.cloud.deployer.spi.core.AppDeploymentRequest;\nimport org.springframework.cloud.deployer.spi.kubernetes.KubernetesDeployerProperties.ConfigMapKeyRef;\nimport org.springframework.cloud.deployer.spi.kubernetes.KubernetesDeployerProperties.InitContainer;\nimport org.springframework.cloud.deployer.spi.kubernetes.KubernetesDeployerProperties.SecretKeyRef;\nimport org.springframework.cloud.deployer.spi.kubernetes.support.PropertyParserUtils;\nimport org.springframework.cloud.deployer.spi.kubernetes.support.RelaxedNames;\nimport org.springframework.cloud.deployer.spi.util.ByteSizeUtils;\nimport org.springframework.cloud.deployer.spi.util.CommandLineTokenizer;\nimport org.springframework.core.io.ByteArrayResource;\nimport org.springframework.lang.Nullable;\nimport org.springframework.util.Assert;\nimport org.springframework.util.CollectionUtils;\nimport org.springframework.util.StringUtils;\n\n/**\n * Class that resolves the appropriate deployment properties based on the property prefix being used.\n * Currently, both the Deployer/TaskLauncher and the Scheduler use this resolver to retrieve the\n * deployment properties for the given Kubernetes deployer properties.\n *\n * @author Chris Schaefer\n * @author Ilayaperumal Gopinathan\n * @author Chris Bono\n * @author Corneil du Plessis\n */\n\nclass DeploymentPropertiesResolver {\n\tstatic final String STATEFUL_SET_IMAGE_NAME = \"busybox\";\n\n\tprivate final Log logger = LogFactory.getLog(getClass().getName());\n\n\tprivate String propertyPrefix;\n\tprivate KubernetesDeployerProperties properties;\n\n\tDeploymentPropertiesResolver(String propertyPrefix, KubernetesDeployerProperties properties) {\n\t\tthis.propertyPrefix = propertyPrefix;\n\t\tthis.properties = properties;\n\t}\n\n\tString getPropertyPrefix() {\n\t\treturn this.propertyPrefix;\n\t}\n\n\tList<Toleration> getTolerations(Map<String, String> kubernetesDeployerProperties) {\n\t\tList<Toleration> tolerations = new ArrayList<>();\n\n\t\tKubernetesDeployerProperties deployerProperties = bindProperties(kubernetesDeployerProperties,\n\t\t\t\tthis.propertyPrefix + \".tolerations\", \"tolerations\" );\n\n\t\tdeployerProperties.getTolerations().forEach(toleration -> tolerations.add(\n\t\t\t\tnew Toleration(toleration.getEffect(), toleration.getKey(), toleration.getOperator(),\n\t\t\t\t\t\ttoleration.getTolerationSeconds(), toleration.getValue())));\n\n\t\tthis.properties.getTolerations().stream()\n\t\t\t\t.filter(toleration -> tolerations.stream()\n\t\t\t\t\t\t.noneMatch(existing -> existing.getKey().equals(toleration.getKey())))\n\t\t\t\t.collect(Collectors.toList())\n\t\t\t\t.forEach(toleration -> tolerations.add(new Toleration(toleration.getEffect(), toleration.getKey(),\n\t\t\t\t\t\ttoleration.getOperator(), toleration.getTolerationSeconds(), toleration.getValue())));\n\n\t\treturn tolerations;\n\t}\n\n\t/**\n\t * Volume deployment properties are specified in YAML format:\n\t *\n\t * <code>\n\t *     spring.cloud.deployer.kubernetes.volumes=[{name: testhostpath, hostPath: { path: '/test/override/hostPath' }},\n\t *     \t{name: 'testpvc', persistentVolumeClaim: { claimName: 'testClaim', readOnly: 'true' }},\n\t *     \t{name: 'testnfs', nfs: { server: '10.0.0.1:111', path: '/test/nfs' }}]\n\t * </code>\n\t *\n\t * Volumes can be specified as deployer properties as well as app deployment properties.\n\t * Deployment properties override deployer properties.\n\t *\n\t * @param kubernetesDeployerProperties the kubernetes deployer properties map\n\t * @return the configured volumes\n\t */\n\tList<Volume> getVolumes(Map<String, String> kubernetesDeployerProperties) {\n\t\tList<Volume> volumes = new ArrayList<>();\n\n\t\tKubernetesDeployerProperties deployerProperties = bindProperties(kubernetesDeployerProperties,\n\t\t\t\tthis.propertyPrefix + \".volumes\", \"volumes\");\n\n\t\tvolumes.addAll(deployerProperties.getVolumes());\n\n\t\t// only add volumes that have not already been added, based on the volume's name\n\t\t// i.e. allow provided deployment volumes to override deployer defined volumes\n\t\tvolumes.addAll(properties.getVolumes().stream()\n\t\t\t\t.filter(volume -> volumes.stream()\n\t\t\t\t\t\t.noneMatch(existingVolume -> existingVolume.getName().equals(volume.getName())))\n\t\t\t\t.collect(Collectors.toList()));\n\n\t\treturn volumes;\n\t}\n\n\t/**\n\t * Get the resource limits for the deployment request. A Pod can define its maximum needed resources by setting the\n\t * limits and Kubernetes can provide more resources if any are free.\n\t * <p>\n\t * Falls back to the server properties if not present in the deployment request.\n\t * <p>\n\t *\n\t * @param kubernetesDeployerProperties the kubernetes deployment properties map\n\t * @return the resource limits to use\n\t */\n\tMap<String, Quantity> deduceResourceLimits(Map<String, String> kubernetesDeployerProperties) {\n\t\tString memory = PropertyParserUtils.getDeploymentPropertyValue(kubernetesDeployerProperties, this.propertyPrefix + \".limits.memory\", properties.getLimits().getMemory());\n\t\tString cpu = PropertyParserUtils.getDeploymentPropertyValue(kubernetesDeployerProperties, this.propertyPrefix + \".limits.cpu\", properties.getLimits().getCpu());\n\t\tString ephemeralStorage = PropertyParserUtils.getDeploymentPropertyValue(kubernetesDeployerProperties, \".limits.ephemeral-storage\", properties.getLimits().getEphemeralStorage());\n\t\tString hugePages2Mi = PropertyParserUtils.getDeploymentPropertyValue(kubernetesDeployerProperties, \".limits.hugepages-2Mi\", properties.getLimits().getHugepages2Mi());\n\t\tString hugePages1Gi = PropertyParserUtils.getDeploymentPropertyValue(kubernetesDeployerProperties, \".limits.hugepages-1Gi\", properties.getLimits().getHugepages1Gi());\n\t\tString gpuVendor = PropertyParserUtils.getDeploymentPropertyValue(kubernetesDeployerProperties, this.propertyPrefix + \".limits.gpuVendor\", properties.getLimits().getGpuVendor());\n\t\tString gpuCount = PropertyParserUtils.getDeploymentPropertyValue(kubernetesDeployerProperties, this.propertyPrefix + \".limits.gpuCount\", properties.getLimits().getGpuCount());\n\n\t\tMap<String,Quantity> limits = new HashMap<String,Quantity>();\n\n\t\tif (StringUtils.hasText(memory)) {\n\t\t\tlimits.put(\"memory\", new Quantity(memory));\n\t\t}\n\n\t\tif (StringUtils.hasText(cpu)) {\n\t\t\tlimits.put(\"cpu\", new Quantity(cpu));\n\t\t}\n\t\tif(StringUtils.hasText(ephemeralStorage)) {\n\t\t\tlimits.put(\"ephemeral-storage\", new Quantity(ephemeralStorage));\n\t\t}\n\n\t\tif(StringUtils.hasText(hugePages2Mi)) {\n\t\t\tlimits.put(\"hugepages-2Mi\", new Quantity(hugePages2Mi));\n\t\t}\n\n\t\tif(StringUtils.hasText(hugePages1Gi)) {\n\t\t\tlimits.put(\"hugepages-1Gi\", new Quantity(hugePages1Gi));\n\t\t}\n\n\t\tif (StringUtils.hasText(gpuVendor) && StringUtils.hasText(gpuCount)) {\n\t\t\tlimits.put(gpuVendor, new Quantity(gpuCount));\n\t\t}\n\n\t\treturn limits;\n\t}\n\n\t/**\n\t * Get the image pull policy for the deployment request. If it is not present use the server default. If an override\n\t * for the deployment is present but not parseable, fall back to a default value.\n\t *\n\t * @param kubernetesDeployerProperties the kubernetes deployment properties map\n\t * @return The image pull policy to use for the container in the request.\n\t */\n\tImagePullPolicy deduceImagePullPolicy(Map<String, String> kubernetesDeployerProperties) {\n\t\tString pullPolicyOverride = PropertyParserUtils.getDeploymentPropertyValue(kubernetesDeployerProperties,\n\t\t\t\t\t\tthis.propertyPrefix + \".imagePullPolicy\");\n\n\t\tImagePullPolicy pullPolicy;\n\t\tif (pullPolicyOverride == null) {\n\t\t\tpullPolicy = properties.getImagePullPolicy();\n\t\t} else {\n\t\t\tpullPolicy = ImagePullPolicy.relaxedValueOf(pullPolicyOverride);\n\t\t\tif (pullPolicy == null) {\n\t\t\t\tlogger.warn(\"Parsing of pull policy \" + pullPolicyOverride + \" failed, using default \\\"IfNotPresent\\\".\");\n\t\t\t\tpullPolicy = ImagePullPolicy.IfNotPresent;\n\t\t\t}\n\t\t}\n\n\t\tlogger.debug(\"Using imagePullPolicy \" + pullPolicy);\n\n\t\treturn pullPolicy;\n\t}\n\n\t/**\n\t * Get the resource requests for the deployment request. Resource requests are guaranteed by the Kubernetes\n\t * runtime.\n\t * Falls back to the server properties if not present in the deployment request.\n\t *\n\t * @param kubernetesDeployerProperties the kubernetes deployer properties map\n\t * @return the resource requests to use\n\t */\n\tMap<String, Quantity> deduceResourceRequests(Map<String, String> kubernetesDeployerProperties) {\n\t\tString memOverride = PropertyParserUtils.getDeploymentPropertyValue(kubernetesDeployerProperties, this.propertyPrefix + \".requests.memory\", properties.getRequests().getMemory());\n\t\tString cpuOverride = PropertyParserUtils.getDeploymentPropertyValue(kubernetesDeployerProperties, this.propertyPrefix + \".requests.cpu\", properties.getRequests().getCpu());\n\n\t\tlogger.debug(\"Using requests - cpu: \" + cpuOverride + \" mem: \" + memOverride);\n\n\t\tString ephemeralStorage = PropertyParserUtils.getDeploymentPropertyValue(kubernetesDeployerProperties, \".requests.ephemeral-storage\", properties.getRequests().getEphemeralStorage());\n\t\tString hugePages2Mi = PropertyParserUtils.getDeploymentPropertyValue(kubernetesDeployerProperties, \".requests.hugepages-2Mi\", properties.getRequests().getHugepages2Mi());\n\t\tString hugePages1Gi = PropertyParserUtils.getDeploymentPropertyValue(kubernetesDeployerProperties, \".requests.hugepages-1Gi\", properties.getRequests().getHugepages1Gi());\n\n\t\tMap<String,Quantity> requests = new HashMap<String, Quantity>();\n\n\t\tif (StringUtils.hasText(memOverride)) {\n\t\t\trequests.put(\"memory\", new Quantity(memOverride));\n\t\t}\n\n\t\tif (StringUtils.hasText(cpuOverride)) {\n\t\t\trequests.put(\"cpu\", new Quantity(cpuOverride));\n\t\t}\n\t\tif(StringUtils.hasText(ephemeralStorage)) {\n\t\t\trequests.put(\"ephemeral-storage\", new Quantity(ephemeralStorage));\n\t\t}\n\n\t\tif(StringUtils.hasText(hugePages2Mi)) {\n\t\t\trequests.put(\"hugepages-2Mi\", new Quantity(hugePages2Mi));\n\t\t}\n\n\t\tif(StringUtils.hasText(hugePages1Gi)) {\n\t\t\trequests.put(\"hugepages-1Gi\", new Quantity(hugePages1Gi));\n\t\t}\n\n\n\t\treturn requests;\n\t}\n\n\t/**\n\t * Get the VolumeClaim template name for Statefulset from the deployment properties.\n\t *\n\t * @param kubernetesDeployerProperties the kubernetes deployer properties\n\t * @return the volume claim template name\n\t */\n\tString getStatefulSetVolumeClaimTemplateName(Map<String, String> kubernetesDeployerProperties) {\n\t\tString name = PropertyParserUtils.getDeploymentPropertyValue(kubernetesDeployerProperties,\n\t\t\t\tthis.propertyPrefix + \".statefulSet.volumeClaimTemplate.name\");\n\n\t\tif (name == null && properties.getStatefulSet() != null && properties.getStatefulSet().getVolumeClaimTemplate() != null) {\n\t\t\tname = properties.getStatefulSet().getVolumeClaimTemplate().getName();\n\t\t}\n\n\t\treturn name;\n\t}\n\n\t/**\n\t * Get the StatefulSet storage class name to be set in VolumeClaim template for the deployment properties.\n\t *\n\t * @param kubernetesDeployerProperties the kubernetes deployer properties\n\t * @return the storage class name\n\t */\n\tString getStatefulSetStorageClassName(Map<String, String> kubernetesDeployerProperties) {\n\t\tString storageClassName = PropertyParserUtils.getDeploymentPropertyValue(kubernetesDeployerProperties,\n\t\t\t\tthis.propertyPrefix + \".statefulSet.volumeClaimTemplate.storageClassName\");\n\n\t\tif (storageClassName == null && properties.getStatefulSet() != null && properties.getStatefulSet().getVolumeClaimTemplate() != null) {\n\t\t\tstorageClassName = properties.getStatefulSet().getVolumeClaimTemplate().getStorageClassName();\n\t\t}\n\n\t\treturn storageClassName;\n\t}\n\n\t/**\n\t * Get the StatefulSet storage value to be set in VolumeClaim template for the given deployment properties.\n\t *\n\t * @param kubernetesDeployerProperties the kubernetes deployer properties\n\t * @return the StatefulSet storage\n\t */\n\tString getStatefulSetStorage(Map<String, String> kubernetesDeployerProperties) {\n\t\tString storage = PropertyParserUtils.getDeploymentPropertyValue(kubernetesDeployerProperties,\n\t\t\t\tthis.propertyPrefix + \".statefulSet.volumeClaimTemplate.storage\");\n\n\t\tif (storage == null && properties.getStatefulSet() != null && properties.getStatefulSet().getVolumeClaimTemplate() != null) {\n\t\t\tstorage = properties.getStatefulSet().getVolumeClaimTemplate().getStorage();\n\t\t}\n\n\t\treturn ByteSizeUtils.parseToMebibytes(storage) + \"Mi\";\n\t}\n\n\t/**\n\t * Get the hostNetwork setting for the deployment request.\n\t *\n\t * @param kubernetesDeployerProperties the kubernetes deployment properties map\n\t * @return Whether host networking is requested\n\t */\n\tboolean getHostNetwork(Map<String, String> kubernetesDeployerProperties) {\n\t\tString hostNetworkOverride = PropertyParserUtils.getDeploymentPropertyValue(kubernetesDeployerProperties,\n\t\t\t\t\t\tthis.propertyPrefix + \".hostNetwork\");\n\t\tboolean hostNetwork;\n\n\t\tif (!StringUtils.hasText(hostNetworkOverride)) {\n\t\t\thostNetwork = properties.isHostNetwork();\n\t\t}\n\t\telse {\n\t\t\thostNetwork = Boolean.valueOf(hostNetworkOverride);\n\t\t}\n\n\t\tlogger.debug(\"Using hostNetwork \" + hostNetwork);\n\n\t\treturn hostNetwork;\n\t}\n\n\t/**\n\t * Get the nodeSelectors setting for the deployment request.\n\t *\n\t * @param deploymentProperties The deployment request deployment properties.\n\t * @return map of nodeSelectors\n\t */\n\tMap<String, String> getNodeSelectors(Map<String, String> deploymentProperties) {\n\t\tMap<String, String> nodeSelectors = new HashMap<>();\n\n\t\tString nodeSelector = this.properties.getNodeSelector();\n\t\tString nodeSelectorDeploymentProperty = deploymentProperties.getOrDefault(KubernetesDeployerProperties.KUBERNETES_DEPLOYMENT_NODE_SELECTOR, \"\");\n\t\tfor (String name : RelaxedNames.forCamelCase(KubernetesDeployerProperties.KUBERNETES_DEPLOYMENT_NODE_SELECTOR)) {\n\t\t\tString value = deploymentProperties.get(name);\n\t\t\tif (StringUtils.hasText(value)) {\n\t\t\t\tnodeSelectorDeploymentProperty = value;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\tboolean hasDeployerPropertyNodeSelector = StringUtils.hasText(nodeSelectorDeploymentProperty);\n\n\t\tif (hasDeployerPropertyNodeSelector) {\n\t\t\tnodeSelector = nodeSelectorDeploymentProperty;\n\t\t}\n\n\t\tif (StringUtils.hasText(nodeSelector)) {\n\t\t\tString[] nodeSelectorPairs = nodeSelector.split(\",\");\n\t\t\tfor (String nodeSelectorPair : nodeSelectorPairs) {\n\t\t\t\tString[] selector = nodeSelectorPair.split(\":\");\n\t\t\t\tAssert.isTrue(selector.length == 2, String.format(\"Invalid nodeSelector value: '%s'\", nodeSelectorPair));\n\t\t\t\tnodeSelectors.put(selector[0].trim(), selector[1].trim());\n\t\t\t}\n\t\t}\n\n\t\treturn nodeSelectors;\n\t}\n\n\tString getImagePullPolicy(Map<String, String> kubernetesDeployerProperties) {\n\t\tImagePullPolicy imagePullPolicy = deduceImagePullPolicy(kubernetesDeployerProperties);\n\t\treturn imagePullPolicy != null ? imagePullPolicy.name() : null;\n\t}\n\n\tString getImagePullSecret(Map<String, String> kubernetesDeployerProperties) {\n\t\tString imagePullSecret = PropertyParserUtils.getDeploymentPropertyValue(kubernetesDeployerProperties,\n\t\t\t\tthis.propertyPrefix + \".imagePullSecret\", \"\");\n\n\t\tif(!StringUtils.hasText(imagePullSecret)) {\n\t\t\timagePullSecret = this.properties.getImagePullSecret();\n\t\t}\n\n\t\treturn imagePullSecret;\n\t}\n\n\tList<String> getImagePullSecrets(Map<String, String> kubernetesDeployerProperties) {\n\n\t\tKubernetesDeployerProperties deployerProperties = bindProperties(kubernetesDeployerProperties,\n\t\t\t\tthis.propertyPrefix + \".imagePullSecrets\", \"imagePullSecrets\");\n\n\t\tif (deployerProperties.getImagePullSecrets() == null || deployerProperties.getImagePullSecrets().isEmpty()) {\n\t\t\treturn properties.getImagePullSecrets();\n\t\t} else {\n\t\t\treturn deployerProperties.getImagePullSecrets();\n\t\t}\n\t}\n\n\tString getDeploymentServiceAccountName(Map<String, String> kubernetesDeployerProperties) {\n\t\tString deploymentServiceAccountName = PropertyParserUtils.getDeploymentPropertyValue(kubernetesDeployerProperties,\n\t\t\t\tthis.propertyPrefix + \".deploymentServiceAccountName\");\n\n\t\tif (!StringUtils.hasText(deploymentServiceAccountName)) {\n\t\t\tdeploymentServiceAccountName = properties.getDeploymentServiceAccountName();\n\t\t}\n\n\t\treturn deploymentServiceAccountName;\n\t}\n\n\tBoolean getShareProcessNamespace(Map<String, String> kubernetesDeployerProperties) {\n\t\tKubernetesDeployerProperties deployerProperties = bindProperties(kubernetesDeployerProperties,\n\t\t\t\tthis.propertyPrefix + \".shareProcessNamespace\", \"shareProcessNamespace\");\n\t\treturn deployerProperties.getShareProcessNamespace();\n\t}\n\n\tString getPriorityClassName(Map<String, String> kubernetesDeployerProperties) {\n\t\tKubernetesDeployerProperties deployerProperties = bindProperties(kubernetesDeployerProperties,\n\t\t\t\tthis.propertyPrefix + \".priorityClassName\", \"priorityClassName\");\n\t\treturn deployerProperties.getPriorityClassName();\n\t}\n\n\tPodSecurityContext getPodSecurityContext(Map<String, String> kubernetesDeployerProperties) {\n\t\tPodSecurityContext podSecurityContext = null;\n\n\t\tKubernetesDeployerProperties deployerProperties = bindProperties(kubernetesDeployerProperties,\n\t\t\t\tthis.propertyPrefix + \".podSecurityContext\", \"podSecurityContext\");\n\n\t\tif (deployerProperties.getPodSecurityContext() != null) {\n\t\t\tpodSecurityContext = buildPodSecurityContext(deployerProperties);\n\t\t} else if (this.properties.getPodSecurityContext() != null ) {\n\t\t\tpodSecurityContext = buildPodSecurityContext(this.properties);\n\t\t}\n\t\treturn podSecurityContext;\n\t}\n\n\tprivate PodSecurityContext buildPodSecurityContext(KubernetesDeployerProperties deployerProperties) {\n\t\tPodSecurityContextBuilder podSecurityContextBuilder = new PodSecurityContextBuilder()\n\t\t\t\t.withRunAsUser(deployerProperties.getPodSecurityContext().getRunAsUser())\n\t\t\t\t.withRunAsGroup(deployerProperties.getPodSecurityContext().getRunAsGroup())\n\t\t\t\t.withRunAsNonRoot(deployerProperties.getPodSecurityContext().getRunAsNonRoot())\n\t\t\t\t.withFsGroup(deployerProperties.getPodSecurityContext().getFsGroup())\n\t\t\t\t.withFsGroupChangePolicy(deployerProperties.getPodSecurityContext().getFsGroupChangePolicy())\n\t\t\t\t.withSupplementalGroups(deployerProperties.getPodSecurityContext().getSupplementalGroups());\n\t\tif (deployerProperties.getPodSecurityContext().getSeccompProfile() != null) {\n\t\t\tpodSecurityContextBuilder.withNewSeccompProfile(\n\t\t\t\t\tdeployerProperties.getPodSecurityContext().getSeccompProfile().getLocalhostProfile(),\n\t\t\t\t\tdeployerProperties.getPodSecurityContext().getSeccompProfile().getType());\n\t\t}\n\t\tif (deployerProperties.getPodSecurityContext().getSeLinuxOptions() != null) {\n\t\t\tpodSecurityContextBuilder.withNewSeLinuxOptions(\n\t\t\t\t\tdeployerProperties.getPodSecurityContext().getSeLinuxOptions().getLevel(),\n\t\t\t\t\tdeployerProperties.getPodSecurityContext().getSeLinuxOptions().getRole(),\n\t\t\t\t\tdeployerProperties.getPodSecurityContext().getSeLinuxOptions().getType(),\n\t\t\t\t\tdeployerProperties.getPodSecurityContext().getSeLinuxOptions().getUser());\n\t\t}\n\t\tif (!CollectionUtils.isEmpty(deployerProperties.getPodSecurityContext().getSysctls()))  {\n\t\t\tList<Sysctl> sysctls = deployerProperties.getPodSecurityContext().getSysctls().stream()\n\t\t\t\t\t.map((sysctlInfo) -> new SysctlBuilder().withName(sysctlInfo.getName())\n\t\t\t\t\t\t\t.withValue(sysctlInfo.getValue()).build())\n\t\t\t\t\t.collect(Collectors.toList());\n\t\t\tpodSecurityContextBuilder.withSysctls(sysctls);\n\t\t}\n\t\tif (deployerProperties.getPodSecurityContext().getWindowsOptions() != null) {\n\t\t\tpodSecurityContextBuilder.withNewWindowsOptions(\n\t\t\t\t\tdeployerProperties.getPodSecurityContext().getWindowsOptions().getGmsaCredentialSpec(),\n\t\t\t\t\tdeployerProperties.getPodSecurityContext().getWindowsOptions().getGmsaCredentialSpecName(),\n\t\t\t\t\tdeployerProperties.getPodSecurityContext().getWindowsOptions().getHostProcess(),\n\t\t\t\t\tdeployerProperties.getPodSecurityContext().getWindowsOptions().getRunAsUserName());\n\t\t}\n\t\treturn podSecurityContextBuilder.build();\n\t}\n\n\tSecurityContext getContainerSecurityContext(Map<String, String> kubernetesDeployerProperties) {\n\t\tSecurityContext securityContext = null;\n\n\t\tKubernetesDeployerProperties deployerProperties = bindProperties(kubernetesDeployerProperties,\n\t\t\t\tthis.propertyPrefix + \".containerSecurityContext\", \"containerSecurityContext\");\n\n\t\tif (deployerProperties.getContainerSecurityContext() != null) {\n\t\t\tsecurityContext = buildContainerSecurityContext(deployerProperties);\n\t\t} else if (this.properties.getContainerSecurityContext() != null ) {\n\t\t\tsecurityContext = buildContainerSecurityContext(this.properties);\n\t\t}\n\t\treturn securityContext;\n\t}\n\n\tprivate SecurityContext buildContainerSecurityContext(KubernetesDeployerProperties deployerProperties) {\n\t\tSecurityContextBuilder securityContextBuilder = new SecurityContextBuilder()\n\t\t\t\t.withAllowPrivilegeEscalation(deployerProperties.getContainerSecurityContext().getAllowPrivilegeEscalation())\n\t\t\t\t.withPrivileged(deployerProperties.getContainerSecurityContext().getPrivileged())\n\t\t\t\t.withProcMount(deployerProperties.getContainerSecurityContext().getProcMount())\n\t\t\t\t.withReadOnlyRootFilesystem(deployerProperties.getContainerSecurityContext().getReadOnlyRootFilesystem())\n\t\t\t\t.withRunAsUser(deployerProperties.getContainerSecurityContext().getRunAsUser())\n\t\t\t\t.withRunAsGroup(deployerProperties.getContainerSecurityContext().getRunAsGroup())\n\t\t\t\t.withRunAsNonRoot(deployerProperties.getContainerSecurityContext().getRunAsNonRoot());\n\n\t\tif (deployerProperties.getContainerSecurityContext().getCapabilities() != null) {\n\t\t\tsecurityContextBuilder.withCapabilities(\n\t\t\t\t\tnew CapabilitiesBuilder().withAdd(deployerProperties.getContainerSecurityContext().getCapabilities().getAdd())\n\t\t\t\t\t\t\t.withDrop(deployerProperties.getContainerSecurityContext().getCapabilities().getDrop())\n\t\t\t\t\t\t\t.build());\n\t\t}\n\t\tif (deployerProperties.getContainerSecurityContext().getSeccompProfile() != null) {\n\t\t\tsecurityContextBuilder.withNewSeccompProfile(\n\t\t\t\t\tdeployerProperties.getContainerSecurityContext().getSeccompProfile().getLocalhostProfile(),\n\t\t\t\t\tdeployerProperties.getContainerSecurityContext().getSeccompProfile().getType());\n\t\t}\n\t\tif (deployerProperties.getContainerSecurityContext().getSeLinuxOptions() != null) {\n\t\t\tsecurityContextBuilder.withNewSeLinuxOptions(\n\t\t\t\t\tdeployerProperties.getContainerSecurityContext().getSeLinuxOptions().getLevel(),\n\t\t\t\t\tdeployerProperties.getContainerSecurityContext().getSeLinuxOptions().getRole(),\n\t\t\t\t\tdeployerProperties.getContainerSecurityContext().getSeLinuxOptions().getType(),\n\t\t\t\t\tdeployerProperties.getContainerSecurityContext().getSeLinuxOptions().getUser());\n\t\t}\n\t\tif (deployerProperties.getContainerSecurityContext().getWindowsOptions() != null) {\n\t\t\tsecurityContextBuilder.withNewWindowsOptions(\n\t\t\t\t\tdeployerProperties.getContainerSecurityContext().getWindowsOptions().getGmsaCredentialSpec(),\n\t\t\t\t\tdeployerProperties.getContainerSecurityContext().getWindowsOptions().getGmsaCredentialSpecName(),\n\t\t\t\t\tdeployerProperties.getContainerSecurityContext().getWindowsOptions().getHostProcess(),\n\t\t\t\t\tdeployerProperties.getContainerSecurityContext().getWindowsOptions().getRunAsUserName());\n\t\t}\n\t\treturn securityContextBuilder.build();\n\t}\n\n\tAffinity getAffinityRules(Map<String, String> kubernetesDeployerProperties) {\n\t\tAffinity affinity = new Affinity();\n\n\t\tString nodeAffinityPropertyKey = this.propertyPrefix + \".affinity.nodeAffinity\";\n\t\tString podAffinityPropertyKey = this.propertyPrefix + \".affinity.podAffinity\";\n\t\tString podAntiAffinityPropertyKey = this.propertyPrefix + \".affinity.podAntiAffinity\";\n\n\t\tString nodeAffinityValue = PropertyParserUtils.getDeploymentPropertyValue(kubernetesDeployerProperties,\n\t\t\t\tnodeAffinityPropertyKey);\n\t\tString podAffinityValue = PropertyParserUtils.getDeploymentPropertyValue(kubernetesDeployerProperties,\n\t\t\t\tpodAffinityPropertyKey);\n\t\tString podAntiAffinityValue = PropertyParserUtils.getDeploymentPropertyValue(kubernetesDeployerProperties,\n\t\t\t\tpodAntiAffinityPropertyKey);\n\n\t\tif (properties.getNodeAffinity() != null && !StringUtils.hasText(nodeAffinityValue)) {\n\t\t\taffinity.setNodeAffinity(new AffinityBuilder()\n\t\t\t\t\t.withNodeAffinity(properties.getNodeAffinity())\n\t\t\t\t\t.buildNodeAffinity());\n\t\t} else if (StringUtils.hasText(nodeAffinityValue)) {\n\t\t\tKubernetesDeployerProperties nodeAffinityProperties = bindProperties(kubernetesDeployerProperties,\n\t\t\t\t\tnodeAffinityPropertyKey, \"nodeAffinity\");\n\n\t\t\taffinity.setNodeAffinity(new AffinityBuilder()\n\t\t\t\t\t.withNodeAffinity(nodeAffinityProperties.getNodeAffinity())\n\t\t\t\t\t.buildNodeAffinity());\n\t\t}\n\n\t\tif (properties.getPodAffinity() != null && !StringUtils.hasText(podAffinityValue)) {\n\t\t\taffinity.setPodAffinity(new AffinityBuilder()\n\t\t\t\t\t.withPodAffinity(properties.getPodAffinity())\n\t\t\t\t\t.buildPodAffinity());\n\t\t} else if (StringUtils.hasText(podAffinityValue)) {\n\t\t\tKubernetesDeployerProperties podAffinityProperties = bindProperties(kubernetesDeployerProperties,\n\t\t\t\t\tpodAffinityPropertyKey, \"podAffinity\");\n\n\t\t\taffinity.setPodAffinity(new AffinityBuilder()\n\t\t\t\t\t.withPodAffinity(podAffinityProperties.getPodAffinity())\n\t\t\t\t\t.buildPodAffinity());\n\t\t}\n\n\t\tif (properties.getPodAntiAffinity() != null && !StringUtils.hasText(podAntiAffinityValue)) {\n\t\t\taffinity.setPodAntiAffinity(new AffinityBuilder()\n\t\t\t\t\t.withPodAntiAffinity(properties.getPodAntiAffinity())\n\t\t\t\t\t.buildPodAntiAffinity());\n\t\t} else if (StringUtils.hasText(podAntiAffinityValue)) {\n\t\t\tKubernetesDeployerProperties podAntiAffinityProperties = bindProperties(kubernetesDeployerProperties,\n\t\t\t\t\tpodAntiAffinityPropertyKey, \"podAntiAffinity\");\n\n\t\t\taffinity.setPodAntiAffinity(new AffinityBuilder()\n\t\t\t\t\t.withPodAntiAffinity(podAntiAffinityProperties.getPodAntiAffinity())\n\t\t\t\t\t.buildPodAntiAffinity());\n\t\t}\n\n\t\treturn affinity;\n\t}\n\n\tCollection<Container> getInitContainers(Map<String, String> kubernetesDeployerProperties) {\n\t\tCollection<Container> initContainers = new ArrayList<>();\n\t\tKubernetesDeployerProperties deployerProperties = bindProperties(kubernetesDeployerProperties,\n\t\t\t\tthis.propertyPrefix + \".initContainer\", \"initContainer\");\n\n\t\t// Deployment prop passed in for entire '.initContainer'\n\t\tInitContainer initContainerProps = deployerProperties.getInitContainer();\n\t\tif (initContainerProps != null) {\n\t\t\tinitContainers.add(containerFromProps(initContainerProps));\n\t\t} else {\n\t\t\tString propertyKey = this.propertyPrefix + \".initContainer\";\n\t\t\tContainer container = initContainerFromProperties(kubernetesDeployerProperties, propertyKey);\n\t\t\tif (container != null) {\n\t\t\t\tinitContainers.add(container);\n\t\t\t} else {\n\t\t\t\tinitContainerProps = this.properties.getInitContainer();\n\t\t\t\tif (initContainerProps != null) {\n\t\t\t\t\tinitContainers.add(containerFromProps(initContainerProps));\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tKubernetesDeployerProperties initContainerDeployerProperties = bindProperties(kubernetesDeployerProperties,\n\t\t\t\tthis.propertyPrefix + \".initContainers\", \"initContainers\");\n\t\tfor (InitContainer initContainer : initContainerDeployerProperties.getInitContainers()) {\n\t\t\tinitContainers.add(containerFromProps(initContainer));\n\t\t}\n\t\tif(initContainerDeployerProperties.getInitContainers().isEmpty()) {\n\t\t\tfor (int i = 0; ; i++) {\n\t\t\t\tString propertyKey = this.propertyPrefix + \".initContainers[\" + i + \"]\";\n\t\t\t\t// Get properties using binding\n\t\t\t\tKubernetesDeployerProperties kubeProps = bindProperties(kubernetesDeployerProperties, propertyKey, \"initContainer\");\n\t\t\t\tif (kubeProps.getInitContainer() != null) {\n\t\t\t\t\tinitContainers.add(containerFromProps(kubeProps.getInitContainer()));\n\t\t\t\t} else {\n\t\t\t\t\t// Get properties using FQN\n\t\t\t\t\tContainer initContainer = initContainerFromProperties(kubernetesDeployerProperties, propertyKey);\n\t\t\t\t\tif (initContainer != null) {\n\t\t\t\t\t\tinitContainers.add(initContainer);\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// Use default is configured\n\t\t\t\t\t\tif (properties.getInitContainers().size() > i) {\n\t\t\t\t\t\t\tinitContainers.add(containerFromProps(properties.getInitContainers().get(i)));\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif (!properties.getInitContainers().isEmpty()) {\n\t\t\t// Add remaining defaults.\n\t\t\tfor (int i = initContainers.size(); i < properties.getInitContainers().size(); i++) {\n\t\t\t\tinitContainers.add(containerFromProps(properties.getInitContainers().get(i)));\n\t\t\t}\n\t\t}\n\t\treturn initContainers;\n\t}\n\n\tprivate @Nullable Container initContainerFromProperties(Map<String, String> kubeProps, String propertyKey) {\n\t\tString name = getFirstProperty(kubeProps, propertyKey, \".name\", \".containerName\");\n\t\tString image = getFirstProperty(kubeProps, propertyKey, \".image\", \".imageName\");\n\t\tif (StringUtils.hasText(name) && StringUtils.hasText(image)) {\n\t\t\tString commandStr = getFirstProperty(kubeProps, propertyKey, \".command\", \".commands\");\n\t\t\tList<String> commands = StringUtils.hasText(commandStr) ? Arrays.asList(commandStr.split(\",\")) : Collections.emptyList();\n\t\t\tString envString = getFirstProperty(kubeProps, propertyKey, \".env\", \".environmentVariables\");\n\t\t\tList<VolumeMount> vms = this.getInitContainerVolumeMounts(kubeProps, propertyKey);\n\t\t\treturn new ContainerBuilder()\n\t\t\t\t\t.withName(name)\n\t\t\t\t\t.withImage(image)\n\t\t\t\t\t.withCommand(commands)\n\t\t\t\t\t.withEnv(toEnvironmentVariables((envString != null) ? envString.split(\",\") : new String[0]))\n\t\t\t\t\t.addAllToVolumeMounts(vms)\n\t\t\t\t\t.build();\n\t\t}\n\t\treturn null;\n\t}\n\n\tpublic static String getFirstProperty(Map<String, String> kubeProps, String baseKey, String... suffixes) {\n\t\tfor (String suffix : suffixes) {\n\t\t\tString value = PropertyParserUtils.getDeploymentPropertyValue(kubeProps, baseKey + suffix);\n\t\t\tif (StringUtils.hasText(value)) {\n\t\t\t\treturn value;\n\t\t\t}\n\t\t}\n\t\treturn null;\n\t}\n\n\tprivate Container containerFromProps(InitContainer initContainerProps) {\n\t\tList<EnvVar> envVarList = new ArrayList<>();\n\t\tenvVarList.addAll(toEnvironmentVariables(initContainerProps.getEnvironmentVariables()));\n\t\tenvVarList.addAll(toEnvironmentVariablesFromFieldRef(initContainerProps.getEnvironmentVariablesFromFieldRefs()));\n\n\t\tList<EnvFromSource> envFromSourceList = new ArrayList<>();\n\t\tenvFromSourceList.addAll(Arrays.stream(initContainerProps.getConfigMapRefEnvVars()).map(this::buildConfigMapRefEnvVar).collect(Collectors.toList()));\n\t\tenvFromSourceList.addAll(Arrays.stream(initContainerProps.getSecretRefEnvVars()).map(this::buildSecretRefEnvVar).collect(Collectors.toList()));\n\n\t\treturn new ContainerBuilder()\n\t\t\t\t.withName(initContainerProps.getName())\n\t\t\t\t.withImage(initContainerProps.getImage())\n\t\t\t\t.withCommand(initContainerProps.getCommand())\n\t\t\t\t.withArgs(initContainerProps.getArgs())\n\t\t\t\t.withEnv(envVarList)\n\t\t\t\t.withEnvFrom(envFromSourceList)\n\t\t\t\t.addAllToVolumeMounts(Optional.ofNullable(initContainerProps.getVolumeMounts()).orElse(Collections.emptyList()))\n\t\t\t\t.build();\n\t}\n\n\tprivate List<EnvVar>  toEnvironmentVariables(String[] environmentVariables) {\n\t\tMap<String, String> envVarsMap = new HashMap<>();\n\t\tif (environmentVariables != null) {\n\t\t\tfor (String envVar : environmentVariables) {\n\t\t\t\tString[] strings = envVar.split(\"=\", 2);\n\t\t\t\tAssert.isTrue(strings.length == 2, \"Invalid environment variable declared: \" + envVar);\n\t\t\t\tenvVarsMap.put(strings[0], strings[1]);\n\t\t\t}\n\t\t}\n\n\t\tList<EnvVar> envVars = new ArrayList<>();\n\t\tfor (Map.Entry<String, String> e : envVarsMap.entrySet()) {\n\t\t\tenvVars.add(new EnvVar(e.getKey(), e.getValue(), null));\n\t\t}\n\t\treturn envVars;\n\t}\n\n\tprivate List<EnvVar> toEnvironmentVariablesFromFieldRef(String[] environmentVariablesFromFieldRef) {\n\t\tif (environmentVariablesFromFieldRef == null || environmentVariablesFromFieldRef.length == 0) {\n\t\t\treturn Collections.emptyList();\n\t\t}\n\t\treturn Arrays.stream(environmentVariablesFromFieldRef)\n\t\t\t.map(entry -> {\n\t\t\t\tString[] tokens = entry.split(\"=\", 2);\n\t\t\t\tAssert.isTrue(tokens.length == 2 && StringUtils.hasText(tokens[0]) && StringUtils.hasText(tokens[1]),\n\t\t\t\t\t\"Invalid environment variable from field ref: \" + entry);\n\t\t\t\tObjectFieldSelector fieldSelector = new ObjectFieldSelectorBuilder()\n\t\t\t\t\t.withFieldPath(tokens[1])\n\t\t\t\t\t.build();\n\t\t\t\treturn new EnvVar(tokens[0], null, new EnvVarSourceBuilder().withFieldRef(fieldSelector).build());\n\t\t\t}).collect(Collectors.toList());\n\t}\n\n\tList<Container> getAdditionalContainers(Map<String, String> deploymentProperties) {\n\t\tList<Container> containers = new ArrayList<>();\n\n\t\tKubernetesDeployerProperties deployerProperties = bindProperties(deploymentProperties,\n\t\t\t\tthis.propertyPrefix + \".additionalContainers\", \"additionalContainers\" );\n\n\t\tif (deployerProperties.getAdditionalContainers() != null) {\n\t\t\tdeployerProperties.getAdditionalContainers().forEach(container ->\n\t\t\t\t\tcontainers.add(container));\n\t\t}\n\n\t\t// Add the containers from the original properties excluding the containers with the matching names from the\n\t\t// deployment properties\n\t\tif (this.properties.getAdditionalContainers() != null) {\n\t\t\tthis.properties.getAdditionalContainers().stream()\n\t\t\t\t\t.filter(container -> containers.stream().noneMatch(existing -> existing.getName().equals(container.getName())))\n\t\t\t\t\t.forEachOrdered(container -> containers.add(container));\n\t\t}\n\n\t\treturn containers;\n\t}\n\n\tMap<String, String> getPodAnnotations(Map<String, String> kubernetesDeployerProperties) {\n\t\tString annotationsValue = PropertyParserUtils.getDeploymentPropertyValue(kubernetesDeployerProperties,\n\t\t\t\tthis.propertyPrefix + \".podAnnotations\", \"\");\n\n\t\tif (!StringUtils.hasText(annotationsValue)) {\n\t\t\tannotationsValue = properties.getPodAnnotations();\n\t\t}\n\n\t\treturn PropertyParserUtils.getStringPairsToMap(annotationsValue);\n\t}\n\n\tMap<String, String> getServiceAnnotations(Map<String, String> kubernetesDeployerProperties) {\n\t\tString annotationsProperty = PropertyParserUtils.getDeploymentPropertyValue(kubernetesDeployerProperties,\n\t\t\t\tthis.propertyPrefix + \".serviceAnnotations\", \"\");\n\n\t\tif (!StringUtils.hasText(annotationsProperty)) {\n\t\t\tannotationsProperty = this.properties.getServiceAnnotations();\n\t\t}\n\n\t\treturn PropertyParserUtils.getStringPairsToMap(annotationsProperty);\n\t}\n\n\tMap<String, String> getDeploymentLabels(Map<String, String> kubernetesDeployerProperties) {\n\t\tMap<String, String> labels = new HashMap<>();\n\n\t\tString deploymentLabels = PropertyParserUtils.getDeploymentPropertyValue(kubernetesDeployerProperties,\n\t\t\t\tthis.propertyPrefix + \".deploymentLabels\", \"\");\n\n\t\t// Add deployment labels set at the deployer level.\n\t\tString updatedLabels = StringUtils.hasText(this.properties.getDeploymentLabels()) ?\n\t\t\t\tnew StringBuilder().append(deploymentLabels).append(StringUtils.hasText(deploymentLabels) ? \",\": \"\")\n\t\t\t\t\t\t.append(this.properties.getDeploymentLabels()).toString() : deploymentLabels;\n\n\t\tif (StringUtils.hasText(updatedLabels)) {\n\t\t\tString[] deploymentLabel = updatedLabels.split(\",\");\n\n\t\t\tfor (String label : deploymentLabel) {\n\t\t\t\tString[] labelPair = label.split(\":\");\n\t\t\t\tAssert.isTrue(labelPair.length == 2,\n\t\t\t\t\t\tString.format(\"Invalid label format, expected 'labelKey:labelValue', got: '%s'\", labelPair));\n\t\t\t\tlabels.put(labelPair[0].trim(), labelPair[1].trim());\n\t\t\t}\n\t\t}\n\t\treturn labels;\n\t}\n\n\tRestartPolicy getRestartPolicy(Map<String, String> kubernetesDeployerProperties) {\n\t\tString restartPolicy = PropertyParserUtils.getDeploymentPropertyValue(kubernetesDeployerProperties,\n\t\t\t\tthis.propertyPrefix + \".restartPolicy\", \"\");\n\n\t\tif (StringUtils.hasText(restartPolicy)) {\n\t\t\treturn RestartPolicy.valueOf(restartPolicy);\n\t\t}\n\n\t\treturn this.properties.getRestartPolicy();\n\t}\n\n\tString getTaskServiceAccountName(Map<String, String> kubernetesDeployerProperties) {\n\t\tString taskServiceAccountName = PropertyParserUtils.getDeploymentPropertyValue(kubernetesDeployerProperties,\n\t\t\t\tthis.propertyPrefix + \".taskServiceAccountName\", \"\");\n\t\tif (StringUtils.hasText(taskServiceAccountName)) {\n\t\t\treturn taskServiceAccountName;\n\t\t}\n\n\t\treturn this.properties.getTaskServiceAccountName();\n\t}\n\n\t/**\n\t * Binds the YAML formatted value of a deployment property to a {@link KubernetesDeployerProperties} instance.\n\t *\n\t * @param kubernetesDeployerProperties the map of Kubernetes deployer properties\n\t * @param propertyKey the property key to obtain the value to bind for\n\t * @param yamlLabel the label representing the field to bind to\n\t * @return a {@link KubernetesDeployerProperties} with the bound property data\n\t */\n\tprivate static KubernetesDeployerProperties bindProperties(Map<String, String> kubernetesDeployerProperties,\n\t\t\tString propertyKey, String yamlLabel) {\n\t\tString deploymentPropertyValue = PropertyParserUtils.getDeploymentPropertyValue(kubernetesDeployerProperties, propertyKey);\n\n\t\tKubernetesDeployerProperties deployerProperties = new KubernetesDeployerProperties();\n\n\t\tif (StringUtils.hasText(deploymentPropertyValue)) {\n\t\t\ttry {\n\t\t\t\tYamlPropertiesFactoryBean properties = new YamlPropertiesFactoryBean();\n\t\t\t\tString tmpYaml = \"{ \" + yamlLabel + \": \" + deploymentPropertyValue + \" }\";\n\t\t\t\tproperties.setResources(new ByteArrayResource(tmpYaml.getBytes()));\n\t\t\t\tProperties yaml = properties.getObject();\n\t\t\t\tMapConfigurationPropertySource source = new MapConfigurationPropertySource(yaml);\n\t\t\t\tdeployerProperties = new Binder(source)\n\t\t\t\t\t\t.bind(\"\", Bindable.of(KubernetesDeployerProperties.class)).get();\n\t\t\t} catch (Exception e) {\n\t\t\t\tthrow new IllegalArgumentException(\n\t\t\t\t\t\tString.format(\"Invalid binding property '%s'\", deploymentPropertyValue), e);\n\t\t\t}\n\t\t}\n\n\t\treturn deployerProperties;\n\t}\n\n\tString getStatefulSetInitContainerImageName(Map<String, String> kubernetesDeployerProperties) {\n\t\tString statefulSetInitContainerImageName = PropertyParserUtils.getDeploymentPropertyValue(kubernetesDeployerProperties,\n\t\t\t\tthis.propertyPrefix + \".statefulSetInitContainerImageName\", \"\");\n\n\t\tif (StringUtils.hasText(statefulSetInitContainerImageName)) {\n\t\t\treturn statefulSetInitContainerImageName;\n\t\t}\n\n\t\tstatefulSetInitContainerImageName = this.properties.getStatefulSetInitContainerImageName();\n\n\t\tif (StringUtils.hasText(statefulSetInitContainerImageName)) {\n\t\t\treturn statefulSetInitContainerImageName;\n\t\t}\n\n\t\treturn STATEFUL_SET_IMAGE_NAME;\n\t}\n\n\tMap<String, String> getJobAnnotations(Map<String, String> kubernetesDeployerProperties) {\n\t\tString annotationsProperty = PropertyParserUtils.getDeploymentPropertyValue(kubernetesDeployerProperties,\n\t\t\t\tthis.propertyPrefix + \".jobAnnotations\", \"\");\n\n\t\tif (!StringUtils.hasText(annotationsProperty)) {\n\t\t\tannotationsProperty = this.properties.getJobAnnotations();\n\t\t}\n\n\t\treturn PropertyParserUtils.getStringPairsToMap(annotationsProperty);\n\t}\n\n\t/**\n\t * Volume mount deployment properties are specified in YAML format:\n\t * <p>\n\t * <code>\n\t * spring.cloud.deployer.kubernetes.volumeMounts=[{name: 'testhostpath', mountPath: '/test/hostPath'},\n\t * {name: 'testpvc', mountPath: '/test/pvc'}, {name: 'testnfs', mountPath: '/test/nfs'}]\n\t * </code>\n\t * <p>\n\t * Volume mounts can be specified as deployer properties as well as app deployment properties.\n\t * Deployment properties override deployer properties.\n\t *\n\t * @param deploymentProperties the deployment properties from {@link AppDeploymentRequest}\n\t * @return the configured volume mounts\n\t */\n\tList<VolumeMount> getVolumeMounts(Map<String, String> deploymentProperties) {\n\t\treturn this.getVolumeMounts(PropertyParserUtils.getDeploymentPropertyValue(deploymentProperties,\n\t\t\t\tthis.propertyPrefix + \".volumeMounts\"));\n\t}\n\n\t/**\n\t * Init Containers volume mount properties are specified in YAML format:\n\t * <p>\n\t * <code>\n\t * spring.cloud.deployer.kubernetes.initContainer.volumeMounts=[{name: 'testhostpath', mountPath: '/test/hostPath'},\n\t * {name: 'testpvc', mountPath: '/test/pvc'}, {name: 'testnfs', mountPath: '/test/nfs'}]\n\t * </code>\n\t * <p>\n\t * They can be specified as deployer properties as well as app deployment properties.\n\t * The later overrides deployer properties.\n\t *\n\t * @param deploymentProperties the deployment properties from {@link AppDeploymentRequest}\n\t * @return the configured volume mounts\n\t */\n\tprivate List<VolumeMount> getInitContainerVolumeMounts(Map<String, String> deploymentProperties, String propertyKey) {\n\t\treturn this.getVolumeMounts(PropertyParserUtils.getDeploymentPropertyValue(\n\t\t\t\tdeploymentProperties,\n\t\t\t\tpropertyKey + \".volumeMounts\")\n\t\t);\n\t}\n\n\tprivate List<VolumeMount> getVolumeMounts(String propertyValue) {\n\t\tList<VolumeMount> volumeMounts = new ArrayList<>();\n\n\t\tif (StringUtils.hasText(propertyValue)) {\n\t\t\ttry {\n\t\t\t\tYamlPropertiesFactoryBean properties = new YamlPropertiesFactoryBean();\n\t\t\t\tString tmpYaml = \"{ volume-mounts: \" + propertyValue + \" }\";\n\t\t\t\tproperties.setResources(new ByteArrayResource(tmpYaml.getBytes()));\n\t\t\t\tProperties yaml = properties.getObject();\n\t\t\t\tMapConfigurationPropertySource source = new MapConfigurationPropertySource(yaml);\n\t\t\t\tKubernetesDeployerProperties deployerProperties = new Binder(source)\n\t\t\t\t\t\t.bind(\"\", Bindable.of(KubernetesDeployerProperties.class)).get();\n\t\t\t\tvolumeMounts.addAll(deployerProperties.getVolumeMounts());\n\t\t\t} catch (Exception e) {\n\t\t\t\tthrow new IllegalArgumentException(\n\t\t\t\t\t\tString.format(\"Invalid volume mount '%s'\", propertyValue), e);\n\t\t\t}\n\t\t}\n\n\t\t// only add volume mounts that have not already been added, based on the volume mount's name\n\t\t// i.e. allow provided deployment volume mounts to override deployer defined volume mounts\n\t\tvolumeMounts.addAll(this.properties.getVolumeMounts().stream().filter(volumeMount -> volumeMounts.stream()\n\t\t\t\t.noneMatch(existingVolumeMount -> existingVolumeMount.getName().equals(volumeMount.getName())))\n\t\t\t\t.collect(Collectors.toList()));\n\n\t\treturn volumeMounts;\n\t}\n\n\t/**\n\t * The list represents a single command with many arguments.\n\t *\n\t * @param deploymentProperties the kubernetes deployer properties map\n\t * @return a list of strings that represents the command and any arguments for that command\n\t */\n\tList<String> getContainerCommand(Map<String, String> deploymentProperties) {\n\t\tString containerCommand = PropertyParserUtils.getDeploymentPropertyValue(deploymentProperties,\n\t\t\t\tthis.propertyPrefix + \".containerCommand\", \"\");\n\n\t\treturn new CommandLineTokenizer(containerCommand).getArgs();\n\t}\n\n\t/**\n\t * Parse Lifecycle hooks.\n\t * @param deploymentProperties the kubernetes deployer properties map\n\t * @return Lifecycle spec\n\t */\n\tKubernetesDeployerProperties.Lifecycle getLifeCycle(Map<String,String> deploymentProperties) {\n\t\tKubernetesDeployerProperties.Lifecycle lifecycle = properties.getLifecycle();\n\n\t\tif (deploymentProperties.keySet().stream()\n\t\t\t\t.noneMatch(s -> s.startsWith(propertyPrefix + \".lifecycle\"))) {\n\t\t\treturn lifecycle;\n\t\t}\n\t\tString postStart = PropertyParserUtils.getDeploymentPropertyValue(deploymentProperties,\n\t\t\t\tthis.propertyPrefix + \".lifecycle.postStart.exec.command\");\n\t\tif (StringUtils.hasText(postStart)) {\n\t\t\tlifecycle.setPostStart(lifecycleHook(postStart));\n\t\t}\n\n\t\tString preStop = PropertyParserUtils.getDeploymentPropertyValue(deploymentProperties,\n\t\t\t\tthis.propertyPrefix + \".lifecycle.preStop.exec.command\");\n\t\tif (StringUtils.hasText(preStop)) {\n\t\t\tlifecycle.setPreStop(lifecycleHook(preStop));\n\t\t}\n\t\treturn lifecycle;\n\t}\n\n\tprivate KubernetesDeployerProperties.Lifecycle.Hook lifecycleHook(String command) {\n\t\tKubernetesDeployerProperties.Lifecycle.Hook hook = new KubernetesDeployerProperties.Lifecycle.Hook();\n\t\tKubernetesDeployerProperties.Lifecycle.Exec exec = new KubernetesDeployerProperties.Lifecycle.Exec();\n\t\texec.setCommand(Arrays.asList(command.split(\",\")));\n\t\thook.setExec(exec);\n\t\treturn hook;\n\t}\n\n\t/**\n\t * Determine the pod-level termination grace period seconds.\n\t * @param deploymentProperties the deployer properties\n\t * @return the termination grace period seconds to use for the pod's containers or null to use the default\n\t */\n\tLong determineTerminationGracePeriodSeconds(Map<String, String> deploymentProperties) {\n\t\tString gracePeriodStr = PropertyParserUtils.getDeploymentPropertyValue(deploymentProperties,\n\t\t\t\tthis.propertyPrefix + \".terminationGracePeriodSeconds\", null);\n\t\tif (gracePeriodStr != null) {\n\t\t\treturn Long.parseLong(gracePeriodStr);\n\t\t}\n\t\treturn this.properties.getTerminationGracePeriodSeconds();\n\t}\n\n\t/**\n\t * @param deploymentProperties the kubernetes deployer properties map\n\t * @return a list of Integers to add to the container\n\t */\n\tList<Integer> getContainerPorts(Map<String, String> deploymentProperties) {\n\t\tList<Integer> containerPortList = new ArrayList<>();\n\t\tString containerPorts = PropertyParserUtils.getDeploymentPropertyValue(deploymentProperties,\n\t\t\t\tthis.propertyPrefix + \".containerPorts\", null);\n\n\t\tif (containerPorts != null) {\n\t\t\tString[] containerPortSplit = containerPorts.split(\",\");\n\t\t\tfor (String containerPort : containerPortSplit) {\n\t\t\t\tlogger.trace(\"Adding container ports from AppDeploymentRequest: \" + containerPort);\n\t\t\t\tInteger port = Integer.parseInt(containerPort.trim());\n\t\t\t\tcontainerPortList.add(port);\n\t\t\t}\n\t\t}\n\n\t\treturn containerPortList;\n\t}\n\n\t/**\n\t * @param deploymentProperties the kubernetes deployer properties map\n\t * @return a List of EnvVar objects for app specific environment settings\n\t */\n\tMap<String, String> getAppEnvironmentVariables(Map<String, String> deploymentProperties) {\n\t\tMap<String, String> appEnvVarMap = new HashMap<>();\n\t\tString appEnvVar = PropertyParserUtils.getDeploymentPropertyValue(deploymentProperties,\n\t\t\t\tthis.propertyPrefix + \".environmentVariables\", null);\n\n\t\tif (appEnvVar != null) {\n\t\t\tString[] appEnvVars = new NestedCommaDelimitedVariableParser().parse(appEnvVar);\n\t\t\tfor (String envVar : appEnvVars) {\n\t\t\t\tlogger.trace(\"Adding environment variable from AppDeploymentRequest: \" + envVar);\n\t\t\t\tString[] strings = envVar.split(\"=\", 2);\n\t\t\t\tAssert.isTrue(strings.length == 2, \"Invalid environment variable declared: \" + envVar);\n\t\t\t\tappEnvVarMap.put(strings[0], strings[1]);\n\t\t\t}\n\t\t}\n\n\t\treturn appEnvVarMap;\n\t}\n\n\tstatic class NestedCommaDelimitedVariableParser {\n\t\tstatic final String REGEX = \"(\\\\w+='.+?'),?\";\n\t\tstatic final Pattern pattern = Pattern.compile(REGEX);\n\n\t\tString[] parse(String value) {\n\t\t\tList<String> vars = new ArrayList<>();\n\n\t\t\tMatcher m = pattern.matcher(value);\n\n\t\t\twhile (m.find()) {\n\t\t\t\tString replacedVar = m.group(1).replaceAll(\"'\",\"\");\n\n\t\t\t\tif (StringUtils.hasText(replacedVar)) {\n\t\t\t\t\tvars.add(replacedVar);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tString nonQuotedVars = value.replaceAll(pattern.pattern(), \"\");\n\n\t\t\tif (StringUtils.hasText(nonQuotedVars)) {\n\t\t\t\tvars.addAll(Arrays.asList(nonQuotedVars.split(\",\")));\n\t\t\t}\n\n\t\t\treturn vars.toArray(new String[0]);\n\t\t}\n\t}\n\n\tEntryPointStyle determineEntryPointStyle(Map<String, String> deploymentProperties) {\n\t\tEntryPointStyle entryPointStyle = null;\n\t\tString deployerPropertyValue = PropertyParserUtils.getDeploymentPropertyValue(deploymentProperties,\n\t\t\t\tthis.propertyPrefix + \".entryPointStyle\", null);\n\n\t\tif (deployerPropertyValue != null) {\n\t\t\ttry {\n\t\t\t\tentryPointStyle = EntryPointStyle.valueOf(deployerPropertyValue.toLowerCase(Locale.ROOT));\n\t\t\t}\n\t\t\tcatch (IllegalArgumentException ignore) {\n\t\t\t}\n\t\t}\n\n\t\tif (entryPointStyle == null) {\n\t\t\tentryPointStyle = this.properties.getEntryPointStyle();\n\t\t}\n\n\t\treturn entryPointStyle;\n\t}\n\n\tProbeType determineProbeType(Map<String, String> deploymentProperties) {\n\t\tProbeType probeType = this.properties.getProbeType();\n\t\tString deployerPropertyValue = PropertyParserUtils.getDeploymentPropertyValue(deploymentProperties,\n\t\t\t\tthis.propertyPrefix + \".probeType\", null);\n\n\t\tif (StringUtils.hasText(deployerPropertyValue)) {\n\t\t\tprobeType = ProbeType.valueOf(deployerPropertyValue.toUpperCase(Locale.ROOT));\n\t\t}\n\n\t\treturn probeType;\n\t}\n\n\tList<EnvVar> getConfigMapKeyRefs(Map<String, String> deploymentProperties) {\n\t\tList<EnvVar> configMapKeyRefs = new ArrayList<>();\n\t\tKubernetesDeployerProperties deployerProperties = bindProperties(deploymentProperties,\n\t\t\t\tthis.propertyPrefix + \".configMapKeyRefs\", \"configMapKeyRefs\");\n\n\t\tdeployerProperties.getConfigMapKeyRefs().forEach(configMapKeyRef ->\n\t\t\t\tconfigMapKeyRefs.add(buildConfigMapKeyRefEnvVar(configMapKeyRef)));\n\n\t\tproperties.getConfigMapKeyRefs().stream()\n\t\t\t\t.filter(configMapKeyRef -> configMapKeyRefs.stream()\n\t\t\t\t\t\t.noneMatch(existing -> existing.getName().equals(configMapKeyRef.getEnvVarName())))\n\t\t\t\t.collect(Collectors.toList())\n\t\t\t\t.forEach(configMapKeyRef -> configMapKeyRefs.add(buildConfigMapKeyRefEnvVar(configMapKeyRef)));\n\n\t\treturn configMapKeyRefs;\n\t}\n\n\tprivate EnvVar buildConfigMapKeyRefEnvVar(ConfigMapKeyRef configMapKeyRef) {\n\t\tConfigMapKeySelector configMapKeySelector = new ConfigMapKeySelector();\n\n\t\tEnvVarSource envVarSource = new EnvVarSource();\n\t\tenvVarSource.setConfigMapKeyRef(configMapKeySelector);\n\n\t\tEnvVar configMapKeyEnvRefVar = new EnvVar();\n\t\tconfigMapKeyEnvRefVar.setValueFrom(envVarSource);\n\t\tconfigMapKeySelector.setName(configMapKeyRef.getConfigMapName());\n\t\tconfigMapKeySelector.setKey(configMapKeyRef.getDataKey());\n\t\tconfigMapKeyEnvRefVar.setName(configMapKeyRef.getEnvVarName());\n\n\t\treturn configMapKeyEnvRefVar;\n\t}\n\n\tList<EnvVar> getSecretKeyRefs(Map<String, String> deploymentProperties) {\n\t\tList<EnvVar> secretKeyRefs = new ArrayList<>();\n\n\t\tKubernetesDeployerProperties deployerProperties = bindProperties(deploymentProperties,\n\t\t\t\tthis.propertyPrefix + \".secretKeyRefs\", \"secretKeyRefs\" );\n\n\t\tdeployerProperties.getSecretKeyRefs().forEach(secretKeyRef ->\n\t\t\t\tsecretKeyRefs.add(buildSecretKeyRefEnvVar(secretKeyRef)));\n\n\t\tproperties.getSecretKeyRefs().stream()\n\t\t\t\t.filter(secretKeyRef -> secretKeyRefs.stream()\n\t\t\t\t\t\t.noneMatch(existing -> existing.getName().equals(secretKeyRef.getEnvVarName())))\n\t\t\t\t.collect(Collectors.toList())\n\t\t\t\t.forEach(secretKeyRef -> secretKeyRefs.add(buildSecretKeyRefEnvVar(secretKeyRef)));\n\n\t\treturn secretKeyRefs;\n\t}\n\n\tprivate EnvVar buildSecretKeyRefEnvVar(SecretKeyRef secretKeyRef) {\n\t\tSecretKeySelector secretKeySelector = new SecretKeySelector();\n\n\t\tEnvVarSource envVarSource = new EnvVarSource();\n\t\tenvVarSource.setSecretKeyRef(secretKeySelector);\n\n\t\tEnvVar secretKeyEnvRefVar = new EnvVar();\n\t\tsecretKeyEnvRefVar.setValueFrom(envVarSource);\n\t\tsecretKeySelector.setName(secretKeyRef.getSecretName());\n\t\tsecretKeySelector.setKey(secretKeyRef.getDataKey());\n\t\tsecretKeyEnvRefVar.setName(secretKeyRef.getEnvVarName());\n\n\t\treturn secretKeyEnvRefVar;\n\t}\n\n\tList<EnvFromSource> getConfigMapRefs(Map<String, String> deploymentProperties) {\n\t\tList<EnvFromSource> configMapRefs = new ArrayList<>();\n\t\tKubernetesDeployerProperties deployerProperties = bindProperties(deploymentProperties,\n\t\t\t\tthis.propertyPrefix + \".configMapRefs\", \"configMapRefs\");\n\n\t\tdeployerProperties.getConfigMapRefs().forEach(configMapRef ->\n\t\t\t\tconfigMapRefs.add(buildConfigMapRefEnvVar(configMapRef)));\n\n\t\tif (deployerProperties.getConfigMapRefs().isEmpty()) {\n\t\t\tproperties.getConfigMapRefs().stream()\n\t\t\t\t\t.filter(configMapRef -> configMapRefs.stream()\n\t\t\t\t\t\t\t.noneMatch(existing -> existing.getConfigMapRef().getName().equals(configMapRef)))\n\t\t\t\t\t.collect(Collectors.toList())\n\t\t\t\t\t.forEach(configMapRef -> configMapRefs.add(buildConfigMapRefEnvVar(configMapRef)));\n\t\t}\n\n\t\treturn configMapRefs;\n\t}\n\n\tprivate EnvFromSource buildConfigMapRefEnvVar(String configMapRefName) {\n\t\tConfigMapEnvSource configMapEnvSource = new ConfigMapEnvSource();\n\t\tconfigMapEnvSource.setName(configMapRefName);\n\n\t\tEnvFromSource envFromSource = new EnvFromSource();\n\t\tenvFromSource.setConfigMapRef(configMapEnvSource);\n\n\t\treturn envFromSource;\n\t}\n\n\tList<EnvFromSource> getSecretRefs(Map<String, String> deploymentProperties) {\n\t\tList<EnvFromSource> secretRefs = new ArrayList<>();\n\t\tKubernetesDeployerProperties deployerProperties = bindProperties(deploymentProperties,\n\t\t\t\tthis.propertyPrefix + \".secretRefs\", \"secretRefs\");\n\n\t\tdeployerProperties.getSecretRefs().forEach(secretRef ->\n\t\t\t\tsecretRefs.add(buildSecretRefEnvVar(secretRef)));\n\n\t\tif (deployerProperties.getSecretRefs().isEmpty()) {\n\t\t\tproperties.getSecretRefs().stream()\n\t\t\t\t\t.filter(secretRef -> secretRefs.stream()\n\t\t\t\t\t\t\t.noneMatch(existing -> existing.getSecretRef().getName().equals(secretRef)))\n\t\t\t\t\t.collect(Collectors.toList())\n\t\t\t\t\t.forEach(secretRef -> secretRefs.add(buildSecretRefEnvVar(secretRef)));\n\t\t}\n\n\t\treturn secretRefs;\n\t}\n\n\tprivate EnvFromSource buildSecretRefEnvVar(String secretRefName) {\n\t\tSecretEnvSource secretEnvSource = new SecretEnvSource();\n\t\tsecretEnvSource.setName(secretRefName);\n\n\t\tEnvFromSource envFromSource = new EnvFromSource();\n\t\tenvFromSource.setSecretRef(secretEnvSource);\n\n\t\treturn envFromSource;\n\t}\n}\n"
  },
  {
    "path": "spring-cloud-deployer-kubernetes/src/main/java/org/springframework/cloud/deployer/spi/kubernetes/EntryPointStyle.java",
    "content": "/*\n * Copyright 2018-2019 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.kubernetes;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport org.springframework.boot.context.properties.bind.Bindable;\nimport org.springframework.boot.context.properties.bind.Binder;\nimport org.springframework.boot.context.properties.source.MapConfigurationPropertySource;\n\n/**\n * Defines container entry point styles that are available. The selected entry point style\n * will determine how application properties are made available to the container.\n *\n * @author Chris Schaefer\n */\npublic enum EntryPointStyle {\n\t/**\n\t * Application properties will be provided to the container as command line arguments.\n\t */\n\texec,\n\n\t/**\n\t * Application properties will be provided to the container as environment variables.\n\t */\n\tshell,\n\n\t/**\n\t * Application properties will be provided to the container as JSON in the\n\t * SPRING_APPLICATION_JSON environment variable. Command line arguments will be passed\n\t * as-is.\n\t */\n\tboot;\n\n\t/**\n\t * Converts the string of the provided entry point style to the appropriate enum value.\n\t * Defaults to {@link EntryPointStyle#exec} if no matching\n\t * entry point style is found.\n\t *\n\t * @param entryPointStyle the entry point style to use\n\t * @return the converted {@link EntryPointStyle}\n\t */\n\tpublic static EntryPointStyle relaxedValueOf(String entryPointStyle) {\n\t\t// 'value' is just a dummy key as you can't bind a single value to an enum\n\t\tMap<String, String> props = new HashMap<>();\n\t\tprops.put(\"value\", entryPointStyle);\n\t\tMapConfigurationPropertySource source = new MapConfigurationPropertySource(props);\n\t\tBinder binder = new Binder(source);\n\t\ttry {\n\t\t\treturn binder.bind(\"value\", Bindable.of(EntryPointStyle.class)).get();\n\t\t} catch (Exception e) {\n\t\t\t// error means we couldn't bind, caller seem to handle null\n\t\t}\n\t\treturn exec;\n\t}\n}\n"
  },
  {
    "path": "spring-cloud-deployer-kubernetes/src/main/java/org/springframework/cloud/deployer/spi/kubernetes/HttpProbeCreator.java",
    "content": "/*\n * Copyright 2020 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.kubernetes;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport io.fabric8.kubernetes.api.model.HTTPGetActionBuilder;\nimport io.fabric8.kubernetes.api.model.HTTPHeader;\nimport io.fabric8.kubernetes.api.model.Probe;\nimport io.fabric8.kubernetes.api.model.ProbeBuilder;\nimport io.fabric8.kubernetes.api.model.Secret;\n\nimport org.springframework.util.Assert;\nimport org.springframework.util.StringUtils;\n\n/**\n * Base class for HTTP based probe creators\n *\n * @author Chris Schaefer\n * @author Corneil du Plessis\n * @since 2.5\n */\npublic abstract class HttpProbeCreator extends ProbeCreator {\n    private static final int BOOT_1_MAJOR_VERSION = 1;\n\n    static final String AUTHORIZATION_HEADER_NAME = \"Authorization\";\n    static final String PROBE_CREDENTIALS_SECRET_KEY_NAME = \"credentials\";\n    static final String BOOT_1_READINESS_PROBE_PATH = \"/info\";\n    static final String BOOT_1_LIVENESS_PROBE_PATH = \"/health\";\n    static final String BOOT_2_READINESS_PROBE_PATH = \"/actuator\" + BOOT_1_READINESS_PROBE_PATH;\n    static final String BOOT_2_LIVENESS_PROBE_PATH = \"/actuator\" + BOOT_1_LIVENESS_PROBE_PATH;\n    static final String DEFAULT_PROBE_SCHEME = \"HTTP\";\n\n    HttpProbeCreator(KubernetesDeployerProperties kubernetesDeployerProperties,\n                     ContainerConfiguration containerConfiguration) {\n        super(kubernetesDeployerProperties, containerConfiguration);\n    }\n\n    protected abstract String getProbePath();\n\n    protected abstract Integer getPort();\n\n    protected abstract String getScheme();\n\n    protected abstract int getTimeout();\n\n    protected Probe create() {\n        HTTPGetActionBuilder httpGetActionBuilder = new HTTPGetActionBuilder()\n                .withPath(getProbePath())\n                .withNewPort(getPort())\n                .withScheme(getScheme());\n\n        List<HTTPHeader> httpHeaders = getHttpHeaders();\n\n        if (!httpHeaders.isEmpty()) {\n            httpGetActionBuilder.withHttpHeaders(httpHeaders);\n        }\n\n        return new ProbeBuilder()\n                .withHttpGet(httpGetActionBuilder.build())\n                .withTimeoutSeconds(getTimeout())\n                .withInitialDelaySeconds(getInitialDelay())\n                .withPeriodSeconds(getPeriod())\n                .withFailureThreshold(getFailure())\n                .withSuccessThreshold(getSuccess())\n                .build();\n\n    }\n\n    private List<HTTPHeader> getHttpHeaders() {\n        List<HTTPHeader> httpHeaders = new ArrayList<>();\n\n        HTTPHeader authenticationHeader = getAuthorizationHeader();\n\n        if (authenticationHeader != null) {\n            httpHeaders.add(authenticationHeader);\n        }\n\n        return httpHeaders;\n    }\n\n    private HTTPHeader getAuthorizationHeader() {\n        HTTPHeader httpHeader = null;\n\n        Secret probeCredentialsSecret = getContainerConfiguration().getProbeCredentialsSecret();\n\n        if (probeCredentialsSecret != null) {\n            Assert.isTrue(probeCredentialsSecret.getData().containsKey(PROBE_CREDENTIALS_SECRET_KEY_NAME),\n                    \"Secret does not contain a key by the name of \" + PROBE_CREDENTIALS_SECRET_KEY_NAME);\n\n            httpHeader = new HTTPHeader();\n            httpHeader.setName(AUTHORIZATION_HEADER_NAME);\n\n            // only Basic auth is supported currently\n            httpHeader.setValue(ProbeAuthenticationType.Basic.name() + \" \" +\n                    probeCredentialsSecret.getData().get(PROBE_CREDENTIALS_SECRET_KEY_NAME));\n        }\n\n        return httpHeader;\n    }\n\n    Integer getDefaultPort() {\n        return getContainerConfiguration().getExternalPort();\n    }\n\n    boolean useBoot1ProbePath() {\n        String bootMajorVersionProperty = KUBERNETES_DEPLOYER_PREFIX + \".bootMajorVersion\";\n        String bootMajorVersionValue = getDeploymentPropertyValue(bootMajorVersionProperty);\n\n        if (StringUtils.hasText(bootMajorVersionValue)) {\n            return Integer.parseInt(bootMajorVersionValue) == BOOT_1_MAJOR_VERSION;\n        }\n\n        return false;\n    }\n\n}\n"
  },
  {
    "path": "spring-cloud-deployer-kubernetes/src/main/java/org/springframework/cloud/deployer/spi/kubernetes/ImagePullPolicy.java",
    "content": "/*\n * Copyright 2015-2016 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.kubernetes;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport org.springframework.boot.context.properties.bind.Bindable;\nimport org.springframework.boot.context.properties.bind.Binder;\nimport org.springframework.boot.context.properties.source.MapConfigurationPropertySource;\n\n/**\n * ImagePullPolicy for containers inside a Kubernetes Pod, cf. https://kubernetes.io/docs/user-guide/images/\n *\n * @author Moritz Schulze\n */\npublic enum ImagePullPolicy {\n\n    Always,\n    IfNotPresent,\n    Never;\n\n    /**\n     * Tries to convert {@code name} to an {@link ImagePullPolicy} by ignoring case, dashes, underscores\n     * and so on in a relaxed fashion.\n     *\n     * @param name The name to convert to an {@link ImagePullPolicy}.\n     * @return The {@link ImagePullPolicy} for {@code name} or {@code null} if the conversion was not possible.\n     */\n    public static ImagePullPolicy relaxedValueOf(String name) {\n\t\t// 'value' is just a dummy key as you can't bind a single value to an enum\n\t\tMap<String, String> props = new HashMap<>();\n\t\tprops.put(\"value\", name);\n\t\tMapConfigurationPropertySource source = new MapConfigurationPropertySource(props);\n\t\tBinder binder = new Binder(source);\n\t\ttry {\n\t\t\treturn binder.bind(\"value\", Bindable.of(ImagePullPolicy.class)).get();\n\t\t} catch (Exception e) {\n\t\t\t// error means we couldn't bind, caller seem to handle null\n\t\t}\n        return null;\n    }\n\n}\n"
  },
  {
    "path": "spring-cloud-deployer-kubernetes/src/main/java/org/springframework/cloud/deployer/spi/kubernetes/KubernetesActuatorTemplate.java",
    "content": "/*\n * Copyright 2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.kubernetes;\n\nimport org.springframework.cloud.deployer.spi.app.AbstractActuatorTemplate;\nimport org.springframework.cloud.deployer.spi.app.AppAdmin;\nimport org.springframework.cloud.deployer.spi.app.AppDeployer;\nimport org.springframework.cloud.deployer.spi.app.AppInstanceStatus;\nimport org.springframework.web.client.RestTemplate;\n\n/**\n * @author David Turanski\n */\n\npublic class KubernetesActuatorTemplate extends AbstractActuatorTemplate {\n\n\tpublic KubernetesActuatorTemplate(RestTemplate restTemplate, AppDeployer appDeployer,\n\t\t\tAppAdmin appAdmin) {\n\t\tsuper(restTemplate, appDeployer, appAdmin);\n\t}\n\n\tprotected String actuatorUrlForInstance(AppInstanceStatus appInstanceStatus) {\n\t\treturn String.format(\"http://%s:%d/%s\", appInstanceStatus.getAttributes().get(\"pod.ip\"),\n\t\t\t\t\tInteger.valueOf(appInstanceStatus.getAttributes().get(\"actuator.port\")),\n\t\t\t\t\t\t\tappInstanceStatus.getAttributes().get(\"actuator.path\"));\n\t}\n}\n"
  },
  {
    "path": "spring-cloud-deployer-kubernetes/src/main/java/org/springframework/cloud/deployer/spi/kubernetes/KubernetesAppDeployer.java",
    "content": "/*\n * Copyright 2015-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.kubernetes;\n\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.LinkedList;\nimport java.util.List;\nimport java.util.Locale;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.function.Function;\n\nimport io.fabric8.kubernetes.api.model.Container;\nimport io.fabric8.kubernetes.api.model.ContainerBuilder;\nimport io.fabric8.kubernetes.api.model.PersistentVolumeClaim;\nimport io.fabric8.kubernetes.api.model.PersistentVolumeClaimBuilder;\nimport io.fabric8.kubernetes.api.model.PersistentVolumeClaimList;\nimport io.fabric8.kubernetes.api.model.Pod;\nimport io.fabric8.kubernetes.api.model.PodList;\nimport io.fabric8.kubernetes.api.model.PodSpec;\nimport io.fabric8.kubernetes.api.model.Quantity;\nimport io.fabric8.kubernetes.api.model.SecurityContext;\nimport io.fabric8.kubernetes.api.model.Service;\nimport io.fabric8.kubernetes.api.model.ServiceBuilder;\nimport io.fabric8.kubernetes.api.model.ServiceList;\nimport io.fabric8.kubernetes.api.model.ServicePort;\nimport io.fabric8.kubernetes.api.model.ServiceSpecBuilder;\nimport io.fabric8.kubernetes.api.model.StatusDetails;\nimport io.fabric8.kubernetes.api.model.VolumeBuilder;\nimport io.fabric8.kubernetes.api.model.VolumeMountBuilder;\nimport io.fabric8.kubernetes.api.model.apps.Deployment;\nimport io.fabric8.kubernetes.api.model.apps.DeploymentBuilder;\nimport io.fabric8.kubernetes.api.model.apps.DeploymentList;\nimport io.fabric8.kubernetes.api.model.apps.StatefulSet;\nimport io.fabric8.kubernetes.api.model.apps.StatefulSetBuilder;\nimport io.fabric8.kubernetes.api.model.apps.StatefulSetList;\nimport io.fabric8.kubernetes.api.model.apps.StatefulSetSpec;\nimport io.fabric8.kubernetes.api.model.apps.StatefulSetSpecBuilder;\nimport io.fabric8.kubernetes.client.KubernetesClient;\nimport io.fabric8.kubernetes.client.KubernetesClientException;\nimport io.fabric8.kubernetes.client.dsl.FilterWatchListDeletable;\nimport io.fabric8.kubernetes.client.dsl.NonDeletingOperation;\nimport io.fabric8.kubernetes.client.dsl.PodResource;\nimport io.fabric8.kubernetes.client.dsl.Resource;\nimport io.fabric8.kubernetes.client.dsl.RollableScalableResource;\nimport io.fabric8.kubernetes.client.dsl.ScalableResource;\nimport io.fabric8.kubernetes.client.dsl.ServiceResource;\nimport io.fabric8.kubernetes.client.dsl.TimeoutableScalable;\nimport io.fabric8.kubernetes.client.dsl.internal.apps.v1.DeploymentOperationsImpl;\nimport io.fabric8.kubernetes.client.dsl.internal.apps.v1.StatefulSetOperationsImpl;\nimport io.fabric8.kubernetes.client.dsl.internal.batch.v1.JobOperationsImpl;\nimport io.fabric8.kubernetes.client.dsl.internal.core.v1.ServiceOperationsImpl;\nimport org.apache.commons.logging.Log;\nimport org.apache.commons.logging.LogFactory;\n\nimport org.springframework.cloud.deployer.spi.app.AppDeployer;\nimport org.springframework.cloud.deployer.spi.app.AppInstanceStatus;\nimport org.springframework.cloud.deployer.spi.app.AppScaleRequest;\nimport org.springframework.cloud.deployer.spi.app.AppStatus;\nimport org.springframework.cloud.deployer.spi.app.DeploymentState;\nimport org.springframework.cloud.deployer.spi.core.AppDeploymentRequest;\nimport org.springframework.cloud.deployer.spi.core.RuntimeEnvironmentInfo;\nimport org.springframework.cloud.deployer.spi.kubernetes.support.ArgumentSanitizer;\nimport org.springframework.cloud.deployer.spi.kubernetes.support.PropertyParserUtils;\nimport org.springframework.util.CollectionUtils;\nimport org.springframework.util.StringUtils;\n\n/**\n * A deployer that targets Kubernetes.\n *\n * @author Florian Rosenberg\n * @author Thomas Risberg\n * @author Mark Fisher\n * @author Donovan Muller\n * @author David Turanski\n * @author Ilayaperumal Gopinathan\n * @author Chris Schaefer\n * @author Christian Tzolov\n * @author Omar Gonzalez\n * @author Chris Bono\n * @author Corneil du Plessis\n */\npublic class KubernetesAppDeployer extends AbstractKubernetesDeployer implements AppDeployer {\n\n    protected final Log logger = LogFactory.getLog(getClass().getName());\n\n    public KubernetesAppDeployer(KubernetesDeployerProperties properties, KubernetesClient client) {\n        this(properties, client, new DefaultContainerFactory(properties));\n    }\n\n    public KubernetesAppDeployer(KubernetesDeployerProperties properties, KubernetesClient client,\n                                 ContainerFactory containerFactory) {\n        this.properties = properties;\n        this.client = client;\n        this.containerFactory = containerFactory;\n        this.deploymentPropertiesResolver = new DeploymentPropertiesResolver(\n                KubernetesDeployerProperties.KUBERNETES_DEPLOYER_PROPERTIES_PREFIX, properties);\n    }\n\n    @Override\n    public String deploy(AppDeploymentRequest request) {\n        String appId = createDeploymentId(request);\n        if (logger.isDebugEnabled()) {\n            ArgumentSanitizer sanitizer = new ArgumentSanitizer();\n\t\t\tMap<String, String> sanitized = sanitizer.sanitizeProperties(request.getDeploymentProperties());\n            List<String> sanitizedCommandlineArguments = sanitizer.sanitizeArguments(request.getCommandlineArguments());\n            logger.debug(String.format(\"Deploying app: %s, request: commandlineArguments=%s, deploymentProperties=%s, definition=%s, resource=%s\", appId, sanitizedCommandlineArguments, sanitized, request.getDefinition(), request.getResource()));\n        }\n\n        try {\n            AppStatus status = status(appId);\n\n            if (!status.getState().equals(DeploymentState.unknown)) {\n                throw new IllegalStateException(String.format(\"App '%s' is already deployed\", appId));\n            }\n\n            String indexedProperty = request.getDeploymentProperties().get(INDEXED_PROPERTY_KEY);\n            boolean indexed = Boolean.parseBoolean(indexedProperty);\n            logPossibleDownloadResourceMessage(request.getResource());\n\n            createService(request);\n            if (indexed) {\n                createStatefulSet(request);\n            } else {\n                createDeployment(request);\n            }\n            return appId;\n        } catch (RuntimeException e) {\n            logger.error(e.getMessage(), e);\n            throw e;\n        }\n    }\n\n    @Override\n    public void undeploy(String appId) {\n        if (logger.isDebugEnabled()) {\n            logger.debug(String.format(\"Undeploying app: %s\", appId));\n        }\n        AppStatus status = status(appId);\n        if (status.getState().equals(DeploymentState.unknown)) {\n            // ensure objects for this appId are deleted in the event a previous deployment failed.\n            // allows for log inspection prior to making an undeploy request.\n            deleteAllObjects(appId);\n\n            throw new IllegalStateException(String.format(\"App '%s' is not deployed\", appId));\n        }\n\n        try {\n            deleteAllObjects(appId);\n        } catch (RuntimeException e) {\n            logger.error(e.getMessage(), e);\n            throw e;\n        }\n    }\n\n    @Override\n    public AppStatus status(String appId) {\n        Map<String, String> selector = new HashMap<>();\n        ServiceList services = client.services().withLabel(SPRING_APP_KEY, appId).list();\n        selector.put(SPRING_APP_KEY, appId);\n        PodList podList = client.pods().withLabels(selector).list();\n        if (logger.isDebugEnabled()) {\n            logger.debug(String.format(\"Building AppStatus for app: %s\", appId));\n            if (podList != null && podList.getItems() != null) {\n                logger.debug(String.format(\"Pods for appId %s: %d\", appId, podList.getItems().size()));\n                for (Pod pod : podList.getItems()) {\n                    logger.debug(String.format(\"Pod: %s\", pod.getMetadata().getName()));\n                }\n            }\n        }\n        AppStatus status = buildAppStatus(appId, podList, services);\n        if (logger.isDebugEnabled()) {\n            logger.debug(String.format(\"status:%s = %s\", appId, status.getDeploymentId()));\n            for (AppInstanceStatus instanceStatus : status.getInstances().values()) {\n                logger.debug(String.format(\"status:%s:%s:%s\", instanceStatus.getId(), instanceStatus.getState(), instanceStatus.getAttributes()));\n            }\n        }\n\n        return status;\n    }\n\n    @Override\n    public String getLog(String appId) {\n        Map<String, String> selector = new HashMap<>();\n        selector.put(SPRING_APP_KEY, appId);\n        PodList podList = client.pods().withLabels(selector).list();\n        StringBuilder logAppender = new StringBuilder();\n        for (Pod pod : podList.getItems()) {\n\n            if (pod.getSpec().getContainers().size() > 1) {\n                for (Container container : pod.getSpec().getContainers()) {\n                    if (container.getEnv().stream().anyMatch(envVar -> \"SPRING_CLOUD_APPLICATION_GUID\".equals(envVar.getName()))) {\n                        //find log for this container\n                        logAppender.append(this.client.pods()\n                                .withName(pod.getMetadata().getName())\n                                .inContainer(container.getName())\n                                .tailingLines(500).getLog());\n                        break;\n                    }\n\n                }\n            } else {\n                logAppender.append(this.client.pods().withName(pod.getMetadata().getName()).tailingLines(500).getLog());\n            }\n        }\n\n        return logAppender.toString();\n    }\n\n    @Override\n    public void scale(AppScaleRequest appScaleRequest) {\n        String deploymentId = appScaleRequest.getDeploymentId();\n        if (logger.isDebugEnabled()) {\n            logger.debug(String.format(\"Scale app: %s to: %s\", deploymentId, appScaleRequest.getCount()));\n        }\n\n\t\ttry {\n\t\t\tScalableResource<?> scalableResource = this.client.apps().deployments().withName(deploymentId);\n\t\t\tif (scalableResource.get() == null) {\n\t\t\t\tscalableResource = this.client.apps().statefulSets().withName(deploymentId);\n\t\t\t}\n\t\t\tif (scalableResource.get() == null) {\n\t\t\t\tthrow new IllegalStateException(String.format(\"App '%s' is not deployed\", deploymentId));\n\t\t\t}\n\t\t\tJobOperationsImpl jobOperations = new JobOperationsImpl(this.client);\n\t\t\tTimeoutableScalable<?> timeoutableScalable = scalableResource.withTimeoutInMillis(jobOperations.getRequestConfig().getScaleTimeout());\n\t\t\ttimeoutableScalable.scale(appScaleRequest.getCount());\n\t\t} catch (KubernetesClientException x) {\n\t\t\tlogger.debug(\"scale:exception:\" + x, x);\n\t\t\tthrow new IllegalStateException(x);\n\t\t}\n\n\n\t}\n\n    @Override\n    public RuntimeEnvironmentInfo environmentInfo() {\n        return super.createRuntimeEnvironmentInfo(AppDeployer.class, this.getClass());\n    }\n\n    private Deployment createDeployment(AppDeploymentRequest request) {\n\n        String appId = createDeploymentId(request);\n        if (logger.isDebugEnabled()) {\n            logger.debug(String.format(\"Creating Deployment: %s\", appId));\n        }\n\n        int replicas = getCountFromRequest(request);\n\n        Map<String, String> idMap = createIdMap(appId, request);\n\n        Map<String, String> kubernetesDeployerProperties = request.getDeploymentProperties();\n\n        Map<String, String> annotations = this.deploymentPropertiesResolver.getPodAnnotations(kubernetesDeployerProperties);\n        Map<String, String> deploymentLabels = this.deploymentPropertiesResolver.getDeploymentLabels(kubernetesDeployerProperties);\n\n        PodSpec podSpec = createPodSpec(request);\n\n        Deployment d = new DeploymentBuilder().withNewMetadata().withName(appId).withLabels(idMap)\n                .addToLabels(SPRING_MARKER_KEY, SPRING_MARKER_VALUE).addToLabels(deploymentLabels).endMetadata()\n                .withNewSpec().withNewSelector().addToMatchLabels(idMap).endSelector().withReplicas(replicas)\n                .withNewTemplate().withNewMetadata().withLabels(idMap).addToLabels(SPRING_MARKER_KEY, SPRING_MARKER_VALUE)\n                .addToLabels(deploymentLabels).withAnnotations(annotations).endMetadata().withSpec(podSpec).endTemplate()\n                .endSpec().build();\n\t\tDeploymentOperationsImpl deploymentOperations = new DeploymentOperationsImpl(this.client);\n\t\td = client.apps().deployments().inNamespace(deploymentOperations.getNamespace()).resource(d).create();\n        if (logger.isDebugEnabled()) {\n            logger.debug(\"created:\" + d.getFullResourceName() + \":\" + d.getStatus());\n        }\n        return d;\n    }\n\n    private int getCountFromRequest(AppDeploymentRequest request) {\n        String countProperty = request.getDeploymentProperties().get(COUNT_PROPERTY_KEY);\n        return (countProperty != null) ? Integer.parseInt(countProperty) : 1;\n    }\n\n    /**\n     * Create a StatefulSet\n     *\n     * @param request the {@link AppDeploymentRequest}\n     */\n    protected void createStatefulSet(AppDeploymentRequest request) {\n\n        String appId = createDeploymentId(request);\n\n        int externalPort = getExternalPort(request);\n\n        Map<String, String> idMap = createIdMap(appId, request);\n\n        int replicas = getCountFromRequest(request);\n\n        Map<String, String> kubernetesDeployerProperties = request.getDeploymentProperties();\n        if (logger.isDebugEnabled()) {\n            logger.debug(String.format(\"Creating StatefulSet: %s on %d with %d replicas\", appId, externalPort, replicas));\n        }\n\n        Map<String, Quantity> storageResource = Collections.singletonMap(\"storage\",\n                new Quantity(this.deploymentPropertiesResolver.getStatefulSetStorage(kubernetesDeployerProperties)));\n\n        String storageClassName = this.deploymentPropertiesResolver.getStatefulSetStorageClassName(kubernetesDeployerProperties);\n\n        String volumeClaimTemplateName = this.deploymentPropertiesResolver.getStatefulSetVolumeClaimTemplateName(kubernetesDeployerProperties);\n\n        volumeClaimTemplateName = StringUtils.hasText(volumeClaimTemplateName) ? volumeClaimTemplateName : appId;\n\n        PersistentVolumeClaimBuilder persistentVolumeClaimBuilder = new PersistentVolumeClaimBuilder().withNewSpec().\n                withStorageClassName(storageClassName).withAccessModes(Collections.singletonList(\"ReadWriteOnce\"))\n                .withNewResources().addToLimits(storageResource).addToRequests(storageResource).endResources()\n                .endSpec().withNewMetadata().withName(volumeClaimTemplateName).withLabels(idMap)\n                .addToLabels(SPRING_MARKER_KEY, SPRING_MARKER_VALUE).endMetadata();\n\n        PodSpec podSpec = createPodSpec(request);\n\n        podSpec.getVolumes().add(new VolumeBuilder().withName(\"config\").withNewEmptyDir().endEmptyDir().build());\n\n        podSpec.getContainers().get(0).getVolumeMounts()\n                .add(new VolumeMountBuilder().withName(\"config\").withMountPath(\"/config\").build());\n\n        String statefulSetInitContainerImageName = this.deploymentPropertiesResolver.getStatefulSetInitContainerImageName(kubernetesDeployerProperties);\n\n        podSpec.getInitContainers().add(createStatefulSetInitContainer(podSpec, statefulSetInitContainerImageName));\n\n        Map<String, String> deploymentLabels = this.deploymentPropertiesResolver.getDeploymentLabels(request.getDeploymentProperties());\n        Map<String, String> annotations = this.deploymentPropertiesResolver.getPodAnnotations(kubernetesDeployerProperties);\n\n        StatefulSetSpec spec = new StatefulSetSpecBuilder().withNewSelector().addToMatchLabels(idMap)\n                .addToMatchLabels(SPRING_MARKER_KEY, SPRING_MARKER_VALUE).endSelector()\n                .withVolumeClaimTemplates(persistentVolumeClaimBuilder.build()).withServiceName(appId)\n                .withPodManagementPolicy(\"Parallel\").withReplicas(replicas).withNewTemplate().withNewMetadata()\n                .withLabels(idMap).addToLabels(SPRING_MARKER_KEY, SPRING_MARKER_VALUE).addToLabels(deploymentLabels)\n                .addToAnnotations(annotations).endMetadata().withSpec(podSpec).endTemplate().build();\n\n        StatefulSet statefulSet = new StatefulSetBuilder().withNewMetadata().withName(appId).withLabels(idMap)\n                .addToLabels(SPRING_MARKER_KEY, SPRING_MARKER_VALUE).addToLabels(deploymentLabels).endMetadata().withSpec(spec).build();\n\t\tStatefulSetOperationsImpl statefulSetOperations = new StatefulSetOperationsImpl(this.client);\n        statefulSet = client.apps().statefulSets().inNamespace(statefulSetOperations.getNamespace()).resource(statefulSet).create();\n        if (logger.isDebugEnabled()) {\n            logger.debug(\"created:\" + statefulSet.getFullResourceName() + \":\" + statefulSet.getStatus());\n        }\n    }\n\n    protected void createService(AppDeploymentRequest request) {\n\n        String appId = createDeploymentId(request);\n\n        int externalPort = getExternalPort(request);\n        if (logger.isDebugEnabled()) {\n            ArgumentSanitizer sanitizer = new ArgumentSanitizer();\n            Map<String, String> sanitized = sanitizer.sanitizeProperties(request.getDeploymentProperties());\n            logger.debug(String.format(\"Creating Service: %s on %d using %s\", appId, externalPort, sanitized));\n        }\n\n        Map<String, String> idMap = createIdMap(appId, request);\n\n        ServiceSpecBuilder spec = new ServiceSpecBuilder();\n        boolean isCreateLoadBalancer = false;\n        String createLoadBalancer = PropertyParserUtils.getDeploymentPropertyValue(request.getDeploymentProperties(),\n                \"spring.cloud.deployer.kubernetes.createLoadBalancer\");\n        String createNodePort = PropertyParserUtils.getDeploymentPropertyValue(request.getDeploymentProperties(),\n                \"spring.cloud.deployer.kubernetes.createNodePort\");\n        String additionalServicePorts = PropertyParserUtils.getDeploymentPropertyValue(request.getDeploymentProperties(),\n                \"spring.cloud.deployer.kubernetes.servicePorts\");\n\n        if (createLoadBalancer != null && createNodePort != null) {\n            throw new IllegalArgumentException(\"Cannot create NodePort and LoadBalancer at the same time.\");\n        }\n\n        if (createLoadBalancer == null) {\n            isCreateLoadBalancer = properties.isCreateLoadBalancer();\n        } else {\n\t\t\tif (\"true\".equalsIgnoreCase(createLoadBalancer)) {\n                isCreateLoadBalancer = true;\n            }\n        }\n\n        if (isCreateLoadBalancer) {\n            spec.withType(\"LoadBalancer\");\n        }\n\n        ServicePort servicePort = new ServicePort();\n        servicePort.setPort(externalPort);\n        servicePort.setName(\"port-\" + externalPort);\n\n        if (createNodePort != null) {\n            spec.withType(\"NodePort\");\n\t\t\tif (!\"true\".equalsIgnoreCase(createNodePort)) {\n                try {\n                    Integer nodePort = Integer.valueOf(createNodePort);\n                    servicePort.setNodePort(nodePort);\n                } catch (NumberFormatException e) {\n                    throw new IllegalArgumentException(\n                            String.format(\"Invalid value: %s: provided port is not valid.\", createNodePort));\n                }\n            }\n        }\n\n        Set<ServicePort> servicePorts = new HashSet<>();\n        servicePorts.add(servicePort);\n\n        if (StringUtils.hasText(additionalServicePorts)) {\n            servicePorts.addAll(addAdditionalServicePorts(additionalServicePorts));\n        }\n\n        spec.addAllToPorts(servicePorts);\n\n        Map<String, String> annotations = this.deploymentPropertiesResolver.getServiceAnnotations(request.getDeploymentProperties());\n\n        String serviceName = getServiceName(request, appId);\n\n        // if called from skipper, use unique selectors to maintain connectivity\n        // between service and pods that are being brought up/down\n        if (request.getDeploymentProperties().containsKey(APP_NAME_PROPERTY_KEY)) {\n            spec.withSelector(Collections.singletonMap(APP_NAME_KEY,\n                    request.getDeploymentProperties().get(APP_NAME_PROPERTY_KEY)));\n\n            String groupId = request.getDeploymentProperties().get(AppDeployer.GROUP_PROPERTY_KEY);\n\n            if (groupId != null) {\n                spec.addToSelector(SPRING_GROUP_KEY, groupId);\n            }\n        } else {\n            spec.withSelector(idMap);\n        }\n\n\t\tService service = new ServiceBuilder().withNewMetadata().withName(serviceName)\n\t\t\t.withLabels(idMap).withAnnotations(annotations).addToLabels(SPRING_MARKER_KEY, SPRING_MARKER_VALUE)\n\t\t\t.endMetadata().withSpec(spec.build()).build();\n\t\tServiceOperationsImpl serviceOperations = new ServiceOperationsImpl(client);\n\t\tservice = client.services().inNamespace(serviceOperations.getNamespace()).resource(service).createOr(\n\t\t\tnew Function<NonDeletingOperation<Service>, Service>() {\n\t\t\t@Override\n\t\t\tpublic Service apply(NonDeletingOperation<Service> serviceNonDeletingOperation) {\n\t\t\t\treturn serviceNonDeletingOperation.update();\n\n\t\t\t}\n\t\t});\n        if (logger.isDebugEnabled()) {\n            logger.debug(\"created:\" + service.getFullResourceName() + \":\" + service.getStatus());\n        }\n    }\n\n    // logic to support using un-versioned service names when called from skipper\n    private String getServiceName(AppDeploymentRequest request, String appId) {\n        String appName = request.getDeploymentProperties().get(APP_NAME_PROPERTY_KEY);\n\n        // if we have an un-versioned app name from skipper\n        if (StringUtils.hasText(appName)) {\n            String serviceName = formatServiceName(request, appName);\n\n            // need to check if a versioned service exists to maintain backwards compat..\n            // version number itself isn't checked on as it could be different if create or upgrade\n            // which we don't know at runtime....\n            List<Service> services = client.services().withLabel(SPRING_DEPLOYMENT_KEY).list().getItems();\n\n            for (Service service : services) {\n                String serviceMetadataName = service.getMetadata().getName();\n\n                if (serviceMetadataName.startsWith(serviceName + \"-v\")) {\n                    return appId;\n                }\n            }\n\n            return serviceName;\n        }\n\n        // no un-versioned app name provided, maybe not called from skipper, return whatever is in appId\n        return appId;\n    }\n\n    private String formatServiceName(AppDeploymentRequest request, String appName) {\n        String groupId = request.getDeploymentProperties().get(AppDeployer.GROUP_PROPERTY_KEY);\n\n        String serviceName = groupId == null ? String.format(\"%s\", appName)\n                : String.format(\"%s-%s\", groupId, appName);\n\n        return serviceName.replace('.', '-').toLowerCase(Locale.ROOT);\n    }\n\n    private Set<ServicePort> addAdditionalServicePorts(String additionalServicePorts) {\n        Set<ServicePort> ports = new HashSet<>();\n\n        String[] servicePorts = additionalServicePorts.split(\",\");\n        for (String servicePort : servicePorts) {\n            Integer port = Integer.parseInt(servicePort.trim());\n            ServicePort extraServicePort = new ServicePort();\n            extraServicePort.setPort(port);\n            extraServicePort.setName(\"port-\" + port);\n\n            ports.add(extraServicePort);\n        }\n\n        return ports;\n    }\n\n    /**\n     * For StatefulSets, create an init container to parse ${HOSTNAME} to get the `instance.index` and write it to\n     * config/application.properties on a shared volume so the main container has it. Using the legacy annotation\n     * configuration since the current client version does not directly support InitContainers.\n     * <p>\n     * Since 1.8 the annotation method has been removed, and the initContainer API is supported since 1.6\n     *\n\t * @param podSpec   the current pod spec the container is being added to\n     * @param imageName the image name to use in the init container\n     * @return a container definition with the  aforementioned configuration\n     */\n    private Container createStatefulSetInitContainer(PodSpec podSpec, String imageName) {\n        List<String> command = new LinkedList<>();\n\n        String commandString = String\n                .format(\"%s && %s\", setIndexProperty(\"INSTANCE_INDEX\"), setIndexProperty(\"spring.cloud.stream.instanceIndex\"));\n\n\t\tcommand.add(\"sh\");\n        command.add(\"-c\");\n        command.add(commandString);\n\n        ContainerBuilder containerBuilder = new ContainerBuilder().withName(\"index-provider\")\n                .withImage(imageName)\n                .withImagePullPolicy(\"IfNotPresent\")\n                .withCommand(command)\n                .withVolumeMounts(new VolumeMountBuilder().withName(\"config\").withMountPath(\"/config\").build());\n\n\t\tif (!CollectionUtils.isEmpty(podSpec.getContainers())) {\n\t\t\tSecurityContext securityContext = podSpec.getContainers().get(0).getSecurityContext();\n\t\t\tif (securityContext != null) {\n\t\t\t\tcontainerBuilder.withSecurityContext(securityContext);\n\t\t\t}\n\t\t}\n\n\t\treturn containerBuilder.build();\n    }\n\n    private String setIndexProperty(String name) {\n        return String\n\t\t\t.format(\"echo %s=\\\"$(expr $HOSTNAME | grep -o \\\"[[:digit:]]*$\\\")\\\" >> \" +\n                        \"/config/application.properties\", name);\n    }\n\n    private void deleteAllObjects(String appIdToDelete) {\n        Map<String, String> labels = Collections.singletonMap(SPRING_APP_KEY, appIdToDelete);\n        if (logger.isDebugEnabled()) {\n            logger.debug(String.format(\"deleteAllObjects:%s:%s\", appIdToDelete, labels));\n        }\n        // waitForLoadBalancerReady(labels); // Not negative effect in not waiting for loadbalancer.\n        deleteService(labels);\n        deleteDeployment(labels);\n        deleteStatefulSet(labels);\n        deletePod(labels);\n        deletePvc(labels);\n    }\n\n    private void deleteService(Map<String, String> labels) {\n\t\tFilterWatchListDeletable<Service, ServiceList, ServiceResource<Service>> servicesToDelete =\n                client.services().withLabels(labels);\n\n        if (servicesToDelete != null && servicesToDelete.list().getItems() != null) {\n\t\t\tList<StatusDetails> servicesDeleted = servicesToDelete.delete();\n\t\t\tif (logger.isDebugEnabled()) {\n\t\t\t\tlogger.debug(String.format(\"Service deleted for: %s - %s\", labels, servicesDeleted));\n\t\t\t}\n        }\n    }\n\n    private void deleteDeployment(Map<String, String> labels) {\n\t\tFilterWatchListDeletable<Deployment, DeploymentList, RollableScalableResource<Deployment>> deploymentsToDelete =\n                client.apps().deployments().withLabels(labels);\n\n        if (deploymentsToDelete != null && deploymentsToDelete.list().getItems() != null) {\n\t\t\tList<StatusDetails> deploymentsDeleted = deploymentsToDelete.delete();\n\t\t\tif (logger.isDebugEnabled()) {\n\t\t\t\tlogger.debug(String.format(\"Deployment deleted for: %s - %s\", labels, deploymentsDeleted));\n\t\t\t}\n        }\n    }\n\n    private void deleteStatefulSet(Map<String, String> labels) {\n\t\tFilterWatchListDeletable<StatefulSet, StatefulSetList, RollableScalableResource<StatefulSet>> ssToDelete =\n                client.apps().statefulSets().withLabels(labels);\n\n        if (ssToDelete != null && ssToDelete.list().getItems() != null) {\n\t\t\tList<StatusDetails> ssDeleted = ssToDelete.delete();\n            if (logger.isDebugEnabled()) {\n                logger.debug(String.format(\"StatefulSet deleted for: %s - %s\", labels, ssDeleted));\n            }\n        }\n    }\n\n    private void deletePod(Map<String, String> labels) {\n\t\tFilterWatchListDeletable<Pod, PodList, PodResource> podsToDelete = client.pods()\n                .withLabels(labels);\n\n        if (podsToDelete != null && podsToDelete.list().getItems() != null) {\n\t\t\tList<StatusDetails> podsDeleted = podsToDelete.delete();\n\t\t\tif (logger.isDebugEnabled()) {\n\t\t\t\tlogger.debug(String.format(\"Pod deleted for: %s - %s\", labels, podsDeleted));\n\t\t\t}\n        }\n    }\n\n    private void deletePvc(Map<String, String> labels) {\n\t\tFilterWatchListDeletable<PersistentVolumeClaim, PersistentVolumeClaimList, Resource<PersistentVolumeClaim>> pvcsToDelete = client.persistentVolumeClaims()\n                .withLabels(labels);\n\n        if (pvcsToDelete != null && pvcsToDelete.list().getItems() != null) {\n\t\t\tList<StatusDetails> pvcsDeleted = pvcsToDelete.delete();\n\t\t\tif (logger.isDebugEnabled()) {\n\t\t\t\tlogger.debug(String.format(\"PVC deleted for: %s - %s\", labels, pvcsDeleted));\n\t\t\t}\n        }\n    }\n\n    private void waitForLoadBalancerReady(Map<String, String> labels) {\n        List<Service> services = client.services().withLabels(labels).list().getItems();\n\n        if (!services.isEmpty()) {\n            Service svc = services.get(0);\n            if (svc != null && \"LoadBalancer\".equals(svc.getSpec().getType())) {\n                int tries = 0;\n                int maxWait = properties.getMinutesToWaitForLoadBalancer() * 6; // we check 6 times per minute\n                while (tries++ < maxWait) {\n                    if (svc.getStatus() != null && svc.getStatus().getLoadBalancer() != null\n                            && svc.getStatus().getLoadBalancer().getIngress() != null && svc.getStatus()\n                            .getLoadBalancer().getIngress().isEmpty()) {\n                        if (tries % 6 == 0) {\n                            logger.warn(\"Waiting for LoadBalancer to complete before deleting it ...\");\n                        }\n                        if (logger.isDebugEnabled()) {\n                            logger.debug(String.format(\"Waiting for LoadBalancer, try %d\", tries));\n                        }\n                        try {\n                            Thread.sleep(10000L);\n                        } catch (InterruptedException e) {\n                        }\n                        svc = client.services().withLabels(labels).list().getItems().get(0);\n                    } else {\n                        break;\n                    }\n                }\n                if (logger.isDebugEnabled()) {\n                    logger.debug(String.format(\"LoadBalancer Ingress: %s\",\n                            svc.getStatus().getLoadBalancer().getIngress().toString()));\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "spring-cloud-deployer-kubernetes/src/main/java/org/springframework/cloud/deployer/spi/kubernetes/KubernetesAppInstanceStatus.java",
    "content": "/*\n * Copyright 2015-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.kubernetes;\n\nimport java.nio.file.Paths;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.concurrent.ConcurrentHashMap;\n\nimport io.fabric8.kubernetes.api.model.Container;\nimport io.fabric8.kubernetes.api.model.ContainerStatus;\nimport io.fabric8.kubernetes.api.model.IntOrString;\nimport io.fabric8.kubernetes.api.model.Pod;\nimport io.fabric8.kubernetes.api.model.Service;\nimport io.fabric8.kubernetes.api.model.ServicePort;\nimport org.apache.commons.logging.Log;\nimport org.apache.commons.logging.LogFactory;\n\nimport org.springframework.cloud.deployer.spi.app.AppInstanceStatus;\nimport org.springframework.cloud.deployer.spi.app.DeploymentState;\n\n/**\n * Represents the status of a module.\n *\n * @author Florian Rosenberg\n * @author Thomas Risberg\n * @author David Turanski\n * @author Chris Schaefer\n */\npublic class KubernetesAppInstanceStatus implements AppInstanceStatus {\n\n    private static Log logger = LogFactory.getLog(KubernetesAppInstanceStatus.class);\n\n    private final Pod pod;\n\n    private final Service service;\n\n    private final KubernetesDeployerProperties properties;\n\n    private ContainerStatus containerStatus;\n\n    private RunningPhaseDeploymentStateResolver runningPhaseDeploymentStateResolver;\n\n    @Deprecated\n    public KubernetesAppInstanceStatus(Pod pod, Service service, KubernetesDeployerProperties properties) {\n        this.pod = pod;\n        this.service = service;\n        this.properties = properties;\n        // we assume one container per pod\n        if (pod != null && pod.getStatus().getContainerStatuses().size() == 1) {\n            this.containerStatus = pod.getStatus().getContainerStatuses().get(0);\n        } else {\n            this.containerStatus = null;\n        }\n        this.runningPhaseDeploymentStateResolver = new DefaultRunningPhaseDeploymentStateResolver(properties);\n    }\n\n    public KubernetesAppInstanceStatus(Pod pod, Service service, KubernetesDeployerProperties properties,\n                                       ContainerStatus containerStatus) {\n        this.pod = pod;\n        this.service = service;\n        this.properties = properties;\n        this.containerStatus = containerStatus;\n        this.runningPhaseDeploymentStateResolver = new DefaultRunningPhaseDeploymentStateResolver(properties);\n    }\n\n    /**\n     * Override the default {@link RunningPhaseDeploymentStateResolver} implementation.\n     *\n     * @param runningPhaseDeploymentStateResolver the\n     *                                            {@link RunningPhaseDeploymentStateResolver} to use\n     */\n    public void setRunningPhaseDeploymentStateResolver(\n            RunningPhaseDeploymentStateResolver runningPhaseDeploymentStateResolver) {\n        this.runningPhaseDeploymentStateResolver = runningPhaseDeploymentStateResolver;\n    }\n\n    @Override\n    public String getId() {\n        return pod == null ? \"N/A\" : pod.getMetadata().getName();\n    }\n\n    @Override\n    public DeploymentState getState() {\n        return pod != null && containerStatus != null ? mapState() : DeploymentState.unknown;\n    }\n\n    /**\n     * Maps Kubernetes phases/states onto Spring Cloud Deployer states\n     */\n    private DeploymentState mapState() {\n        logger.debug(String.format(\"%s - Phase [ %s ]\", pod.getMetadata().getName(), pod.getStatus().getPhase()));\n        logger.debug(String.format(\"%s - ContainerStatus [ %s ]\", pod.getMetadata().getName(), containerStatus));\n        switch (pod.getStatus().getPhase()) {\n\n            case \"Pending\":\n                return DeploymentState.deploying;\n\n            // We only report a module as running if the container is also ready to service requests.\n            // We also implement the Readiness check as part of the container to ensure ready means\n            // that the module is up and running and not only that the JVM has been created and the\n            // Spring module is still starting up\n            case \"Running\":\n                // we assume we only have one container\n                return runningPhaseDeploymentStateResolver.resolve(containerStatus);\n            case \"Failed\":\n                return DeploymentState.failed;\n\n            case \"Unknown\":\n                return DeploymentState.unknown;\n\n            default:\n                return DeploymentState.unknown;\n        }\n    }\n\n    @Override\n    public Map<String, String> getAttributes() {\n\n        ConcurrentHashMap<String, String> result = new ConcurrentHashMap<>();\n\n        if (pod != null) {\n            result.put(\"pod.name\", pod.getMetadata().getName());\n            result.put(\"pod.startTime\", nullSafe(pod.getStatus().getStartTime()));\n            result.put(\"pod.ip\", nullSafe(pod.getStatus().getPodIP()));\n            result.put(\"actuator.path\", determineActuatorPathFromLivenessProbe(pod));\n            result.put(\"actuator.port\", determineActuatorPortFromLivenessProbe(pod, result.get(\"actuator.path\")));\n            result.put(\"host.ip\", nullSafe(pod.getStatus().getHostIP()));\n            result.put(\"phase\", nullSafe(pod.getStatus().getPhase()));\n            result.put(AbstractKubernetesDeployer.SPRING_APP_KEY.replace('-', '.'),\n                    pod.getMetadata().getLabels().get(AbstractKubernetesDeployer.SPRING_APP_KEY));\n            result.put(AbstractKubernetesDeployer.SPRING_DEPLOYMENT_KEY.replace('-', '.'),\n                    pod.getMetadata().getLabels().get(AbstractKubernetesDeployer.SPRING_DEPLOYMENT_KEY));\n            result.put(\"guid\", pod.getMetadata().getUid());\n        } else {\n            logger.debug(\"getAttributes:no pod\");\n        }\n        if (service != null) {\n            result.put(\"service.name\", service.getMetadata().getName());\n            if (\"LoadBalancer\".equals(service.getSpec().getType())) {\n                if (service.getStatus() != null && service.getStatus().getLoadBalancer() != null\n                        && service.getStatus().getLoadBalancer().getIngress() != null && !service.getStatus()\n                        .getLoadBalancer().getIngress().isEmpty()) {\n                    String externalIp = service.getStatus().getLoadBalancer().getIngress().get(0).getIp();\n                    if (externalIp == null) {\n                        externalIp = service.getStatus().getLoadBalancer().getIngress().get(0).getHostname();\n                    }\n                    result.put(\"service.external.ip\", externalIp);\n                    List<ServicePort> ports = service.getSpec().getPorts();\n                    int port = 0;\n                    if (ports != null && ports.size() > 0) {\n                        port = ports.get(0).getPort();\n                        result.put(\"service.external.port\", String.valueOf(port));\n                    }\n                    if (externalIp != null) {\n                        result.put(\"url\", \"http://\" + externalIp + (port > 0 && port != 80 ? \":\" + port : \"\"));\n                    }\n\n                }\n            }\n        } else {\n            logger.debug(\"getAttributes:no service\");\n        }\n        if (containerStatus != null) {\n            result.put(\"container.restartCount\", \"\" + containerStatus.getRestartCount());\n            if (containerStatus.getLastState() != null && containerStatus.getLastState().getTerminated() != null) {\n                result.put(\"container.lastState.terminated.exitCode\",\n                        \"\" + containerStatus.getLastState().getTerminated().getExitCode());\n                result.put(\"container.lastState.terminated.reason\",\n                        containerStatus.getLastState().getTerminated().getReason());\n            }\n            if (containerStatus.getState() != null && containerStatus.getState().getTerminated() != null) {\n                result.put(\"container.state.terminated.exitCode\",\n                        \"\" + containerStatus.getState().getTerminated().getExitCode());\n                result.put(\"container.state.terminated.reason\", containerStatus.getState().getTerminated().getReason());\n            }\n        } else {\n            logger.debug(\"getAttributes:no containerStatus\");\n        }\n        if (logger.isDebugEnabled()) {\n            logger.debug(\"getAttributes:\" + result);\n        }\n        return result;\n    }\n\n    private String nullSafe(String value) {\n        return value == null ? \"\" : value;\n    }\n\n    private String determineActuatorPathFromLivenessProbe(Pod pod) {\n        return pod.getSpec().getContainers().stream()\n                .filter((Container container) -> container.getLivenessProbe() != null &&\n                        container.getLivenessProbe().getHttpGet() != null)\n                .findFirst()\n                .map(container ->\n                        Paths.get(container.getLivenessProbe().getHttpGet().getPath()).getParent().toString())\n                .orElse(\"/actuator\");\n    }\n\n    private String determineActuatorPortFromLivenessProbe(Pod pod, String path) {\n        IntOrString intOrString = pod.getSpec().getContainers().stream()\n                .filter(container -> container.getLivenessProbe() != null &&\n                        container.getLivenessProbe().getHttpGet() != null &&\n                        container.getLivenessProbe().getHttpGet().getPath().equals(path))\n                .findFirst()\n                .map(container -> container.getLivenessProbe().getHttpGet().getPort())\n                .orElse(new IntOrString(8080));\n        return intOrString.getIntVal() != null ? String.valueOf(intOrString.getIntVal()) : intOrString.getStrVal();\n    }\n}\n"
  },
  {
    "path": "spring-cloud-deployer-kubernetes/src/main/java/org/springframework/cloud/deployer/spi/kubernetes/KubernetesAutoConfiguration.java",
    "content": "/*\n * Copyright 2015-2020 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.kubernetes;\n\nimport io.fabric8.kubernetes.client.KubernetesClient;\n\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.boot.autoconfigure.AutoConfigureOrder;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;\nimport org.springframework.boot.context.properties.EnableConfigurationProperties;\nimport org.springframework.cloud.deployer.spi.app.ActuatorOperations;\nimport org.springframework.cloud.deployer.spi.app.AppDeployer;\nimport org.springframework.cloud.deployer.spi.task.TaskLauncher;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.core.Ordered;\nimport org.springframework.web.client.RestTemplate;\n\n/**\n * Spring Bean configuration for the {@link KubernetesAppDeployer}.\n *\n * @author Florian Rosenberg\n * @author Thomas Risberg\n * @author Ilayaperumal Gopinathan\n * @author Chris Schaefer\n */\n@Configuration\n@EnableConfigurationProperties({KubernetesDeployerProperties.class, KubernetesTaskLauncherProperties.class})\n@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)\npublic class KubernetesAutoConfiguration {\n\t\n\t@Autowired\n\tprivate KubernetesDeployerProperties deployerProperties;\n\n\t@Autowired\n\tprivate KubernetesTaskLauncherProperties taskLauncherProperties;\n\n\t@Bean\n\t@ConditionalOnMissingBean(AppDeployer.class)\n\tpublic AppDeployer appDeployer(KubernetesClient kubernetesClient,\n\t                               ContainerFactory containerFactory) {\n\t\treturn new KubernetesAppDeployer(deployerProperties, kubernetesClient, containerFactory);\n\t}\n\n\t@Bean\n\t@ConditionalOnMissingBean(TaskLauncher.class)\n\tpublic TaskLauncher taskDeployer(KubernetesClient kubernetesClient,\n\t                                 ContainerFactory containerFactory) {\n\t\treturn new KubernetesTaskLauncher(deployerProperties, taskLauncherProperties, kubernetesClient, containerFactory);\n\t}\n\n\t@Bean\n\t@ConditionalOnMissingBean(KubernetesClient.class)\n\tpublic KubernetesClient kubernetesClient() {\n\t\treturn KubernetesClientFactory.getKubernetesClient(this.deployerProperties);\n\t}\n\n\t@Bean\n\tpublic ContainerFactory containerFactory() {\n\t\treturn new DefaultContainerFactory(deployerProperties);\n\t}\n\n\t@Bean\n\t@ConditionalOnMissingBean(ActuatorOperations.class)\n\tActuatorOperations actuatorOperations(RestTemplate actuatorRestTemplate, AppDeployer appDeployer,\n\t\t\tKubernetesDeployerProperties properties) {\n\t\treturn new KubernetesActuatorTemplate(actuatorRestTemplate, appDeployer, properties.getAppAdmin());\n\t}\n\n\t@Bean\n\t@ConditionalOnMissingBean\n\tRestTemplate actuatorRestTemplate() {\n\t\t//TODO: Configure security\n\t\treturn new RestTemplate();\n\t}\n\n}\n"
  },
  {
    "path": "spring-cloud-deployer-kubernetes/src/main/java/org/springframework/cloud/deployer/spi/kubernetes/KubernetesClientFactory.java",
    "content": "/*\n * Copyright 2018-2019 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.kubernetes;\n\nimport io.fabric8.kubernetes.client.Config;\nimport io.fabric8.kubernetes.client.KubernetesClient;\nimport io.fabric8.kubernetes.client.KubernetesClientBuilder;\n\n/**\n * The class responsible for creating Kubernetes Client based on the deployer properties.\n *\n * @author Ilayaperumal Gopinathan\n * @author Chris Schaefer\n */\npublic class KubernetesClientFactory {\n\n\tpublic static KubernetesClient getKubernetesClient(KubernetesDeployerProperties kubernetesDeployerProperties) {\n\t\tConfig config = kubernetesDeployerProperties.getFabric8();\n\n\t\t// use any fabric8 auto-detected properties, only set namespace from deployer properties if not null\n\t\tif (kubernetesDeployerProperties.getNamespace() != null) {\n\t\t\tconfig.setNamespace(kubernetesDeployerProperties.getNamespace());\n\t\t}\n\t\tKubernetesClientBuilder builder = new KubernetesClientBuilder();\n\t\treturn builder.withConfig(config).build();\n\t}\n}\n"
  },
  {
    "path": "spring-cloud-deployer-kubernetes/src/main/java/org/springframework/cloud/deployer/spi/kubernetes/KubernetesDeployerProperties.java",
    "content": "/*\n * Copyright 2015-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.kubernetes;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport com.fasterxml.jackson.annotation.JsonAlias;\nimport com.fasterxml.jackson.annotation.JsonProperty;\nimport io.fabric8.kubernetes.api.model.NodeAffinity;\nimport io.fabric8.kubernetes.api.model.PodAffinity;\nimport io.fabric8.kubernetes.api.model.PodAntiAffinity;\nimport io.fabric8.kubernetes.api.model.Volume;\nimport io.fabric8.kubernetes.api.model.VolumeMount;\nimport io.fabric8.kubernetes.client.Config;\n\nimport org.springframework.boot.context.properties.ConfigurationProperties;\nimport org.springframework.boot.context.properties.NestedConfigurationProperty;\nimport org.springframework.cloud.deployer.spi.app.AppAdmin;\n\n/**\n * @author Florian Rosenberg\n * @author Thomas Risberg\n * @author Donovan Muller\n * @author Ilayaperumal Gopinathan\n * @author Leonardo Diniz\n * @author Chris Schaefer\n * @author David Turanski\n * @author Enrique Medina Montenegro\n * @author Chris Bono\n * @author Corneil du Plessis\n */\n@ConfigurationProperties(prefix = KubernetesDeployerProperties.KUBERNETES_DEPLOYER_PROPERTIES_PREFIX)\npublic class KubernetesDeployerProperties {\n\n    static final String KUBERNETES_DEPLOYER_PROPERTIES_PREFIX = \"spring.cloud.deployer.kubernetes\";\n\n    /**\n     * Constants for app deployment properties that don't have a deployer level default\n     * property.\n     */\n    static final String KUBERNETES_DEPLOYMENT_NODE_SELECTOR = KUBERNETES_DEPLOYER_PROPERTIES_PREFIX + \".nodeSelector\";\n\n    /**\n     * The maximum concurrent tasks allowed for this platform instance.\n     */\n    private int maximumConcurrentTasks = 20;\n\n\t/**\n\t * The number of seconds to wait before terminating the container.\n\t */\n\tprivate Long terminationGracePeriodSeconds;\n\n    @NestedConfigurationProperty\n    private Config fabric8 = Config.autoConfigure(null);\n\n\n    public Config getFabric8() {\n        return this.fabric8;\n    }\n\n    public void setFabric8(Config fabric8) {\n        this.fabric8 = fabric8;\n    }\n\n    /**\n     * Encapsulates resources for Kubernetes Container resource limits\n     */\n    public static class LimitsResources {\n\n        /**\n         * Container resource cpu limit.\n         */\n        private String cpu;\n\n        /**\n         * Container resource memory limit.\n         */\n        private String memory;\n        /**\n         * Container resource ephemeral-storage limit.\n         */\n        private String ephemeralStorage;\n\n        /**\n         * Container resource hugepages-2Mi limit.\n         */\n        private String hugepages2Mi;\n\n        /**\n         * Container resource hugepages-1Gi limit.\n         */\n        private String hugepages1Gi;\n\n        /**\n         * Container GPU vendor name for limit\n         * If gpuVendor=nvidia.com/gpu and gpuCount=2 then the following will be used\n         * {@code\n         * limits:\n         *   nvidia.com/gpu: 2\n         * }\n         */\n        private String gpuVendor;\n\n        /**\n         * Container GPU count for limit.\n         */\n        private String gpuCount;\n\n        public LimitsResources() {\n        }\n        /**\n         * New all args constructor\n         * @param cpu Container limit for cpu resource\n         * @param memory Container limit for memory resource\n         * @param ephemeralStorage Container limit for ephemetal storage\n         * @param hugepages2Mi Container limit for 2M huge pages\n         * @param hugepages1Gi Container limit for 1G huge pages\n         * @param gpuVendor The complete limit entry name for gpu vendor.\n         * @param gpuCount\n         */\n        public LimitsResources(String cpu, String memory, String ephemeralStorage, String hugepages2Mi, String hugepages1Gi, String gpuVendor, String gpuCount) {\n            this.cpu = cpu;\n            this.memory = memory;\n            this.ephemeralStorage = ephemeralStorage;\n            this.hugepages2Mi = hugepages2Mi;\n            this.hugepages1Gi = hugepages1Gi;\n            this.gpuVendor = gpuVendor;\n            this.gpuCount = gpuCount;\n        }\n\n        public String getCpu() {\n            return cpu;\n        }\n\n        public void setCpu(String cpu) {\n            this.cpu = cpu;\n        }\n\n        public String getMemory() {\n            return memory;\n        }\n\n        public void setMemory(String memory) {\n            this.memory = memory;\n        }\n\n        public String getEphemeralStorage() {\n            return ephemeralStorage;\n        }\n\n        public void setEphemeralStorage(String ephemeralStorage) {\n            this.ephemeralStorage = ephemeralStorage;\n        }\n\n        public String getHugepages2Mi() {\n            return hugepages2Mi;\n        }\n\n        public void setHugepages2Mi(String hugepages2Mi) {\n            this.hugepages2Mi = hugepages2Mi;\n        }\n\n        public String getHugepages1Gi() {\n            return hugepages1Gi;\n        }\n\n        public void setHugepages1Gi(String hugepages1Gi) {\n            this.hugepages1Gi = hugepages1Gi;\n        }\n\n        public String getGpuVendor() {\n            return gpuVendor;\n        }\n\n        public void setGpuVendor(String gpuVendor) {\n            this.gpuVendor = gpuVendor;\n        }\n\n        public String getGpuCount() {\n            return gpuCount;\n        }\n\n        public void setGpuCount(String gpuCount) {\n            this.gpuCount = gpuCount;\n        }\n    }\n\n    /**\n     * Encapsulates resources for Kubernetes Container resource requests\n     */\n    public static class RequestsResources {\n\n        /**\n         * Container request limit.\n         */\n        private String cpu;\n\n        /**\n         * Container memory limit.\n         */\n        private String memory;\n\n        /**\n         * Container resource ephemeral-storage request.\n         */\n        private String ephemeralStorage;\n\n\t\t/**\n         * Container resource hugepages-2Mi request.\n         */\n        private String hugepages2Mi;\n\n        /**\n         * Container resource hugepages-1Gi request.\n         */\n        private String hugepages1Gi;\n\n\n        public RequestsResources() {\n        }\n\n\n        @Deprecated\n        public RequestsResources(String cpu, String memory, String ephemeralStorage, String hugepages2Mi, String hugepages1Gi) {\n            this.cpu = cpu;\n            this.memory = memory;\n            this.ephemeralStorage = ephemeralStorage;\n            this.hugepages2Mi = hugepages2Mi;\n            this.hugepages1Gi = hugepages1Gi;\n        }\n\n        public String getCpu() {\n            return cpu;\n        }\n\n        public void setCpu(String cpu) {\n            this.cpu = cpu;\n        }\n\n        public String getMemory() {\n            return memory;\n        }\n\n        public void setMemory(String memory) {\n            this.memory = memory;\n        }\n\n        public String getEphemeralStorage() {\n            return ephemeralStorage;\n        }\n\n        public void setEphemeralStorage(String ephemeralStorage) {\n            this.ephemeralStorage = ephemeralStorage;\n        }\n\n        public String getHugepages2Mi() {\n            return hugepages2Mi;\n        }\n\n        public void setHugepages2Mi(String hugepages2Mi) {\n            this.hugepages2Mi = hugepages2Mi;\n        }\n\n        public String getHugepages1Gi() {\n            return hugepages1Gi;\n        }\n\n        public void setHugepages1Gi(String hugepages1Gi) {\n            this.hugepages1Gi = hugepages1Gi;\n        }\n    }\n\n    public static class StatefulSet {\n\n        private VolumeClaimTemplate volumeClaimTemplate = new VolumeClaimTemplate();\n\n        public VolumeClaimTemplate getVolumeClaimTemplate() {\n            return volumeClaimTemplate;\n        }\n\n        public void setVolumeClaimTemplate(VolumeClaimTemplate volumeClaimTemplate) {\n            this.volumeClaimTemplate = volumeClaimTemplate;\n        }\n\n        public static class VolumeClaimTemplate {\n\n            /**\n             * VolumeClaimTemplate name\n             */\n            private String name;\n\n            /**\n             * VolumeClaimTemplate storage.\n             */\n            private String storage = \"10m\";\n\n            /**\n             * VolumeClaimTemplate storage class name.\n             */\n            private String storageClassName;\n\n            public String getName() {\n                return this.name;\n            }\n\n            public void setName(String name) {\n                this.name = name;\n            }\n\n            public String getStorage() {\n                return storage;\n            }\n\n            public void setStorage(String storage) {\n                this.storage = storage;\n            }\n\n            public String getStorageClassName() {\n                return storageClassName;\n            }\n\n            public void setStorageClassName(String storageClassName) {\n                this.storageClassName = storageClassName;\n            }\n        }\n    }\n\n    public static class Toleration {\n\n        private String effect;\n\n        private String key;\n\n        private String operator;\n\n        private Long tolerationSeconds;\n\n        private String value;\n\n        public String getEffect() {\n            return effect;\n        }\n\n        public void setEffect(String effect) {\n            this.effect = effect;\n        }\n\n        public String getKey() {\n            return key;\n        }\n\n        public void setKey(String key) {\n            this.key = key;\n        }\n\n        public String getOperator() {\n            return operator;\n        }\n\n        public void setOperator(String operator) {\n            this.operator = operator;\n        }\n\n        public Long getTolerationSeconds() {\n            return tolerationSeconds;\n        }\n\n        public void setTolerationSeconds(Long tolerationSeconds) {\n            this.tolerationSeconds = tolerationSeconds;\n        }\n\n        public String getValue() {\n            return value;\n        }\n\n        public void setValue(String value) {\n            this.value = value;\n        }\n    }\n\n    static class KeyRef {\n        private String envVarName;\n\n        private String dataKey;\n\n        public void setEnvVarName(String envVarName) {\n            this.envVarName = envVarName;\n        }\n\n        public String getEnvVarName() {\n            return envVarName;\n        }\n\n        public void setDataKey(String dataKey) {\n            this.dataKey = dataKey;\n        }\n\n        public String getDataKey() {\n            return dataKey;\n        }\n    }\n\n    public static class SecretKeyRef extends KeyRef {\n        private String secretName;\n\n        public void setSecretName(String secretName) {\n            this.secretName = secretName;\n        }\n\n        public String getSecretName() {\n            return secretName;\n        }\n    }\n\n    public static class ConfigMapKeyRef extends KeyRef {\n        private String configMapName;\n\n        public void setConfigMapName(String configMapName) {\n            this.configMapName = configMapName;\n        }\n\n        public String getConfigMapName() {\n            return configMapName;\n        }\n    }\n\n    public static class PodSecurityContext {\n\t\t/**\n\t\t * The numeric user ID to run pod container processes under\n\t\t */\n\t\tprivate Long runAsUser;\n\n\t\t/**\n\t\t * The numeric group id to run the entrypoint of the container process\n\t\t */\n\t\tprivate Long runAsGroup;\n\n\t\t/**\n\t\t * Indicates that the container must run as a non-root user\n\t\t */\n\t\tprivate Boolean runAsNonRoot;\n\n        /**\n         * The numeric group ID for the volumes of the pod\n         */\n        private Long fsGroup;\n\n\t\t/**\n\t\t * Defines behavior of changing ownership and permission of the volume before being\n\t\t * exposed inside pod (only applies to volume types which support fsGroup based\n\t\t * ownership and permissions) - possible values are \"OnRootMismatch\", \"Always\"\n\t\t */\n\t\tprivate String fsGroupChangePolicy;\n\n        /**\n         * The numeric group IDs applied to the pod container processes, in addition to the container's primary group ID\n         */\n        private Long[] supplementalGroups;\n\n\t\t/**\n\t\t * The seccomp options to use for the pod containers\n\t\t */\n\t\tprivate SeccompProfile seccompProfile;\n\n\t\t/**\n\t\t * The SELinux context to be applied to the pod containers\n\t\t */\n\t\tprivate SELinuxOptions seLinuxOptions;\n\n\t\t/**\n\t\t * List of namespaced sysctls used for the pod (not used when spec.os.name is windows).\n\t\t */\n\t\tprivate List<SysctlInfo> sysctls;\n\n\t\t/**\n\t\t * The Windows specific settings applied to all containers.\n\t\t */\n\t\tprivate WindowsSecurityContextOptions windowsOptions;\n\n        public void setRunAsUser(Long runAsUser) {\n            this.runAsUser = runAsUser;\n        }\n\n        public Long getRunAsUser() {\n            return this.runAsUser;\n        }\n\n\t\tpublic Long getRunAsGroup() {\n\t\t\treturn runAsGroup;\n\t\t}\n\n\t\tpublic void setRunAsGroup(Long runAsGroup) {\n\t\t\tthis.runAsGroup = runAsGroup;\n\t\t}\n\n\t\tpublic Boolean getRunAsNonRoot() {\n\t\t\treturn runAsNonRoot;\n\t\t}\n\n\t\tpublic void setRunAsNonRoot(Boolean runAsNonRoot) {\n\t\t\tthis.runAsNonRoot = runAsNonRoot;\n\t\t}\n\n\t\tpublic void setFsGroup(Long fsGroup) {\n            this.fsGroup = fsGroup;\n        }\n\n        public Long getFsGroup() {\n            return fsGroup;\n        }\n\n\t\tpublic String getFsGroupChangePolicy() {\n\t\t\treturn fsGroupChangePolicy;\n\t\t}\n\n\t\tpublic void setFsGroupChangePolicy(String fsGroupChangePolicy) {\n\t\t\tthis.fsGroupChangePolicy = fsGroupChangePolicy;\n\t\t}\n\n\t\tpublic void setSupplementalGroups(Long[] supplementalGroups) {\n            this.supplementalGroups = supplementalGroups;\n        }\n\n        public Long[] getSupplementalGroups() {\n            return supplementalGroups;\n        }\n\n        public SeccompProfile getSeccompProfile() {\n            return seccompProfile;\n        }\n\n        public void setSeccompProfile(SeccompProfile seccompProfile) {\n            this.seccompProfile = seccompProfile;\n        }\n\n\t\tpublic SELinuxOptions getSeLinuxOptions() {\n\t\t\treturn seLinuxOptions;\n\t\t}\n\n\t\tpublic void setSeLinuxOptions(SELinuxOptions seLinuxOptions) {\n\t\t\tthis.seLinuxOptions = seLinuxOptions;\n\t\t}\n\n\t\tpublic List<SysctlInfo> getSysctls() {\n\t\t\treturn sysctls;\n\t\t}\n\n\t\tpublic void setSysctls(List<SysctlInfo> sysctls) {\n\t\t\tthis.sysctls = sysctls;\n\t\t}\n\n\t\tpublic WindowsSecurityContextOptions getWindowsOptions() {\n\t\t\treturn windowsOptions;\n\t\t}\n\n\t\tpublic void setWindowsOptions(WindowsSecurityContextOptions windowsOptions) {\n\t\t\tthis.windowsOptions = windowsOptions;\n\t\t}\n\t}\n\n\tpublic static class ContainerSecurityContext {\n\n\t\t/**\n\t\t * Whether a process can gain more privileges than its parent process\n\t\t */\n\t\tprivate Boolean allowPrivilegeEscalation;\n\n\t\t/**\n\t\t * The capabilities to add/drop when running the container (cannot be set when spec.os.name is windows)\n\t\t */\n\t\tprivate Capabilities capabilities;\n\n\t\t/**\n\t\t * Run container in privileged mode.\n\t\t */\n\t\tprivate Boolean privileged;\n\n\t\t/**\n\t\t * The type of proc mount to use for the container (cannot be set when spec.os.name is windows)\n\t\t */\n\t\tprivate String procMount;\n\n\t\t/**\n\t\t * Mounts the container's root filesystem as read-only\n\t\t */\n\t\tprivate Boolean readOnlyRootFilesystem;\n\n\t\t/**\n\t\t * The numeric user ID to run pod container processes under\n\t\t */\n\t\tprivate Long runAsUser;\n\n\t\t/**\n\t\t * The numeric group id to run the entrypoint of the container process\n\t\t */\n\t\tprivate Long runAsGroup;\n\n\t\t/**\n\t\t * Indicates that the container must run as a non-root user\n\t\t */\n\t\tprivate Boolean runAsNonRoot;\n\n\t\t/**\n\t\t * The seccomp options to use for the container\n\t\t */\n\t\tprivate SeccompProfile seccompProfile;\n\n\t\t/**\n\t\t * The SELinux context to be applied to the container.\n\t\t */\n\t\tprivate SELinuxOptions seLinuxOptions;\n\n\t\t/**\n\t\t * The Windows specific settings applied to the container.\n\t\t */\n\t\tprivate WindowsSecurityContextOptions windowsOptions;\n\n\t\tpublic Boolean getAllowPrivilegeEscalation() {\n\t\t\treturn allowPrivilegeEscalation;\n\t\t}\n\n\t\tpublic void setAllowPrivilegeEscalation(Boolean allowPrivilegeEscalation) {\n\t\t\tthis.allowPrivilegeEscalation = allowPrivilegeEscalation;\n\t\t}\n\n\t\tpublic Capabilities getCapabilities() {\n\t\t\treturn capabilities;\n\t\t}\n\n\t\tpublic void setCapabilities(Capabilities capabilities) {\n\t\t\tthis.capabilities = capabilities;\n\t\t}\n\n\t\tpublic Boolean getPrivileged() {\n\t\t\treturn privileged;\n\t\t}\n\n\t\tpublic void setPrivileged(Boolean privileged) {\n\t\t\tthis.privileged = privileged;\n\t\t}\n\n\t\tpublic String getProcMount() {\n\t\t\treturn procMount;\n\t\t}\n\n\t\tpublic void setProcMount(String procMount) {\n\t\t\tthis.procMount = procMount;\n\t\t}\n\n\t\tpublic Boolean getReadOnlyRootFilesystem() {\n\t\t\treturn readOnlyRootFilesystem;\n\t\t}\n\n\t\tpublic void setReadOnlyRootFilesystem(Boolean readOnlyRootFilesystem) {\n\t\t\tthis.readOnlyRootFilesystem = readOnlyRootFilesystem;\n\t\t}\n\n\t\tpublic Long getRunAsUser() {\n\t\t\treturn runAsUser;\n\t\t}\n\n\t\tpublic void setRunAsUser(Long runAsUser) {\n\t\t\tthis.runAsUser = runAsUser;\n\t\t}\n\n\t\tpublic Long getRunAsGroup() {\n\t\t\treturn runAsGroup;\n\t\t}\n\n\t\tpublic void setRunAsGroup(Long runAsGroup) {\n\t\t\tthis.runAsGroup = runAsGroup;\n\t\t}\n\n\t\tpublic Boolean getRunAsNonRoot() {\n\t\t\treturn runAsNonRoot;\n\t\t}\n\n\t\tpublic void setRunAsNonRoot(Boolean runAsNonRoot) {\n\t\t\tthis.runAsNonRoot = runAsNonRoot;\n\t\t}\n\n\t\tpublic SeccompProfile getSeccompProfile() {\n\t\t\treturn seccompProfile;\n\t\t}\n\n\t\tpublic void setSeccompProfile(SeccompProfile seccompProfile) {\n\t\t\tthis.seccompProfile = seccompProfile;\n\t\t}\n\n\t\tpublic SELinuxOptions getSeLinuxOptions() {\n\t\t\treturn seLinuxOptions;\n\t\t}\n\n\t\tpublic void setSeLinuxOptions(SELinuxOptions seLinuxOptions) {\n\t\t\tthis.seLinuxOptions = seLinuxOptions;\n\t\t}\n\n\t\tpublic WindowsSecurityContextOptions getWindowsOptions() {\n\t\t\treturn windowsOptions;\n\t\t}\n\n\t\tpublic void setWindowsOptions(WindowsSecurityContextOptions windowsOptions) {\n\t\t\tthis.windowsOptions = windowsOptions;\n\t\t}\n\t}\n\n\t/**\n\t * Adds and removes POSIX capabilities from running containers.\n\t */\n\tpublic static class Capabilities {\n\t\t/**\n\t\t * Added capabilities.\n\t\t */\n\t\tprivate List<String> add;\n\n\t\t/**\n\t\t * Removed capabilities.\n\t\t */\n\t\tprivate List<String> drop;\n\n\t\tpublic List<String> getAdd() {\n\t\t\treturn add;\n\t\t}\n\n\t\tpublic void setAdd(List<String> add) {\n\t\t\tthis.add = add;\n\t\t}\n\n\t\tpublic List<String> getDrop() {\n\t\t\treturn drop;\n\t\t}\n\n\t\tpublic void setDrop(List<String> drop) {\n\t\t\tthis.drop = drop;\n\t\t}\n\t}\n\n\t/**\n     * Defines a pod seccomp profile settings.\n     */\n\tpublic static class SeccompProfile {\n\n        /**\n         * Type of seccomp profile.\n         */\n        private String type;\n\n        /**\n         * Path of the pre-configured profile on the node, relative to the kubelet's configured Seccomp profile location, only valid when type is \"Localhost\".\n         */\n        private String localhostProfile;\n\n        public String getType() {\n            return type;\n        }\n\n        public void setType(String type) {\n            this.type = type;\n        }\n\n        public String getLocalhostProfile() {\n            return localhostProfile;\n        }\n\n        public void setLocalhostProfile(String localhostProfile) {\n            this.localhostProfile = localhostProfile;\n        }\n    }\n\n\t/**\n\t * The labels to be applied to the container.\n\t */\n\tpublic static class SELinuxOptions {\n\t\t/**\n\t\t * Level label applied to the container\n\t\t */\n\t\tprivate String level;\n\n\t\t/**\n\t\t * Role Level label applied to the container\n\t\t */\n\t\tprivate String role;\n\n\t\t/**\n\t\t * Type label applied to the container\n\t\t */\n\t\tprivate String type;\n\n\t\t/**\n\t\t * User label applied to the container\n\t\t */\n\t\tprivate String user;\n\n\t\tpublic String getLevel() {\n\t\t\treturn level;\n\t\t}\n\n\t\tpublic void setLevel(String level) {\n\t\t\tthis.level = level;\n\t\t}\n\n\t\tpublic String getRole() {\n\t\t\treturn role;\n\t\t}\n\n\t\tpublic void setRole(String role) {\n\t\t\tthis.role = role;\n\t\t}\n\n\t\tpublic String getType() {\n\t\t\treturn type;\n\t\t}\n\n\t\tpublic void setType(String type) {\n\t\t\tthis.type = type;\n\t\t}\n\n\t\tpublic String getUser() {\n\t\t\treturn user;\n\t\t}\n\n\t\tpublic void setUser(String user) {\n\t\t\tthis.user = user;\n\t\t}\n\t}\n\n\t/**\n\t * Sysctl defines a kernel parameter to be set on the pod.\n\t */\n\tpublic static class SysctlInfo {\n\t\t/**\n\t\t * Name of the property\n\t\t */\n\t\tprivate String name;\n\n\t\t/**\n\t\t * Value of the property\n\t\t */\n\t\tprivate String value;\n\n\t\tpublic String getName() {\n\t\t\treturn name;\n\t\t}\n\n\t\tpublic void setName(String name) {\n\t\t\tthis.name = name;\n\t\t}\n\n\t\tpublic String getValue() {\n\t\t\treturn value;\n\t\t}\n\n\t\tpublic void setValue(String value) {\n\t\t\tthis.value = value;\n\t\t}\n\t}\n\n\t/**\n\t * Contains Windows-specific options and credentials.\n\t */\n\tpublic static class WindowsSecurityContextOptions {\n\n\t\t/**\n\t\t * Where the GMSA admission webhook inlines the contents of the GMSA credential spec\n\t\t * named by the GMSACredentialSpecName field.\n\t\t */\n\t\tprivate String gmsaCredentialSpec;\n\n\t\t/**\n\t\t * The name of the GMSA credential spec to use.\n\t\t */\n\t\tprivate String gmsaCredentialSpecName;\n\n\t\t/**\n\t\t * Whether a container should be run as a 'Host Process' container.\n\t\t */\n\t\tprivate Boolean hostProcess;\n\n\t\t/**\n\t\t * The username in Windows to run the entrypoint of the container process.\n\t\t */\n\t\tprivate String runAsUserName;\n\n\t\tpublic String getGmsaCredentialSpec() {\n\t\t\treturn gmsaCredentialSpec;\n\t\t}\n\n\t\tpublic void setGmsaCredentialSpec(String gmsaCredentialSpec) {\n\t\t\tthis.gmsaCredentialSpec = gmsaCredentialSpec;\n\t\t}\n\n\t\tpublic String getGmsaCredentialSpecName() {\n\t\t\treturn gmsaCredentialSpecName;\n\t\t}\n\n\t\tpublic void setGmsaCredentialSpecName(String gmsaCredentialSpecName) {\n\t\t\tthis.gmsaCredentialSpecName = gmsaCredentialSpecName;\n\t\t}\n\n\t\tpublic Boolean getHostProcess() {\n\t\t\treturn hostProcess;\n\t\t}\n\n\t\tpublic void setHostProcess(Boolean hostProcess) {\n\t\t\tthis.hostProcess = hostProcess;\n\t\t}\n\n\t\tpublic String getRunAsUserName() {\n\t\t\treturn runAsUserName;\n\t\t}\n\n\t\tpublic void setRunAsUserName(String runAsUserName) {\n\t\t\tthis.runAsUserName = runAsUserName;\n\t\t}\n\t}\n\n\tpublic static class Lifecycle {\n        private Hook postStart;\n        private Hook preStop;\n\n        Hook getPreStop() {\n            return preStop;\n        }\n\n        Hook getPostStart() {\n            return postStart;\n        }\n\n        void setPostStart(Hook postStart) {\n            this.postStart = postStart;\n        }\n\n        void setPreStop(Hook preStop) {\n            this.preStop = preStop;\n\n        }\n\n        public static class Hook {\n            private Exec exec;\n\n            Exec getExec() {\n                return exec;\n            }\n\n            void setExec(Exec exec) {\n                this.exec = exec;\n            }\n        }\n\n        public static class Exec {\n            private List<String> command;\n\n            List<String> getCommand() {\n                return command;\n            }\n\n            void setCommand(List<String> command) {\n                this.command = command;\n            }\n        }\n    }\n\n\tpublic static class InitContainer extends ContainerProperties {\n    }\n\n    static class Container extends io.fabric8.kubernetes.api.model.Container {\n    }\n\n    public static class ContainerProperties {\n\t\t@JsonAlias(\"imageName\")\n\t\t@JsonProperty(\"image\")\n\t\tprivate String image;\n\t\t@JsonAlias(\"containerName\")\n\t\t@JsonProperty(\"name\")\n\t\tprivate String name;\n\t\t@JsonProperty(\"command\")\n\t\t@JsonAlias(\"commands\")\n\t\tprivate List<String> command;\n\t\tprivate List<String> args;\n\t\tprivate List<VolumeMount> volumeMounts;\n\n        /**\n         * Environment variables to set for any deployed init container.\n         */\n        private String[] environmentVariables = new String[]{};\n\t\tprivate String[] environmentVariablesFromFieldRefs = new String[]{};\n        private String[] configMapRefEnvVars = new String[]{};\n        private String[] secretRefEnvVars = new String[]{};\n\n\t\tpublic String getName() {\n\t\t\treturn name;\n\t\t}\n\n\t\tpublic void setName(String name) {\n\t\t\tthis.name = name;\n\t\t}\n\t\t@Deprecated\n\t\tpublic String getImageName() {\n\t\t\treturn getImage();\n\t\t}\n\t\t@Deprecated\n\t\tpublic void setImageName(String image) {\n\t\t\tsetImage(image);\n\t\t}\n\t\tpublic String getImage() {\n\t\t\treturn image;\n\t\t}\n\n\t\tpublic void setImage(String image) {\n\t\t\tthis.image = image;\n\t\t}\n\n\t\t@Deprecated\n\t\tpublic String getContainerName() {\n\t\t\treturn getName();\n\t\t}\n\n\t\t@Deprecated\n\t\tpublic void setContainerName(String containerName) {\n\t\t\tsetName(containerName);\n\t\t}\n\n\t\tpublic List<String> getCommand() {\n\t\t\treturn command;\n\t\t}\n\n\t\tpublic void setCommand(List<String> command) {\n\t\t\tthis.command = command;\n\t\t}\n\t\t@Deprecated\n\t\tpublic List<String> getCommands() {\n\t\t\treturn getCommand();\n\t\t}\n\n\t\t@Deprecated\n\t\tpublic void setCommands(List<String> commands) {\n\t\t\tsetCommand(commands);\n\t\t}\n\n        public List<String> getArgs() {\n            return args;\n        }\n\n        public void setArgs(List<String> args) {\n            this.args = args;\n        }\n\n        public List<VolumeMount> getVolumeMounts() {\n            return volumeMounts;\n        }\n\n        public void setVolumeMounts(List<VolumeMount> volumeMounts) {\n            this.volumeMounts = volumeMounts;\n        }\n\n        public String[] getEnvironmentVariables() {\n            return environmentVariables;\n        }\n\n        public void setEnvironmentVariables(String[] environmentVariables) {\n            this.environmentVariables = environmentVariables;\n        }\n\n\t\tpublic String[] getEnvironmentVariablesFromFieldRefs() {\n\t\t\treturn environmentVariablesFromFieldRefs;\n\t\t}\n\n\t\tpublic void setEnvironmentVariablesFromFieldRefs(String[] environmentVariablesFromFieldRefs) {\n\t\t\tthis.environmentVariablesFromFieldRefs = environmentVariablesFromFieldRefs;\n\t\t}\n\n        public String[] getConfigMapRefEnvVars() {\n            return configMapRefEnvVars;\n        }\n\n        public void setConfigMapRefEnvVars(String[] configMapRefEnvVars) {\n            this.configMapRefEnvVars = configMapRefEnvVars;\n        }\n\n        public String[] getSecretRefEnvVars() {\n            return secretRefEnvVars;\n        }\n\n        public void setSecretRefEnvVars(String[] secretRefEnvVars) {\n            this.secretRefEnvVars = secretRefEnvVars;\n        }\n    }\n\n    /**\n     * The {@link RestartPolicy} to use. Defaults to {@link RestartPolicy#Always}.\n     */\n    private RestartPolicy restartPolicy = RestartPolicy.Always;\n\n    /**\n     * The default service account name to use for tasks.\n     */\n    protected static final String DEFAULT_TASK_SERVICE_ACCOUNT_NAME = \"default\";\n\n    /**\n     * Service account name to use for tasks, defaults to:\n     * {@link #DEFAULT_TASK_SERVICE_ACCOUNT_NAME}\n     */\n    private String taskServiceAccountName = DEFAULT_TASK_SERVICE_ACCOUNT_NAME;\n\n    /**\n     * Obtains the {@link RestartPolicy} to use. Defaults to\n     * {@link #restartPolicy}.\n     *\n     * @return the {@link RestartPolicy} to use\n     */\n    public RestartPolicy getRestartPolicy() {\n        return restartPolicy;\n    }\n\n    /**\n     * Sets the {@link RestartPolicy} to use.\n     *\n     * @param restartPolicy the {@link RestartPolicy} to use\n     */\n    public void setRestartPolicy(RestartPolicy restartPolicy) {\n        this.restartPolicy = restartPolicy;\n    }\n\n    /**\n     * Obtains the service account name to use for tasks.\n     *\n     * @return the service account name\n     */\n    public String getTaskServiceAccountName() {\n        return taskServiceAccountName;\n    }\n\n    /**\n     * Sets the service account name to use for tasks.\n     *\n     * @param taskServiceAccountName the service account name\n     */\n    public void setTaskServiceAccountName(String taskServiceAccountName) {\n        this.taskServiceAccountName = taskServiceAccountName;\n    }\n\n    /**\n     * Name of the environment variable that can define the Kubernetes namespace to use.\n     */\n    public static final String ENV_KEY_KUBERNETES_NAMESPACE = \"KUBERNETES_NAMESPACE\";\n\n    private static String KUBERNETES_NAMESPACE = System.getenv(\"KUBERNETES_NAMESPACE\");\n\n    /**\n     * Namespace to use.\n     */\n    private String namespace = KUBERNETES_NAMESPACE;\n\n    /**\n     * Secrets for a access a private registry to pull images.\n     */\n    private String imagePullSecret;\n\n    /**\n     * List of Secrets for a access a private registry to pull images.\n     */\n    private List<String> imagePullSecrets;\n\n    /**\n     * Delay in seconds when the Kubernetes liveness check of the app container should start\n     * checking its health status.\n     */\n    private int livenessHttpProbeDelay = 1;\n    /**\n     * When the probe fails more times than the failure value the pod is restarted.\n     */\n    private int livenessHttpProbeFailure = 3;\n    /**\n     * When the probe passes success times it is considered live.\n     */\n    private int livenessHttpProbeSuccess = 1;\n\n    /**\n     * Period in seconds for performing the Kubernetes liveness check of the app container.\n     */\n    private int livenessHttpProbePeriod = 60;\n\n    /**\n     * Timeout in seconds for the check to wait for a response after which it is considered a failed check.\n     */\n    private int livenessHttpProbeTimeout = 5;\n    /**\n     * Timeout in seconds for the liveness check to wait for a response before the check is considered a failure.\n     */\n    private int livenessTcpProbeTimeout = 5;\n\n    /**\n     * Path that app container has to respond to for liveness check.\n     */\n    private String livenessHttpProbePath;\n\n    /**\n     * Port that app container has to respond on for liveness check.\n     */\n    private Integer livenessHttpProbePort = null;\n\n    /**\n     * Schema that app container has to respond on for liveness check.\n     */\n    private String livenessHttpProbeScheme = \"HTTP\";\n\n    /**\n     * Schema that app container has to respond to for readiness check.\n     */\n    private String readinessHttpProbeScheme = \"HTTP\";\n\n    /**\n     * Schema that app container has to respond to for startup check.\n     */\n    private String startupProbeScheme = \"HTTP\";\n\n    /**\n     * If present will assign to spec.shareProcessNamespace of the Pod.\n     */\n    private Boolean shareProcessNamespace;\n    /**\n     * Delay in seconds when the readiness check of the app container should start checking if\n     * the module is fully up and running.\n     */\n    private int readinessHttpProbeDelay = 1;\n\n    private int readinessHttpProbeSuccess = 1;\n\n    private int readinessHttpProbeFailure = 3;\n\n    private int readinessTcpProbeTimeout = 3;\n\n    private int readinessTcpProbeFailure = 3;\n    private int readinessTcpProbeSuccess = 1;\n    /**\n     * Delay in seconds when the startup check of the app container should start checking if\n     * the module is fully up and running.\n     */\n    private int startupHttpProbeDelay = 30;\n    /**\n     * The number of time the http startup probe will be allowed to fail before restarting the pod.\n     */\n\n    private int startupHttpProbeFailure = 20;\n    /**\n     * The number of time the http startup probe will be required to pass before considering the application started.\n     */\n    private int startupHttpProbeSuccess = 1;\n    /**\n     * The number of time the tcp startup probe will be allowed to fail before restarting the pod.\n     */\n    private int startupTcpProbeFailure = 20;\n    /**\n     * The number of time the tcp startup probe will be required to pass before considering the application started.\n     */\n    private int startupTcpProbeSuccess = 1;\n    /**\n     * The number of time the command startup probe will be allowed to fail before restarting the pod.\n     */\n    private int startupCommandProbeFailure = 10;\n    /**\n     * The number of time the command startup probe will be required to pass before considering the application started.\n     */\n    private int startupCommandProbeSuccess = 1;\n    /**\n     * Period in seconds to perform the readiness check of the app container.\n     */\n    private int readinessHttpProbePeriod = 10;\n\n    /**\n     * Period in seconds to perform the startup check of the app container.\n     */\n    private int startupHttpProbePeriod = 3;\n\n    /**\n     * Timeout in seconds that the app container has to respond to its status during\n     * the startup check.\n     */\n    private int startupHttpProbeTimeout = 5;\n\n    /**\n     * Timeout in seconds that the app container has to respond to its health status during\n     * the readiness check.\n     */\n    private int readinessHttpProbeTimeout = 5;\n\n    private int readinessCommandProbeFailure = 3;\n    private int readinessCommandProbeSuccess = 1;\n\n    /**\n     * Path that app container has to respond to for readiness check.\n     */\n    private String readinessHttpProbePath;\n\n    /**\n     * Path that app container has to respond to for startup check.\n     */\n    private String startupHttpProbePath;\n    /**\n     * Port that app container has to respond on for readiness check.\n     */\n    private Integer readinessHttpProbePort = null;\n\n    /**\n     * Port that app container has to respond on for startup check.\n     */\n    private Integer startupHttpProbePort = null;\n    /**\n     * Delay in seconds when the liveness TCP check should start checking\n     */\n    private int livenessTcpProbeDelay = 10;\n\n    private int livenessTcpProbeSuccess = 1;\n\n    private int livenessTcpProbeFailure = 3;\n\n    /**\n     * Period in seconds to perform the liveness TCP check\n     */\n    private int livenessTcpProbePeriod = 60;\n\n    /**\n     * The TCP port the liveness probe should check\n     */\n    private Integer livenessTcpProbePort = null;\n\n    /**\n     * Delay in seconds when the readiness TCP check should start checking\n     */\n    private int readinessTcpProbeDelay = 1;\n\n    /**\n     * Period in seconds to perform the readiness TCP check\n     */\n    private int readinessTcpProbePeriod = 10;\n\n    /**\n     * The TCP port the readiness probe should check\n     */\n    private Integer readinessTcpProbePort = null;\n\n    /**\n     * Delay in seconds when the readiness command check should start checking\n     */\n    private int readinessCommandProbeDelay = 1;\n\n    /**\n     * Period in seconds to perform the readiness command check\n     */\n    private int readinessCommandProbePeriod = 10;\n\n    /**\n     * The command the readiness probe should use to check\n     */\n    private String readinessCommandProbeCommand = null;\n\n    /**\n     * Delay in seconds when the readiness TCP check should start checking\n     */\n    private int startupTcpProbeDelay = 30;\n\n    private int startupTcpProbeTimeout = 5;\n\n    /**\n     * Period in seconds to perform the readiness TCP check\n     */\n    private int startupTcpProbePeriod = 3;\n\n    /**\n     * The TCP port the readiness probe should check\n     */\n    private Integer startupTcpProbePort = null;\n\n    /**\n     * Delay in seconds when the readiness command check should start checking\n     */\n    private int startupCommandProbeDelay = 30;\n\n    /**\n     * Period in seconds to perform the readiness command check\n     */\n    private int startupCommandProbePeriod = 10;\n\n    /**\n     * The command the readiness probe should use to check\n     */\n    private String startupCommandProbeCommand = null;\n\n    /**\n     * Delay in seconds when the liveness command check should start checking\n     */\n    private int livenessCommandProbeDelay = 10;\n\n    private int livenessCommandProbeFailure = 3;\n    private int livenessCommandProbeSuccess = 1;\n\n    /**\n     * Period in seconds to perform the liveness command check\n     */\n    private int livenessCommandProbePeriod = 10;\n\n    /**\n     * The command the liveness probe should use to check\n     */\n    private String livenessCommandProbeCommand = null;\n\n    /**\n     * The secret name containing the credentials to use when accessing secured probe\n     * endpoints.\n     */\n    private String probeCredentialsSecret;\n\n    /**\n     * The probe type to use when doing health checks. Defaults to HTTP.\n     */\n    private ProbeType probeType = ProbeType.HTTP;\n\n    /**\n     * Memory and CPU limits (i.e. maximum needed values) to allocate for a Pod.\n     */\n    private LimitsResources limits = new LimitsResources();\n\n    /**\n     * Memory and CPU requests (i.e. guaranteed needed values) to allocate for a Pod.\n     */\n    private RequestsResources requests = new RequestsResources();\n\n    /**\n     * Tolerations to allocate for a Pod.\n     */\n    private List<Toleration> tolerations = new ArrayList<>();\n\n    /**\n     * Secret key references to be added to the Pod environment.\n     */\n    private List<SecretKeyRef> secretKeyRefs = new ArrayList<>();\n\n    /**\n     * ConfigMap key references to be added to the Pod environment.\n     */\n    private List<ConfigMapKeyRef> configMapKeyRefs = new ArrayList<>();\n\n    /**\n     * ConfigMap references to be added to the Pod environment.\n     */\n    private List<String> configMapRefs = new ArrayList<>();\n\n    /**\n     * Secret references to be added to the Pod environment.\n     */\n    private List<String> secretRefs = new ArrayList<>();\n\n    /**\n     * Resources to assign for VolumeClaimTemplates (identified by metadata name) inside\n     * StatefulSet.\n     */\n    private StatefulSet statefulSet = new StatefulSet();\n\n    /**\n     * Environment variables to set for any deployed app container. To be used for service\n     * binding.\n     */\n    private String[] environmentVariables = new String[]{};\n\n    /**\n     * Entry point style used for the Docker image. To be used to determine how to pass in\n     * properties.\n     */\n    private EntryPointStyle entryPointStyle = EntryPointStyle.exec;\n\n    /**\n     * Create a \"LoadBalancer\" for the service created for each app. This facilitates\n     * assignment of external IP to app.\n     */\n    private boolean createLoadBalancer = false;\n\n    /**\n     * Service annotations to set for the service created for each app.\n     */\n    private String serviceAnnotations = null;\n\n    /**\n     * Pod annotations to set for the pod created for each deployment.\n     */\n    private String podAnnotations;\n\n    /**\n     * Job annotations to set for the pod or job created for a job.\n     */\n    private String jobAnnotations;\n\n    /**\n     * Time to wait for load balancer to be available before attempting delete of service (in\n     * minutes).\n     */\n    private int minutesToWaitForLoadBalancer = 5;\n\n    /**\n     * Maximum allowed restarts for app that fails due to an error or excessive resource use.\n     */\n    private int maxTerminatedErrorRestarts = 2;\n\n    /**\n     * Maximum allowed restarts for app that is in a CrashLoopBackOff.\n     */\n    private int maxCrashLoopBackOffRestarts = 4;\n\n    /**\n     * The image pull policy to use for Pod deployments in Kubernetes.\n     */\n    private ImagePullPolicy imagePullPolicy = ImagePullPolicy.IfNotPresent;\n\n    /**\n     * Volume mounts that a container is requesting. This can be specified as a deployer\n     * property or as an app deployment property. Deployment properties will override deployer\n     * properties.\n     */\n    private List<VolumeMount> volumeMounts = new ArrayList<>();\n\n    /**\n     * The volumes that a Kubernetes instance supports. See\n     * https://kubernetes.io/docs/user-guide/volumes/#types-of-volumes This can be specified\n     * as a deployer property or as an app deployment property. Deployment properties will\n     * override deployer properties.\n     */\n    private List<Volume> volumes = new ArrayList<>();\n\n    /**\n     * The hostNetwork setting for the deployments. See\n     * https://kubernetes.io/docs/api-reference/v1/definitions/#_v1_podspec This can be\n     * specified as a deployer property or as an app deployment property. Deployment\n     * properties will override deployer properties.\n     */\n    private boolean hostNetwork = false;\n\n    /**\n     * Create a \"Job\" instead of just a \"Pod\" when launching tasks. See\n     * https://kubernetes.io/docs/concepts/workloads/controllers/jobs-run-to-completion/\n     */\n    private boolean createJob = false;\n\n    /**\n     * The node selector to use in key:value format, comma separated\n     */\n    private String nodeSelector;\n\n    /**\n     * Service account name to use for app deployments\n     */\n    private String deploymentServiceAccountName;\n\n    /**\n     * The security context to apply to created pod's.\n     */\n    private PodSecurityContext podSecurityContext;\n\n    /**\n     * The security context to apply to created pod's main container.\n     */\n    private ContainerSecurityContext containerSecurityContext;\n\n    /**\n     * The node affinity rules to apply.\n     */\n    private NodeAffinity nodeAffinity;\n\n    /**\n     * The pod affinity rules to apply\n     */\n    private PodAffinity podAffinity;\n\n    /**\n     * The pod anti-affinity rules to apply\n     */\n    private PodAntiAffinity podAntiAffinity;\n\n    /**\n     * A custom init container image name to use when creating a StatefulSet\n     */\n    private String statefulSetInitContainerImageName;\n\n    /**\n     * A custom init container to apply.\n     */\n    private InitContainer initContainer;\n    /**\n     * Apply multiple custom init containers.\n     * Will add initContainer first if it exists and add to list.\n     */\n    private List<InitContainer> initContainers = new ArrayList<>();\n    /**\n     * Lifecycle spec to apply.\n     */\n    private Lifecycle lifecycle = new Lifecycle();\n\n\n    /**\n     * The additional containers one can add to the main application container.\n     */\n    private List<Container> additionalContainers;\n\n    /**\n     * Deployment label to be applied to Deployment, StatefulSet, JobSpec etc.,\n     */\n    private String deploymentLabels;\n\n    /**\n     * Pod priorityClassName. The user should create a PriorityClass before setting this property.\n     */\n    private String priorityClassName;\n\n    private AppAdmin appAdmin = new AppAdmin();\n\n    public String getNamespace() {\n        return namespace;\n    }\n\n    public void setNamespace(String namespace) {\n        this.namespace = namespace;\n    }\n\n    public String getImagePullSecret() {\n        return imagePullSecret;\n    }\n\n    public void setImagePullSecret(String imagePullSecret) {\n        this.imagePullSecret = imagePullSecret;\n    }\n\n    public List<String> getImagePullSecrets() {\n        return imagePullSecrets;\n    }\n\n    public void setImagePullSecrets(List<String> imagePullSecrets) {\n        this.imagePullSecrets = imagePullSecrets;\n    }\n\n\tpublic static class CronConfig {\n\t\tprivate String concurrencyPolicy;\n\n\t\tprivate Integer ttlSecondsAfterFinished;\n\n\t\tprivate Integer backoffLimit;\n\n\t\tpublic String getConcurrencyPolicy() {\n\t\t\treturn concurrencyPolicy;\n\t\t}\n\n\t\tpublic void setConcurrencyPolicy(String concurrencyPolicy) {\n\t\t\tthis.concurrencyPolicy = concurrencyPolicy;\n\t\t}\n\n\t\tpublic Integer getTtlSecondsAfterFinished() {\n\t\t\treturn ttlSecondsAfterFinished;\n\t\t}\n\n\t\tpublic void setTtlSecondsAfterFinished(Integer ttlSecondsAfterFinished) {\n\t\t\tthis.ttlSecondsAfterFinished = ttlSecondsAfterFinished;\n\t\t}\n\n\t\tpublic Integer getBackoffLimit() {\n\t\t\treturn backoffLimit;\n\t\t}\n\n\t\tpublic void setBackoffLimit(Integer backoffLimit) {\n\t\t\tthis.backoffLimit = backoffLimit;\n\t\t}\n\t}\n\n    public Boolean getShareProcessNamespace() {\n        return shareProcessNamespace;\n    }\n\n    public void setShareProcessNamespace(Boolean shareProcessNamespace) {\n        this.shareProcessNamespace = shareProcessNamespace;\n    }\n\n    public String getPriorityClassName() {\n        return priorityClassName;\n    }\n\n    public void setPriorityClassName(String priorityClassName) {\n        this.priorityClassName = priorityClassName;\n    }\n\n    public int getLivenessTcpProbeSuccess() {\n        return livenessTcpProbeSuccess;\n    }\n\n    public void setLivenessTcpProbeSuccess(int livenessTcpProbeSuccess) {\n        this.livenessTcpProbeSuccess = livenessTcpProbeSuccess;\n    }\n\n    public int getLivenessTcpProbeFailure() {\n        return livenessTcpProbeFailure;\n    }\n\n    public void setLivenessTcpProbeFailure(int livenessTcpProbeFailure) {\n        this.livenessTcpProbeFailure = livenessTcpProbeFailure;\n    }\n\n    public int getLivenessHttpProbeDelay() {\n        return livenessHttpProbeDelay;\n    }\n\n    public void setLivenessHttpProbeDelay(int livenessHttpProbeDelay) {\n        this.livenessHttpProbeDelay = livenessHttpProbeDelay;\n    }\n\n    public int getLivenessTcpProbeTimeout() {\n        return livenessTcpProbeTimeout;\n    }\n\n    public void setLivenessTcpProbeTimeout(int livenessTcpProbeTimeout) {\n        this.livenessTcpProbeTimeout = livenessTcpProbeTimeout;\n    }\n\n\n    public int getLivenessHttpProbePeriod() {\n        return livenessHttpProbePeriod;\n    }\n\n    public void setLivenessHttpProbePeriod(int livenessHttpProbePeriod) {\n        this.livenessHttpProbePeriod = livenessHttpProbePeriod;\n    }\n\n    public int getLivenessHttpProbeTimeout() {\n        return livenessHttpProbeTimeout;\n    }\n\n    public void setLivenessHttpProbeTimeout(int livenessHttpProbeTimeout) {\n        this.livenessHttpProbeTimeout = livenessHttpProbeTimeout;\n    }\n\n    public String getLivenessHttpProbePath() {\n        return livenessHttpProbePath;\n    }\n\n    public void setLivenessHttpProbePath(String livenessHttpProbePath) {\n        this.livenessHttpProbePath = livenessHttpProbePath;\n    }\n\n    public Integer getLivenessHttpProbePort() {\n        return livenessHttpProbePort;\n    }\n\n    public void setLivenessHttpProbePort(Integer livenessHttpProbePort) {\n        this.livenessHttpProbePort = livenessHttpProbePort;\n    }\n\n    public int getStartupHttpProbeTimeout() {\n        return startupHttpProbeTimeout;\n    }\n\n    public int getStartupHttpProbeFailure() {\n        return startupHttpProbeFailure;\n    }\n\n    public void setStartupHttpProbeFailure(int startupHttpProbeFailure) {\n        this.startupHttpProbeFailure = startupHttpProbeFailure;\n    }\n\n    public void setStartupProbeFailure(int startupHttpProbeFailure) {\n        this.startupHttpProbeFailure = startupHttpProbeFailure;\n    }\n\n    public int getLivenessHttpProbeFailure() {\n        return livenessHttpProbeFailure;\n    }\n\n    public void setLivenessHttpProbeFailure(int livenessHttpProbeFailure) {\n        this.livenessHttpProbeFailure = livenessHttpProbeFailure;\n    }\n\n    public void setLivenessProbeFailure(int livenessHttpProbeFailure) {\n        this.livenessHttpProbeFailure = livenessHttpProbeFailure;\n    }\n\n    public int getLivenessHttpProbeSuccess() {\n        return livenessHttpProbeSuccess;\n    }\n\n    public void setLivenessHttpProbeSuccess(int livenessHttpProbeSuccess) {\n        this.livenessHttpProbeSuccess = livenessHttpProbeSuccess;\n    }\n\n    public void setLivenessProbeSuccess(int livenessHttpProbeSuccess) {\n        this.livenessHttpProbeSuccess = livenessHttpProbeSuccess;\n    }\n\n    public int getStartupHttpProbeSuccess() {\n        return startupHttpProbeSuccess;\n    }\n\n    public void setStartupHttpProbeSuccess(int startupHttpProbeSuccess) {\n        this.startupHttpProbeSuccess = startupHttpProbeSuccess;\n    }\n\n    public void setStartupProbeSuccess(int startupHttpProbeSuccess) {\n        this.startupHttpProbeSuccess = startupHttpProbeSuccess;\n    }\n\n    public int getStartupTcpProbeFailure() {\n        return startupTcpProbeFailure;\n    }\n\n    public void setStartupTcpProbeFailure(int startupTcpProbeFailure) {\n        this.startupTcpProbeFailure = startupTcpProbeFailure;\n    }\n\n    public int getStartupTcpProbeSuccess() {\n        return startupTcpProbeSuccess;\n    }\n\n    public void setStartupTcpProbeSuccess(int startupTcpProbeSuccess) {\n        this.startupTcpProbeSuccess = startupTcpProbeSuccess;\n    }\n\n    public int getStartupCommandProbeFailure() {\n        return startupCommandProbeFailure;\n    }\n\n    public void setStartupCommandProbeFailure(int startupCommandProbeFailure) {\n        this.startupCommandProbeFailure = startupCommandProbeFailure;\n    }\n\n    public int getStartupCommandProbeSuccess() {\n        return startupCommandProbeSuccess;\n    }\n\n    public void setStartupCommandProbeSuccess(int startupCommandProbeSuccess) {\n        this.startupCommandProbeSuccess = startupCommandProbeSuccess;\n    }\n\n    public int getLivenessCommandProbeFailure() {\n        return livenessCommandProbeFailure;\n    }\n\n    public void setLivenessCommandProbeFailure(int livenessCommandProbeFailure) {\n        this.livenessCommandProbeFailure = livenessCommandProbeFailure;\n    }\n\n    public int getLivenessCommandProbeSuccess() {\n        return livenessCommandProbeSuccess;\n    }\n\n    public void setLivenessCommandProbeSuccess(int livenessCommandProbeSuccess) {\n        this.livenessCommandProbeSuccess = livenessCommandProbeSuccess;\n    }\n\n    public int getStartupHttpProbeDelay() {\n        return startupHttpProbeDelay;\n    }\n\n    public void setStartupHttpProbeTimeout(int startupHttpProbeTimeout) {\n        this.startupHttpProbeTimeout = startupHttpProbeTimeout;\n    }\n\n    public void setStartupProbeTimeout(int startupHttpProbeTimeout) {\n        this.startupHttpProbeTimeout = startupHttpProbeTimeout;\n    }\n\n    public void setStartupHttpProbeDelay(int startupHttpProbeDelay) {\n        this.startupHttpProbeDelay = startupHttpProbeDelay;\n    }\n\n    public void setStartupProbeDelay(int startupHttpProbeDelay) {\n        this.startupHttpProbeDelay = startupHttpProbeDelay;\n    }\n\n    public String getStartupHttpProbePath() {\n        return startupHttpProbePath;\n    }\n\n    public void setStartupHttpProbePath(String startupHttpProbePath) {\n        this.startupHttpProbePath = startupHttpProbePath;\n    }\n\n    public Integer getStartupHttpProbePort() {\n        return startupHttpProbePort;\n    }\n\n    public void setStartupHttpProbePort(Integer startupHttpProbePort) {\n        this.startupHttpProbePort = startupHttpProbePort;\n    }\n\n    public void setStartupProbePort(Integer startupHttpProbePort) {\n        this.startupHttpProbePort = startupHttpProbePort;\n    }\n\n    public String getStartupProbeScheme() {\n        return startupProbeScheme;\n    }\n\n    public void setStartupProbeScheme(String startupProbeScheme) {\n        this.startupProbeScheme = startupProbeScheme;\n    }\n\n    public int getStartupHttpProbePeriod() {\n        return startupHttpProbePeriod;\n    }\n\n    public void setStartupHttpProbePeriod(int startupHttpProbePeriod) {\n        this.startupHttpProbePeriod = startupHttpProbePeriod;\n    }\n\n    public int getStartupProbePeriod() {\n        return startupHttpProbePeriod;\n    }\n\n    public void setStartupProbePeriod(int startupHttpProbePeriod) {\n        this.startupHttpProbePeriod = startupHttpProbePeriod;\n    }\n\n    public int getStartupTcpProbeDelay() {\n        return startupTcpProbeDelay;\n    }\n\n    public void setStartupTcpProbeDelay(int startupTcpProbeDelay) {\n        this.startupTcpProbeDelay = startupTcpProbeDelay;\n    }\n\n    public int getStartupTcpProbeTimeout() {\n        return startupTcpProbeTimeout;\n    }\n\n    public void setStartupTcpProbeTimeout(int startupTcpProbeTimeout) {\n        this.startupTcpProbeTimeout = startupTcpProbeTimeout;\n    }\n\t\n\t/**\n\t * Cron configuration for job scheduling\n\t */\n\tprivate CronConfig cron = new CronConfig();\n\n    public int getStartupTcpProbePeriod() {\n        return startupTcpProbePeriod;\n    }\n\n    public void setStartupTcpProbePeriod(int startupTcpProbePeriod) {\n        this.startupTcpProbePeriod = startupTcpProbePeriod;\n    }\n\n    public Integer getStartupTcpProbePort() {\n        return startupTcpProbePort;\n    }\n\n    public void setStartupTcpProbePort(Integer startupTcpProbePort) {\n        this.startupTcpProbePort = startupTcpProbePort;\n    }\n\n    public int getStartupCommandProbeDelay() {\n        return startupCommandProbeDelay;\n    }\n\n    public void setStartupCommandProbeDelay(int startupCommandProbeDelay) {\n        this.startupCommandProbeDelay = startupCommandProbeDelay;\n    }\n\n    public int getStartupCommandProbePeriod() {\n        return startupCommandProbePeriod;\n    }\n\n    public void setStartupCommandProbePeriod(int startupCommandProbePeriod) {\n        this.startupCommandProbePeriod = startupCommandProbePeriod;\n    }\n\n    public String getStartupCommandProbeCommand() {\n        return startupCommandProbeCommand;\n    }\n\n    public void setStartupCommandProbeCommand(String startupCommandProbeCommand) {\n        this.startupCommandProbeCommand = startupCommandProbeCommand;\n    }\n\n    public int getReadinessTcpProbeFailure() {\n        return readinessTcpProbeFailure;\n    }\n\n    public void setReadinessTcpProbeFailure(int readinessTcpProbeFailure) {\n        this.readinessTcpProbeFailure = readinessTcpProbeFailure;\n    }\n\n    public int getReadinessTcpProbeSuccess() {\n        return readinessTcpProbeSuccess;\n    }\n\n    public void setReadinessTcpProbeSuccess(int readinessTcpProbeSuccess) {\n        this.readinessTcpProbeSuccess = readinessTcpProbeSuccess;\n    }\n\n    public int getReadinessHttpProbeSuccess() {\n        return readinessHttpProbeSuccess;\n    }\n\n    public void setReadinessHttpProbeSuccess(int readinessHttpProbeSuccess) {\n        this.readinessHttpProbeSuccess = readinessHttpProbeSuccess;\n    }\n\n    public int getReadinessHttpProbeFailure() {\n        return readinessHttpProbeFailure;\n    }\n\n    public void setReadinessHttpProbeFailure(int readinessHttpProbeFailure) {\n        this.readinessHttpProbeFailure = readinessHttpProbeFailure;\n    }\n\n    public void setReadinessProbeFailure(int readinessHttpProbeFailure) {\n        this.readinessHttpProbeFailure = readinessHttpProbeFailure;\n    }\n\n\n    public int getReadinessCommandProbeFailure() {\n        return readinessCommandProbeFailure;\n    }\n\n    public void setReadinessCommandProbeFailure(int readinessCommandProbeFailure) {\n        this.readinessCommandProbeFailure = readinessCommandProbeFailure;\n    }\n\n    public int getReadinessCommandProbeSuccess() {\n        return readinessCommandProbeSuccess;\n    }\n\n    public void setReadinessCommandProbeSuccess(int readinessCommandProbeSuccess) {\n        this.readinessCommandProbeSuccess = readinessCommandProbeSuccess;\n    }\n\n    public int getReadinessHttpProbeDelay() {\n        return readinessHttpProbeDelay;\n    }\n\n    public void setReadinessHttpProbeDelay(int readinessHttpProbeDelay) {\n        this.readinessHttpProbeDelay = readinessHttpProbeDelay;\n    }\n\n    public int getReadinessHttpProbePeriod() {\n        return readinessHttpProbePeriod;\n    }\n\n    public void setReadinessHttpProbePeriod(int readinessHttpProbePeriod) {\n        this.readinessHttpProbePeriod = readinessHttpProbePeriod;\n    }\n\n    public int getReadinessTcpProbeTimeout() {\n        return readinessTcpProbeTimeout;\n    }\n\n    public void setReadinessTcpProbeTimeout(int readinessTcpProbeTimeout) {\n        this.readinessTcpProbeTimeout = readinessTcpProbeTimeout;\n    }\n\n    public int getReadinessHttpProbeTimeout() {\n        return readinessHttpProbeTimeout;\n    }\n\n    public void setReadinessHttpProbeTimeout(int readinessHttpProbeTimeout) {\n        this.readinessHttpProbeTimeout = readinessHttpProbeTimeout;\n    }\n\n    public String getReadinessHttpProbePath() {\n        return readinessHttpProbePath;\n    }\n\n    public void setReadinessHttpProbePath(String readinessHttpProbePath) {\n        this.readinessHttpProbePath = readinessHttpProbePath;\n    }\n\n    public Integer getReadinessHttpProbePort() {\n        return readinessHttpProbePort;\n    }\n\n    public void setReadinessHttpProbePort(Integer readinessHttpProbePort) {\n        this.readinessHttpProbePort = readinessHttpProbePort;\n    }\n\n    public int getLivenessTcpProbeDelay() {\n        return livenessTcpProbeDelay;\n    }\n\n    public void setLivenessTcpProbeDelay(int livenessTcpProbeDelay) {\n        this.livenessTcpProbeDelay = livenessTcpProbeDelay;\n    }\n\n    public int getLivenessTcpProbePeriod() {\n        return livenessTcpProbePeriod;\n    }\n\n    public void setLivenessTcpProbePeriod(int livenessTcpProbePeriod) {\n        this.livenessTcpProbePeriod = livenessTcpProbePeriod;\n    }\n\n    public Integer getLivenessTcpProbePort() {\n        return livenessTcpProbePort;\n    }\n\n    public void setLivenessTcpProbePort(Integer livenessTcpProbePort) {\n        this.livenessTcpProbePort = livenessTcpProbePort;\n    }\n\n    public int getReadinessTcpProbeDelay() {\n        return readinessTcpProbeDelay;\n    }\n\n    public void setReadinessTcpProbeDelay(int readinessTcpProbeDelay) {\n        this.readinessTcpProbeDelay = readinessTcpProbeDelay;\n    }\n\n    public int getReadinessTcpProbePeriod() {\n        return readinessTcpProbePeriod;\n    }\n\n    public void setReadinessTcpProbePeriod(int readinessTcpProbePeriod) {\n        this.readinessTcpProbePeriod = readinessTcpProbePeriod;\n    }\n\n    public Integer getReadinessTcpProbePort() {\n        return readinessTcpProbePort;\n    }\n\n    public void setReadinessTcpProbePort(Integer readinessTcpProbePort) {\n        this.readinessTcpProbePort = readinessTcpProbePort;\n    }\n\n    public int getReadinessCommandProbeDelay() {\n        return readinessCommandProbeDelay;\n    }\n\n    public void setReadinessCommandProbeDelay(int readinessCommandProbeDelay) {\n        this.readinessCommandProbeDelay = readinessCommandProbeDelay;\n    }\n\n    public int getReadinessCommandProbePeriod() {\n        return readinessCommandProbePeriod;\n    }\n\n    public void setReadinessCommandProbePeriod(int readinessCommandProbePeriod) {\n        this.readinessCommandProbePeriod = readinessCommandProbePeriod;\n    }\n\n    public String getReadinessCommandProbeCommand() {\n        return readinessCommandProbeCommand;\n    }\n\n    public void setReadinessCommandProbeCommand(String readinessCommandProbeCommand) {\n        this.readinessCommandProbeCommand = readinessCommandProbeCommand;\n    }\n\n    public int getLivenessCommandProbeDelay() {\n        return livenessCommandProbeDelay;\n    }\n\n    public void setLivenessCommandProbeDelay(int livenessCommandProbeDelay) {\n        this.livenessCommandProbeDelay = livenessCommandProbeDelay;\n    }\n\n    public int getLivenessCommandProbePeriod() {\n        return livenessCommandProbePeriod;\n    }\n\n    public void setLivenessCommandProbePeriod(int livenessCommandProbePeriod) {\n        this.livenessCommandProbePeriod = livenessCommandProbePeriod;\n    }\n\n    public String getLivenessCommandProbeCommand() {\n        return livenessCommandProbeCommand;\n    }\n\n    public void setLivenessCommandProbeCommand(String livenessCommandProbeCommand) {\n        this.livenessCommandProbeCommand = livenessCommandProbeCommand;\n    }\n\n    public String getProbeCredentialsSecret() {\n        return probeCredentialsSecret;\n    }\n\n    public void setProbeCredentialsSecret(String probeCredentialsSecret) {\n        this.probeCredentialsSecret = probeCredentialsSecret;\n    }\n\n    public ProbeType getProbeType() {\n        return probeType;\n    }\n\n    public void setProbeType(ProbeType probeType) {\n        this.probeType = probeType;\n    }\n\n    public StatefulSet getStatefulSet() {\n        return statefulSet;\n    }\n\n    public void setStatefulSet(\n            StatefulSet statefulSet) {\n        this.statefulSet = statefulSet;\n    }\n\n    public List<Toleration> getTolerations() {\n        return tolerations;\n    }\n\n    public void setTolerations(List<Toleration> tolerations) {\n        this.tolerations = tolerations;\n    }\n\n    public List<SecretKeyRef> getSecretKeyRefs() {\n        return secretKeyRefs;\n    }\n\n    public void setSecretKeyRefs(List<SecretKeyRef> secretKeyRefs) {\n        this.secretKeyRefs = secretKeyRefs;\n    }\n\n    public List<ConfigMapKeyRef> getConfigMapKeyRefs() {\n        return configMapKeyRefs;\n    }\n\n    public void setConfigMapKeyRefs(List<ConfigMapKeyRef> configMapKeyRefs) {\n        this.configMapKeyRefs = configMapKeyRefs;\n    }\n\n    public List<String> getConfigMapRefs() {\n        return configMapRefs;\n    }\n\n    public void setConfigMapRefs(List<String> configMapRefs) {\n        this.configMapRefs = configMapRefs;\n    }\n\n    public List<String> getSecretRefs() {\n        return secretRefs;\n    }\n\n    public void setSecretRefs(List<String> secretRefs) {\n        this.secretRefs = secretRefs;\n    }\n\n    public String[] getEnvironmentVariables() {\n        return environmentVariables;\n    }\n\n    public void setEnvironmentVariables(String[] environmentVariables) {\n        this.environmentVariables = environmentVariables;\n    }\n\n    public EntryPointStyle getEntryPointStyle() {\n        return entryPointStyle;\n    }\n\n    public void setEntryPointStyle(EntryPointStyle entryPointStyle) {\n        this.entryPointStyle = entryPointStyle;\n    }\n\n    public boolean isCreateLoadBalancer() {\n        return createLoadBalancer;\n    }\n\n    public void setCreateLoadBalancer(boolean createLoadBalancer) {\n        this.createLoadBalancer = createLoadBalancer;\n    }\n\n    public String getServiceAnnotations() {\n        return serviceAnnotations;\n    }\n\n    public void setServiceAnnotations(String serviceAnnotations) {\n        this.serviceAnnotations = serviceAnnotations;\n    }\n\n    public String getPodAnnotations() {\n        return podAnnotations;\n    }\n\n    public void setPodAnnotations(String podAnnotations) {\n        this.podAnnotations = podAnnotations;\n    }\n\n    public String getJobAnnotations() {\n        return jobAnnotations;\n    }\n\n    public void setJobAnnotations(String jobAnnotations) {\n        this.jobAnnotations = jobAnnotations;\n    }\n\n    public int getMinutesToWaitForLoadBalancer() {\n        return minutesToWaitForLoadBalancer;\n    }\n\n    public void setMinutesToWaitForLoadBalancer(int minutesToWaitForLoadBalancer) {\n        this.minutesToWaitForLoadBalancer = minutesToWaitForLoadBalancer;\n    }\n\n    public int getMaxTerminatedErrorRestarts() {\n        return maxTerminatedErrorRestarts;\n    }\n\n    public void setMaxTerminatedErrorRestarts(int maxTerminatedErrorRestarts) {\n        this.maxTerminatedErrorRestarts = maxTerminatedErrorRestarts;\n    }\n\n    public int getMaxCrashLoopBackOffRestarts() {\n        return maxCrashLoopBackOffRestarts;\n    }\n\n    public void setMaxCrashLoopBackOffRestarts(int maxCrashLoopBackOffRestarts) {\n        this.maxCrashLoopBackOffRestarts = maxCrashLoopBackOffRestarts;\n    }\n\n    public ImagePullPolicy getImagePullPolicy() {\n        return imagePullPolicy;\n    }\n\n    public void setImagePullPolicy(ImagePullPolicy imagePullPolicy) {\n        this.imagePullPolicy = imagePullPolicy;\n    }\n\n    public LimitsResources getLimits() {\n        return limits;\n    }\n\n    public void setLimits(LimitsResources limits) {\n        this.limits = limits;\n    }\n\n    public RequestsResources getRequests() {\n        return requests;\n    }\n\n    public void setRequests(RequestsResources requests) {\n        this.requests = requests;\n    }\n\n    public List<VolumeMount> getVolumeMounts() {\n        return volumeMounts;\n    }\n\n    public void setVolumeMounts(List<VolumeMount> volumeMounts) {\n        this.volumeMounts = volumeMounts;\n    }\n\n    public List<Volume> getVolumes() {\n        return volumes;\n    }\n\n    public void setVolumes(List<Volume> volumes) {\n        this.volumes = volumes;\n    }\n\n    public boolean isHostNetwork() {\n        return hostNetwork;\n    }\n\n    public void setHostNetwork(boolean hostNetwork) {\n        this.hostNetwork = hostNetwork;\n    }\n\n    public boolean isCreateJob() {\n        return createJob;\n    }\n\n    public void setCreateJob(boolean createJob) {\n        this.createJob = createJob;\n    }\n\n    public String getDeploymentServiceAccountName() {\n        return deploymentServiceAccountName;\n    }\n\n    public void setDeploymentServiceAccountName(String deploymentServiceAccountName) {\n        this.deploymentServiceAccountName = deploymentServiceAccountName;\n    }\n\n    public int getMaximumConcurrentTasks() {\n        return maximumConcurrentTasks;\n    }\n\n    public void setMaximumConcurrentTasks(int maximumConcurrentTasks) {\n        this.maximumConcurrentTasks = maximumConcurrentTasks;\n    }\n\n\tpublic Long getTerminationGracePeriodSeconds() {\n\t\treturn terminationGracePeriodSeconds;\n\t}\n\n\tpublic void setTerminationGracePeriodSeconds(Long terminationGracePeriodSeconds) {\n\t\tthis.terminationGracePeriodSeconds = terminationGracePeriodSeconds;\n\t}\n\n\tpublic void setNodeSelector(String nodeSelector) {\n        this.nodeSelector = nodeSelector;\n    }\n\n    public String getNodeSelector() {\n        return nodeSelector;\n    }\n\n    public void setPodSecurityContext(PodSecurityContext podSecurityContext) {\n        this.podSecurityContext = podSecurityContext;\n    }\n\n    public PodSecurityContext getPodSecurityContext() {\n        return podSecurityContext;\n    }\n\n    public void setContainerSecurityContext(ContainerSecurityContext containerSecurityContext) {\n        this.containerSecurityContext = containerSecurityContext;\n    }\n\n    public ContainerSecurityContext getContainerSecurityContext() {\n        return containerSecurityContext;\n    }\n\n    public NodeAffinity getNodeAffinity() {\n        return nodeAffinity;\n    }\n\n    public void setNodeAffinity(NodeAffinity nodeAffinity) {\n        this.nodeAffinity = nodeAffinity;\n    }\n\n    public PodAffinity getPodAffinity() {\n        return podAffinity;\n    }\n\n    public void setPodAffinity(PodAffinity podAffinity) {\n        this.podAffinity = podAffinity;\n    }\n\n    public PodAntiAffinity getPodAntiAffinity() {\n        return podAntiAffinity;\n    }\n\n    public void setPodAntiAffinity(PodAntiAffinity podAntiAffinity) {\n        this.podAntiAffinity = podAntiAffinity;\n    }\n\n    public String getStatefulSetInitContainerImageName() {\n        return statefulSetInitContainerImageName;\n    }\n\n    public void setStatefulSetInitContainerImageName(String statefulSetInitContainerImageName) {\n        this.statefulSetInitContainerImageName = statefulSetInitContainerImageName;\n    }\n\n    public InitContainer getInitContainer() {\n        return initContainer;\n    }\n\n    public void setInitContainer(InitContainer initContainer) {\n        this.initContainer = initContainer;\n    }\n\n    public List<InitContainer> getInitContainers() {\n        return initContainers;\n    }\n\n    public void setInitContainers(List<InitContainer> initContainers) {\n        this.initContainers = initContainers;\n    }\n\n    public List<Container> getAdditionalContainers() {\n        return this.additionalContainers;\n    }\n\n    public void setAdditionalContainers(List<Container> additionalContainers) {\n        this.additionalContainers = additionalContainers;\n    }\n\n    public String getLivenessHttpProbeScheme() {\n        return livenessHttpProbeScheme;\n    }\n\n    public void setLivenessHttpProbeScheme(String livenessHttpProbeScheme) {\n        this.livenessHttpProbeScheme = livenessHttpProbeScheme;\n    }\n\n    public String getReadinessHttpProbeScheme() {\n        return readinessHttpProbeScheme;\n    }\n\n    public void setReadinessHttpProbeScheme(String readinessHttpProbeScheme) {\n        this.readinessHttpProbeScheme = readinessHttpProbeScheme;\n    }\n\n    Lifecycle getLifecycle() {\n        return lifecycle;\n    }\n\n    void setLifecycle(Lifecycle lifecycle) {\n        this.lifecycle = lifecycle;\n    }\n\n    public String getDeploymentLabels() {\n        return deploymentLabels;\n    }\n\n    public void setDeploymentLabels(String deploymentLabels) {\n        this.deploymentLabels = deploymentLabels;\n    }\n\n    public AppAdmin getAppAdmin() {\n        return appAdmin;\n    }\n\n    public void setAppAdmin(AppAdmin appAdmin) {\n        this.appAdmin = appAdmin;\n    }\n\n\tpublic CronConfig getCron() {\n\t\treturn cron;\n\t}\n\n\tpublic void setCron(CronConfig cron) {\n\t\tthis.cron = cron;\n\t}\n}\n"
  },
  {
    "path": "spring-cloud-deployer-kubernetes/src/main/java/org/springframework/cloud/deployer/spi/kubernetes/KubernetesScheduler.java",
    "content": "/*\n * Copyright 2018-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.kubernetes;\n\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.regex.Pattern;\nimport java.util.stream.Collectors;\n\nimport io.fabric8.kubernetes.api.model.LocalObjectReference;\nimport io.fabric8.kubernetes.api.model.PodSpec;\nimport io.fabric8.kubernetes.api.model.StatusCause;\nimport io.fabric8.kubernetes.api.model.StatusDetails;\nimport io.fabric8.kubernetes.api.model.batch.v1.CronJob;\nimport io.fabric8.kubernetes.api.model.batch.v1.CronJobBuilder;\nimport io.fabric8.kubernetes.api.model.batch.v1.CronJobList;\nimport io.fabric8.kubernetes.client.KubernetesClient;\nimport io.fabric8.kubernetes.client.KubernetesClientException;\n\nimport io.fabric8.kubernetes.client.dsl.internal.batch.v1.JobOperationsImpl;\nimport org.springframework.cloud.deployer.spi.scheduler.CreateScheduleException;\nimport org.springframework.cloud.deployer.spi.scheduler.ScheduleInfo;\nimport org.springframework.cloud.deployer.spi.scheduler.ScheduleRequest;\nimport org.springframework.cloud.deployer.spi.scheduler.Scheduler;\nimport org.springframework.cloud.deployer.spi.scheduler.SchedulerException;\nimport org.springframework.cloud.deployer.spi.scheduler.SchedulerPropertyKeys;\nimport org.springframework.util.Assert;\nimport org.springframework.util.CollectionUtils;\nimport org.springframework.util.StringUtils;\n\n/**\n * Kubernetes implementation of the {@link Scheduler} SPI.\n *\n * @author Chris Schaefer\n * @author Ilayaperumal Gopinathan\n * @author Corneil du Plessis\n */\npublic class KubernetesScheduler extends AbstractKubernetesDeployer implements Scheduler {\n\tprotected static final String SPRING_CRONJOB_ID_KEY = \"spring-cronjob-id\";\n\n\tprivate static final String SCHEDULE_EXPRESSION_FIELD_NAME = \"spec.schedule\";\n\n\tstatic final String KUBERNETES_DEPLOYER_CRON_CONCURRENCY_POLICY = KubernetesDeployerProperties.KUBERNETES_DEPLOYER_PROPERTIES_PREFIX + \".cron.concurrencyPolicy\";\n\n\tstatic final String KUBERNETES_DEPLOYER_CRON_TTL_SECONDS_AFTER_FINISHED = KubernetesDeployerProperties.KUBERNETES_DEPLOYER_PROPERTIES_PREFIX + \".cron.ttlSecondsAfterFinished\";\n\n\tstatic final String KUBERNETES_DEPLOYER_CRON_BACKOFF_LIMIT = KubernetesDeployerProperties.KUBERNETES_DEPLOYER_PROPERTIES_PREFIX + \".cron.backoffLimit\";\n\n\tpublic KubernetesScheduler(KubernetesClient client,\n\t\t\tKubernetesDeployerProperties properties) {\n\t\tAssert.notNull(client, \"KubernetesClient must not be null\");\n\t\tAssert.notNull(properties, \"KubernetesSchedulerProperties must not be null\");\n\n\t\tthis.client = client;\n\t\tthis.properties = properties;\n\t\tthis.containerFactory = new DefaultContainerFactory(properties);\n\t\tthis.deploymentPropertiesResolver = new DeploymentPropertiesResolver(\n\t\t\t\t(properties instanceof KubernetesSchedulerProperties) ?\n\t\t\t\t\t\tKubernetesSchedulerProperties.KUBERNETES_SCHEDULER_PROPERTIES_PREFIX :\n\t\t\t\t\t\tKubernetesDeployerProperties.KUBERNETES_DEPLOYER_PROPERTIES_PREFIX, properties);\n\t}\n\n\t@Override\n\tpublic void schedule(ScheduleRequest scheduleRequest) {\n\n\t\tvalidateScheduleName(scheduleRequest);\n\t\ttry {\n\t\t\tcreateCronJob(scheduleRequest);\n\t\t}\n\t\tcatch (KubernetesClientException e) {\n\t\t\tString invalidCronExceptionMessage = getExceptionMessageForField(e, SCHEDULE_EXPRESSION_FIELD_NAME);\n\n\t\t\tif (StringUtils.hasText(invalidCronExceptionMessage)) {\n\t\t\t\tthrow new CreateScheduleException(invalidCronExceptionMessage, e);\n\t\t\t}\n\n\t\t\tthrow new CreateScheduleException(\"Failed to create schedule \" + scheduleRequest.getScheduleName(), e);\n\t\t}\n\t}\n\n\t/**\n\t * Merge the Deployment properties into Scheduler properties.\n\t * This way, the CronJob's scheduler properties are updated with the deployer properties if set any.\n\t * @param scheduleRequest the {@link ScheduleRequest}\n\t * @return the merged schedule properties\n\t */\n\tstatic Map<String, String> mergeSchedulerProperties(ScheduleRequest scheduleRequest) {\n\t\tMap<String, String> deploymentProperties = new HashMap<>();\n\t\tMap<String, String> schedulerProperties = scheduleRequest.getSchedulerProperties();\n\t\tif(scheduleRequest.getDeploymentProperties() != null) {\n\t\t\tdeploymentProperties.putAll(scheduleRequest.getDeploymentProperties());\n\t\t}\n\t\tif (schedulerProperties != null) {\n\t\t\tfor (Map.Entry<String, String> schedulerProperty : schedulerProperties.entrySet()) {\n\t\t\t\tString schedulerPropertyKey = schedulerProperty.getKey();\n\t\t\t\tif (StringUtils.hasText(schedulerPropertyKey) && schedulerPropertyKey.startsWith(KubernetesSchedulerProperties.KUBERNETES_SCHEDULER_PROPERTIES_PREFIX)) {\n\t\t\t\t\tString deployerPropertyKey = KubernetesDeployerProperties.KUBERNETES_DEPLOYER_PROPERTIES_PREFIX +\n\t\t\t\t\t\t\tschedulerPropertyKey.substring(KubernetesSchedulerProperties.KUBERNETES_SCHEDULER_PROPERTIES_PREFIX.length());\n\t\t\t\t\tdeploymentProperties.put(deployerPropertyKey, schedulerProperty.getValue());\n\t\t\t\t}\n\t\t\t\telse if(StringUtils.hasText(schedulerPropertyKey) && schedulerPropertyKey.startsWith(SchedulerPropertyKeys.PREFIX)) {\n\t\t\t\t\tif (!deploymentProperties.containsKey(schedulerPropertyKey)) {\n\t\t\t\t\t\tdeploymentProperties.put(schedulerPropertyKey, schedulerProperty.getValue());\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif(!deploymentProperties.containsKey(KubernetesDeployerProperties.KUBERNETES_DEPLOYER_PROPERTIES_PREFIX + \".restartPolicy\")) {\n\t\t\tdeploymentProperties.put(KubernetesDeployerProperties.KUBERNETES_DEPLOYER_PROPERTIES_PREFIX + \".restartPolicy\", RestartPolicy.Never.name());\n\t\t}\n\t\tif(deploymentProperties.containsKey(\"spring.cloud.deployer.cron.expression\")) {\n\t\t\tdeploymentProperties.put(KubernetesDeployerProperties.KUBERNETES_DEPLOYER_PROPERTIES_PREFIX + \".cron.expression\", deploymentProperties.get(\"spring.cloud.deployer.cron.expression\"));\n\t\t}\n\t\tMap<String, String> updatedDeploymentProperties = new HashMap<>();\n\t\tMap<String, String> updatedSchedulerProperties = new HashMap<>();\n\t\tfor (Map.Entry<String, String> schedulerProperty : deploymentProperties.entrySet()) {\n\t\t\tString schedulerPropertyKey = schedulerProperty.getKey();\n\t\t\tif (StringUtils.hasText(schedulerPropertyKey) && schedulerPropertyKey.startsWith(KubernetesSchedulerProperties.KUBERNETES_SCHEDULER_PROPERTIES_PREFIX)) {\n\t\t\t\tString deployerPropertyKey = KubernetesDeployerProperties.KUBERNETES_DEPLOYER_PROPERTIES_PREFIX +\n\t\t\t\t\t\tschedulerPropertyKey.substring(KubernetesSchedulerProperties.KUBERNETES_SCHEDULER_PROPERTIES_PREFIX.length());\n\t\t\t\tupdatedSchedulerProperties.put(deployerPropertyKey, schedulerProperty.getValue());\n\t\t\t}\n\t\t\telse {\n\t\t\t\tupdatedDeploymentProperties.put(schedulerProperty.getKey(), schedulerProperty.getValue());\n\t\t\t}\n\t\t}\n\t\tdeploymentProperties.clear();\n\t\tdeploymentProperties.putAll(updatedDeploymentProperties);\n\t\tdeploymentProperties.putAll(updatedSchedulerProperties);\n\t\treturn deploymentProperties;\n\t}\n\n\tpublic void validateScheduleName(ScheduleRequest request) {\n\t\tif(request.getScheduleName() == null) {\n\t\t\tthrow new CreateScheduleException(\"The name for the schedule request is null\", null);\n\t\t}\n\t\tif(request.getScheduleName().length() > 52) {\n\t\t\tthrow new CreateScheduleException(String.format(\"because Schedule Name: '%s' has too many characters.  Schedule name length must be 52 characters or less\", request.getScheduleName()), null);\n\t\t}\n\t\tif(!Pattern.matches(\"^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\", request.getScheduleName())) {\n\t\t\tthrow new CreateScheduleException(\"Invalid Format for Schedule Name. Schedule name can only contain lowercase letters, numbers 0-9 and hyphens.\", null);\n\t\t}\n\n\t}\n\n\t@Override\n\tpublic void unschedule(String scheduleName) {\n\t\tList<StatusDetails> unscheduled = this.client.batch().v1().cronjobs().withName(scheduleName).delete();\n\n\t\tif (unscheduled == null || unscheduled.isEmpty()) {\n\t\t\tthrow new SchedulerException(\"Failed to unschedule \" + scheduleName);\n\t\t}\n\t}\n\n\t@Override\n\tpublic List<ScheduleInfo> list(String taskDefinitionName) {\n\t\treturn list()\n\t\t\t\t.stream()\n\t\t\t\t.filter(scheduleInfo -> taskDefinitionName.equals(scheduleInfo.getTaskDefinitionName()))\n\t\t\t\t.collect(Collectors.toList());\n\t}\n\n\t@Override\n\tpublic List<ScheduleInfo> list() {\n\t\tCronJobList cronJobList = this.client.batch().v1().cronjobs().list();\n\n\t\tList<CronJob> cronJobs = cronJobList.getItems();\n\t\tList<ScheduleInfo> scheduleInfos = new ArrayList<>();\n\n\t\tfor (CronJob cronJob : cronJobs) {\n\t\t\tif (cronJob.getMetadata() != null && cronJob.getMetadata().getLabels() != null &&\n\t\t\t\t\tStringUtils.hasText(cronJob.getMetadata().getLabels().get(SPRING_CRONJOB_ID_KEY))) {\n\t\t\t\tMap<String, String> properties = new HashMap<>();\n\t\t\t\tproperties.put(SchedulerPropertyKeys.CRON_EXPRESSION, cronJob.getSpec().getSchedule());\n\n\t\t\t\tScheduleInfo scheduleInfo = new ScheduleInfo();\n\t\t\t\tscheduleInfo.setScheduleName(cronJob.getMetadata().getName());\n\t\t\t\tscheduleInfo.setTaskDefinitionName(cronJob.getMetadata().getLabels().get(SPRING_CRONJOB_ID_KEY));\n\t\t\t\tscheduleInfo.setScheduleProperties(properties);\n\n\t\t\t\tscheduleInfos.add(scheduleInfo);\n\t\t\t}\n\t\t}\n\n\t\treturn scheduleInfos;\n\t}\n\n\tprotected CronJob createCronJob(ScheduleRequest scheduleRequest) {\n\t\tMap<String, String> labels = new HashMap<>();\n\t\tlabels.put(SPRING_CRONJOB_ID_KEY, scheduleRequest.getDefinition().getName());\n\n\t\tMap<String, String> schedulerProperties = mergeSchedulerProperties(scheduleRequest);\n\n\t\tString schedule = schedulerProperties.get(\"spring.cloud.deployer.kubernetes.cron.expression\") != null ?\n\t\t\t\tschedulerProperties.get(\"spring.cloud.deployer.kubernetes.cron.expression\") :\n\t\t\t\tschedulerProperties.get(SchedulerPropertyKeys.CRON_EXPRESSION);\n\t\tAssert.hasText(schedule, \"The property spring.cloud.deployer.cron.expression must be defined\");\n\n\t\tString concurrencyPolicy = schedulerProperties.get(KUBERNETES_DEPLOYER_CRON_CONCURRENCY_POLICY);\n\t\t// check default server properties\n\t\tif (!StringUtils.hasText(concurrencyPolicy)) {\n\t\t\tconcurrencyPolicy = this.properties.getCron().getConcurrencyPolicy();\n\t\t}\n\t\tif (concurrencyPolicy == null) {\n\t\t\tconcurrencyPolicy = \"Allow\";\n\t\t}\n\n\t\tfinal Integer ttlSecondsAfterFinished;\n\t\tString ttlSecondsAfterFinishedString = schedulerProperties.get(KUBERNETES_DEPLOYER_CRON_TTL_SECONDS_AFTER_FINISHED);\n\t\tif (StringUtils.hasText(ttlSecondsAfterFinishedString)) {\n\t\t\tttlSecondsAfterFinished = Integer.parseInt(ttlSecondsAfterFinishedString);\n\t\t}\n\t\telse {\n\t\t\tttlSecondsAfterFinished = this.properties.getCron().getTtlSecondsAfterFinished();\n\t\t}\n\n\t\tfinal Integer backoffLimit;\n\t\tString backoffLimitString = schedulerProperties.get(KUBERNETES_DEPLOYER_CRON_BACKOFF_LIMIT);\n\t\tif (StringUtils.hasText(backoffLimitString)) {\n\t\t\tbackoffLimit = Integer.parseInt(backoffLimitString);\n\t\t}\n\t\telse {\n\t\t\tbackoffLimit = this.properties.getCron().getBackoffLimit();\n\t\t}\n\n\t\tPodSpec podSpec = createPodSpec(new ScheduleRequest(scheduleRequest.getDefinition(),schedulerProperties, scheduleRequest.getCommandlineArguments(), scheduleRequest.getScheduleName(),scheduleRequest.getResource()));\n\t\tString taskServiceAccountName = this.deploymentPropertiesResolver.getTaskServiceAccountName(schedulerProperties);\n\t\ttaskServiceAccountName = taskServiceAccountName != null ? taskServiceAccountName : KubernetesDeployerProperties.DEFAULT_TASK_SERVICE_ACCOUNT_NAME;\n\t\tif (StringUtils.hasText(taskServiceAccountName)) {\n\t\t\tpodSpec.setServiceAccountName(taskServiceAccountName);\n\t\t}\n\t\tMap<String, String> annotations = this.deploymentPropertiesResolver.getPodAnnotations(schedulerProperties);\n\t\tlabels.putAll(this.deploymentPropertiesResolver.getDeploymentLabels(schedulerProperties));\n\n\t\tCronJob cronJob = new CronJobBuilder()\n\t\t\t\t.withNewMetadata()\n\t\t\t\t\t.withName(scheduleRequest.getScheduleName())\n\t\t\t\t\t.withLabels(labels)\n\t\t\t\t\t.withAnnotations(this.deploymentPropertiesResolver.getJobAnnotations(schedulerProperties))\n\t\t\t\t.endMetadata()\n\t\t\t\t.withNewSpec()\n\t\t\t\t\t.withSchedule(schedule)\n\t\t\t\t\t\t.withConcurrencyPolicy(concurrencyPolicy)\n\t\t\t\t\t\t.withNewJobTemplate()\n\t\t\t\t\t\t\t.withNewSpec()\n\t\t\t\t\t\t\t\t.withBackoffLimit(backoffLimit)\n\t\t\t\t\t\t\t\t.withTtlSecondsAfterFinished(ttlSecondsAfterFinished)\n\t\t\t\t\t\t\t\t.withNewTemplate()\n\t\t\t\t\t\t\t\t\t.withNewMetadata()\n\t\t\t\t\t\t\t\t\t\t.addToAnnotations(annotations).addToLabels(labels)\n\t\t\t\t\t\t\t\t\t.endMetadata()\n\t\t\t\t\t\t\t\t\t.withSpec(podSpec)\n\t\t\t\t\t\t\t\t.endTemplate()\n\t\t\t\t\t\t\t.endSpec()\n\t\t\t\t\t\t.endJobTemplate()\n\t\t\t\t.endSpec()\n\t\t\t\t.build();\n\n\t\tsetImagePullSecret(scheduleRequest, cronJob);\n\t\tJobOperationsImpl jobOperations = new JobOperationsImpl(this.client);\n\t\treturn this.client.batch().v1().cronjobs().inNamespace(jobOperations.getNamespace()).resource(cronJob).create();\n\t}\n\n\tprotected String getExceptionMessageForField(KubernetesClientException clientException,\n\t\t\tString fieldName) {\n\t\tif (clientException.getStatus() == null || clientException.getStatus().getDetails() == null) {\n\t\t\treturn null;\n\t\t}\n\t\tList<StatusCause> statusCauses = clientException.getStatus().getDetails().getCauses();\n\n\t\tif (!CollectionUtils.isEmpty(statusCauses)) {\n\t\t\tfor (StatusCause statusCause : statusCauses) {\n\t\t\t\tif (fieldName.equals(statusCause.getField())) {\n\t\t\t\t\treturn clientException.getStatus().getMessage();\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn null;\n\t}\n\n\tprivate void setImagePullSecret(ScheduleRequest scheduleRequest, CronJob cronJob) {\n\n\t\tString imagePullSecret = this.deploymentPropertiesResolver.getImagePullSecret(scheduleRequest.getDeploymentProperties());\n\n\t\tif (StringUtils.hasText(imagePullSecret)) {\n\t\t\tLocalObjectReference localObjectReference = new LocalObjectReference();\n\t\t\tlocalObjectReference.setName(imagePullSecret);\n\n\t\t\tcronJob.getSpec().getJobTemplate().getSpec().getTemplate().getSpec().getImagePullSecrets()\n\t\t\t\t\t.add(localObjectReference);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "spring-cloud-deployer-kubernetes/src/main/java/org/springframework/cloud/deployer/spi/kubernetes/KubernetesSchedulerProperties.java",
    "content": "/*\n * Copyright 2018-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.kubernetes;\n\nimport org.springframework.boot.context.properties.ConfigurationProperties;\n\n/**\n * Configuration properties for the Kubernetes Scheduler.\n *\n * @author Chris Schaefer\n */\n@Deprecated\n@ConfigurationProperties(prefix = KubernetesSchedulerProperties.KUBERNETES_SCHEDULER_PROPERTIES_PREFIX)\npublic class KubernetesSchedulerProperties extends KubernetesDeployerProperties {\n\t/**\n\t * Namespace to use for Kubernetes Scheduler properties.\n\t */\n\tpublic static final String KUBERNETES_SCHEDULER_PROPERTIES_PREFIX = \"spring.cloud.scheduler.kubernetes\";\n\n\t/**\n\t * The {@link RestartPolicy} to use. Defaults to {@link RestartPolicy#Never}.\n\t */\n\tprivate RestartPolicy restartPolicy = RestartPolicy.Never;\n\n\t/**\n\t * The default service account name to use for tasks.\n\t */\n\tprotected static final String DEFAULT_TASK_SERVICE_ACCOUNT_NAME = \"default\";\n\n\t/**\n\t * Service account name to use for tasks, defaults to:\n\t * {@link KubernetesSchedulerProperties#DEFAULT_TASK_SERVICE_ACCOUNT_NAME}\n\t */\n\tprivate String taskServiceAccountName = DEFAULT_TASK_SERVICE_ACCOUNT_NAME;\n\n\t/**\n\t * Obtains the {@link RestartPolicy} to use. Defaults to\n\t * {@link KubernetesSchedulerProperties#restartPolicy}.\n\t *\n\t * @return the {@link RestartPolicy} to use\n\t */\n\tpublic RestartPolicy getRestartPolicy() {\n\t\treturn restartPolicy;\n\t}\n\n\t/**\n\t * Sets the {@link RestartPolicy} to use.\n\t *\n\t * @param restartPolicy the {@link RestartPolicy} to use\n\t */\n\tpublic void setRestartPolicy(RestartPolicy restartPolicy) {\n\t\tthis.restartPolicy = restartPolicy;\n\t}\n\n\t/**\n\t * Obtains the service account name to use for tasks.\n\t *\n\t * @return the service account name\n\t */\n\tpublic String getTaskServiceAccountName() {\n\t\treturn taskServiceAccountName;\n\t}\n\n\t/**\n\t * Sets the service account name to use for tasks.\n\t *\n\t * @param taskServiceAccountName the service account name\n\t */\n\tpublic void setTaskServiceAccountName(String taskServiceAccountName) {\n\t\tthis.taskServiceAccountName = taskServiceAccountName;\n\t}\n}\n"
  },
  {
    "path": "spring-cloud-deployer-kubernetes/src/main/java/org/springframework/cloud/deployer/spi/kubernetes/KubernetesTaskLauncher.java",
    "content": "/*\n * Copyright 2016-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.kubernetes;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Locale;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.concurrent.ThreadLocalRandom;\nimport java.util.concurrent.atomic.AtomicInteger;\n\nimport io.fabric8.kubernetes.api.model.Container;\nimport io.fabric8.kubernetes.api.model.HasMetadata;\nimport io.fabric8.kubernetes.api.model.KubernetesResourceList;\nimport io.fabric8.kubernetes.api.model.ObjectMeta;\nimport io.fabric8.kubernetes.api.model.ObjectMetaBuilder;\nimport io.fabric8.kubernetes.api.model.Pod;\nimport io.fabric8.kubernetes.api.model.PodBuilder;\nimport io.fabric8.kubernetes.api.model.PodList;\nimport io.fabric8.kubernetes.api.model.PodSpec;\nimport io.fabric8.kubernetes.api.model.PodStatus;\nimport io.fabric8.kubernetes.api.model.PodTemplateSpec;\nimport io.fabric8.kubernetes.api.model.StatusDetails;\nimport io.fabric8.kubernetes.api.model.batch.v1.Job;\nimport io.fabric8.kubernetes.api.model.batch.v1.JobBuilder;\nimport io.fabric8.kubernetes.api.model.batch.v1.JobList;\nimport io.fabric8.kubernetes.api.model.batch.v1.JobSpec;\nimport io.fabric8.kubernetes.api.model.batch.v1.JobSpecBuilder;\nimport io.fabric8.kubernetes.api.model.batch.v1.JobStatus;\nimport io.fabric8.kubernetes.client.KubernetesClient;\nimport io.fabric8.kubernetes.client.KubernetesClientException;\nimport io.fabric8.kubernetes.client.dsl.FilterWatchListDeletable;\nimport io.fabric8.kubernetes.client.dsl.PodResource;\nimport io.fabric8.kubernetes.client.dsl.ScalableResource;\nimport io.fabric8.kubernetes.client.dsl.internal.batch.v1.JobOperationsImpl;\nimport io.fabric8.kubernetes.client.dsl.internal.core.v1.PodOperationsImpl;\nimport org.hashids.Hashids;\n\nimport org.springframework.cloud.deployer.spi.core.AppDeploymentRequest;\nimport org.springframework.cloud.deployer.spi.core.RuntimeEnvironmentInfo;\nimport org.springframework.cloud.deployer.spi.kubernetes.support.PropertyParserUtils;\nimport org.springframework.cloud.deployer.spi.task.LaunchState;\nimport org.springframework.cloud.deployer.spi.task.TaskLauncher;\nimport org.springframework.cloud.deployer.spi.task.TaskStatus;\nimport org.springframework.util.Assert;\nimport org.springframework.util.CollectionUtils;\nimport org.springframework.util.ObjectUtils;\nimport org.springframework.util.StringUtils;\n\n/**\n * A task launcher that targets Kubernetes.\n *\n * @author Thomas Risberg\n * @author David Turanski\n * @author Leonardo Diniz\n * @author Chris Schaefer\n * @author Ilayaperumal Gopinathan\n * @author Glenn Renfro\n * @author Corneil du Plessis\n */\npublic class KubernetesTaskLauncher extends AbstractKubernetesDeployer implements TaskLauncher {\n\tprivate final KubernetesTaskLauncherProperties taskLauncherProperties;\n\n\tpublic KubernetesTaskLauncher(KubernetesDeployerProperties properties,\n\t\t\t\t\t\t\t\t  KubernetesClient client) {\n\t\tthis(properties, new KubernetesTaskLauncherProperties(), client, new DefaultContainerFactory(properties));\n\t}\n\n\n\tpublic KubernetesTaskLauncher(KubernetesDeployerProperties properties,\n\t\t\t\t\t\t\t\t  KubernetesClient client, ContainerFactory containerFactory) {\n\t\tthis(properties, new KubernetesTaskLauncherProperties(), client, containerFactory);\n\t}\n\n\tpublic KubernetesTaskLauncher(KubernetesDeployerProperties deployerProperties,\n\t\t\t\t\t\t\t\t  KubernetesTaskLauncherProperties taskLauncherProperties, KubernetesClient client) {\n\t\tthis(deployerProperties, taskLauncherProperties, client, new DefaultContainerFactory(deployerProperties));\n\t}\n\n\tpublic KubernetesTaskLauncher(KubernetesDeployerProperties kubernetesDeployerProperties,\n\t\t\t\t\t\t\t\t  KubernetesTaskLauncherProperties taskLauncherProperties,\n\t\t\t\t\t\t\t\t  KubernetesClient client, ContainerFactory containerFactory) {\n\t\tthis.properties = kubernetesDeployerProperties;\n\t\tthis.taskLauncherProperties = taskLauncherProperties;\n\t\tthis.client = client;\n\t\tthis.containerFactory = containerFactory;\n\t\tthis.deploymentPropertiesResolver = new DeploymentPropertiesResolver(\n\t\t\tKubernetesDeployerProperties.KUBERNETES_DEPLOYER_PROPERTIES_PREFIX, properties);\n\t}\n\n\t@Override\n\tpublic String launch(AppDeploymentRequest request) {\n\t\tString appId = createDeploymentId(request);\n\t\tTaskStatus status = status(appId);\n\n\t\tif (!status.getState().equals(LaunchState.unknown)) {\n\t\t\tthrow new IllegalStateException(\"Task \" + appId + \" already exists with a state of \" + status);\n\t\t}\n\n\t\tif (this.maxConcurrentExecutionsReached()) {\n\t\t\tthrow new IllegalStateException(\n\t\t\t\tString.format(\"Cannot launch task %s. The maximum concurrent task executions is at its limit [%d].\",\n\t\t\t\t\trequest.getDefinition().getName(), this.getMaximumConcurrentTasks())\n\t\t\t);\n\t\t}\n\n\t\tlogPossibleDownloadResourceMessage(request.getResource());\n\t\ttry {\n\t\t\tlaunch(appId, request);\n\t\t\treturn appId;\n\t\t} catch (RuntimeException e) {\n\t\t\tlogger.error(e.getMessage(), e);\n\t\t\tthrow e;\n\t\t}\n\t}\n\n\t@Override\n\tpublic void cancel(String id) {\n\t\tlogger.debug(String.format(\"Cancelling task: %s\", id));\n\t\t//ToDo: what does cancel mean? Kubernetes doesn't have stop - just cleanup\n\t\tcleanup(id);\n\t}\n\n\t@Override\n\tpublic void cleanup(String id) {\n\t\ttry {\n\t\t\tif (properties.isCreateJob()) {\n\t\t\t\tdeleteJob(id);\n\t\t\t} else {\n\t\t\t\tdeletePod(id);\n\t\t\t}\n\t\t} catch (RuntimeException e) {\n\t\t\tlogger.error(e.getMessage(), e);\n\t\t\tthrow e;\n\t\t}\n\t}\n\n\t@Override\n\tpublic void destroy(String appName) {\n\t\tfor (String id : getIdsForTasks(Optional.of(appName), properties.isCreateJob())) {\n\t\t\tcleanup(id);\n\t\t}\n\t}\n\n\t@Override\n\tpublic RuntimeEnvironmentInfo environmentInfo() {\n\t\treturn super.createRuntimeEnvironmentInfo(TaskLauncher.class, this.getClass());\n\t}\n\n\t@Override\n\tpublic TaskStatus status(String id) {\n\t\tTaskStatus status = buildTaskStatus(id);\n\t\tlogger.debug(String.format(\"Status for task: %s is %s\", id, status));\n\n\t\treturn status;\n\t}\n\n\t@Override\n\tpublic int getMaximumConcurrentTasks() {\n\t\treturn this.properties.getMaximumConcurrentTasks();\n\t}\n\n\t@Override\n\tpublic int getRunningTaskExecutionCount() {\n\t\tList<String> taskIds = getIdsForTasks(Optional.empty(), false);\n\t\tAtomicInteger executionCount = new AtomicInteger();\n\n\t\ttaskIds.forEach(id -> {\n\t\t\tif (buildPodStatus(id).getState() == LaunchState.running) {\n\t\t\t\texecutionCount.incrementAndGet();\n\t\t\t}\n\t\t});\n\n\t\treturn executionCount.get();\n\t}\n\n\t@Override\n\tpublic String getLog(String id) {\n\t\tif (properties.isCreateJob()) {\n\t\t\tJob job = getJob(id);\n\t\t\tAssert.notNull(job, \"Expected job\");\n\t\t\tMap<String, String> selector = new HashMap<>();\n\t\t\tselector.put(SPRING_APP_KEY, id);\n\t\t\tselector.put(\"job-name\", job.getMetadata().getName());\n\t\t\treturn getLogFromSelector(selector);\n\t\t} else {\n\t\t\tMap<String, String> selector = new HashMap<>();\n\t\t\tselector.put(SPRING_APP_KEY, id);\n\t\t\treturn getLogFromSelector(selector);\n\t\t}\n\t}\n\n\tprivate String getLogFromSelector(Map<String, String> selector) {\n\t\tPodList podList = client.pods().withLabels(selector).list();\n\t\tStringBuilder logAppender = new StringBuilder();\n\t\tfor (Pod pod : podList.getItems()) {\n\t\t\tfor (Container container : pod.getSpec().getContainers()) {\n\t\t\t\tlogAppender.append(this.client.pods().withName(pod.getMetadata().getName())\n\t\t\t\t\t.inContainer(container.getName()).tailingLines(500).getLog());\n\t\t\t}\n\t\t}\n\t\treturn logAppender.toString();\n\t}\n\n\tprivate boolean maxConcurrentExecutionsReached() {\n\t\treturn this.getRunningTaskExecutionCount() >= this.getMaximumConcurrentTasks();\n\t}\n\n\tprotected String createDeploymentId(AppDeploymentRequest request) {\n\t\tString name = request.getDefinition().getName();\n\t\tHashids hashids = new Hashids(name, 0, \"abcdefghijklmnopqrstuvwxyz1234567890\");\n\t\tlong idToEncode = System.currentTimeMillis() + ThreadLocalRandom.current().nextInt(1000);\n\t\tString hashid = hashids.encode(idToEncode);\n\t\tString deploymentId = name + \"-\" + hashid;\n\t\t// Kubernetes does not allow . in the name and does not allow uppercase in the name\n\t\treturn deploymentId.replace('.', '-').toLowerCase(Locale.ROOT);\n\t}\n\n\n\tprivate synchronized void launch(String appId, AppDeploymentRequest request) {\n\t\tMap<String, String> idMap = createIdMap(appId, request);\n\t\tMap<String, String> podLabelMap = new HashMap<>();\n\t\tpodLabelMap.put(\"task-name\", request.getDefinition().getName());\n\t\tpodLabelMap.put(SPRING_MARKER_KEY, SPRING_MARKER_VALUE);\n\n\t\tMap<String, String> deploymentProperties = request.getDeploymentProperties();\n\t\tMap<String, String> deploymentLabels = this.deploymentPropertiesResolver.getDeploymentLabels(deploymentProperties);\n\t\tif (!CollectionUtils.isEmpty(deploymentLabels)) {\n\t\t\tlogger.debug(String.format(\"Adding deploymentLabels: %s\", deploymentLabels));\n\t\t}\n\t\tPodSpec podSpec = createPodSpec(request);\n\n\t\tpodSpec.setRestartPolicy(getRestartPolicy(request).name());\n\t\tif (this.properties.isCreateJob()) {\n\t\t\tlogger.debug(String.format(\"Launching Job for task: %s\", appId));\n\t\t\tObjectMeta objectMeta = new ObjectMetaBuilder()\n\t\t\t\t.withLabels(podLabelMap)\n\t\t\t\t.addToLabels(idMap)\n\t\t\t\t.addToLabels(deploymentLabels)\n\t\t\t\t.withAnnotations(this.deploymentPropertiesResolver.getJobAnnotations(deploymentProperties))\n\t\t\t\t.addToAnnotations(this.deploymentPropertiesResolver.getPodAnnotations(deploymentProperties))\n\t\t\t\t.build();\n\t\t\tPodTemplateSpec podTemplateSpec = new PodTemplateSpec(objectMeta, podSpec);\n\n\t\t\tJobSpec jobSpec = new JobSpecBuilder()\n\t\t\t\t.withTemplate(podTemplateSpec)\n\t\t\t\t.withBackoffLimit(getBackoffLimit(request))\n\t\t\t\t.withTtlSecondsAfterFinished(getTtlSecondsAfterFinished(request))\n\t\t\t\t.build();\n\t\t\tJobOperationsImpl jobOperations = new JobOperationsImpl(this.client);\n\t\t\tthis.client.batch().v1().jobs().inNamespace(jobOperations.getNamespace()).resource(new JobBuilder()\n\t\t\t\t.withNewMetadata()\n\t\t\t\t.withName(appId)\n\t\t\t\t.withLabels(Collections.singletonMap(\"task-name\", podLabelMap.get(\"task-name\")))\n\t\t\t\t.addToLabels(idMap)\n\t\t\t\t.withAnnotations(this.deploymentPropertiesResolver.getJobAnnotations(deploymentProperties))\n\t\t\t\t.endMetadata()\n\t\t\t\t.withSpec(jobSpec)\n\t\t\t\t.build()\n\t\t\t).create();\n\n;\n\t\t}\n\t\telse {\n\t\t\tlogger.debug(String.format(\"Launching Pod for task: %s\", appId));\n\t\t\tPodOperationsImpl podOperations = new PodOperationsImpl(this.client);\n\t\t\tthis.client.pods().inNamespace(podOperations.getNamespace()).resource(\n\t\t\t\tnew PodBuilder()\n\t\t\t\t\t.withNewMetadata()\n\t\t\t\t\t.withName(appId)\n\t\t\t\t\t.withLabels(podLabelMap)\n\t\t\t\t\t.addToLabels(deploymentLabels)\n\t\t\t\t\t.withAnnotations(this.deploymentPropertiesResolver.getJobAnnotations(deploymentProperties))\n\t\t\t\t\t.addToAnnotations(this.deploymentPropertiesResolver.getPodAnnotations(deploymentProperties))\n\t\t\t\t\t.addToLabels(idMap)\n\t\t\t\t\t.endMetadata()\n\t\t\t\t\t.withSpec(podSpec)\n\t\t\t\t\t.build()\n\t\t\t).create();\n\t\t}\n\t}\n\n\tprivate List<String> getIdsForTasks(Optional<String> taskName, boolean isCreateJob) {\n\t\tList<String> ids = new ArrayList<>();\n\t\ttry {\n\t\t\tKubernetesResourceList<?> resourceList = getTaskResources(taskName, isCreateJob);\n\n\t\t\tfor (HasMetadata hasMetadata : resourceList.getItems()) {\n\t\t\t\tids.add(hasMetadata.getMetadata().getName());\n\t\t\t}\n\t\t}\n\t\tcatch (KubernetesClientException kce) {\n\t\t\tlogger.warn(String.format(\"Failed to retrieve pods for task: %s\", taskName), kce);\n\t\t}\n\n\t\treturn ids;\n\t}\n\n\tprivate KubernetesResourceList<?> getTaskResources(Optional<String> taskName, boolean isCreateJob) {\n\t\tKubernetesResourceList<?> resourceList;\n\t\tif (taskName.isPresent()) {\n\t\t\tif (isCreateJob) {\n\t\t\t\tresourceList = client.batch().v1().jobs().withLabel(\"task-name\", taskName.get()).list();\n\t\t\t}\n\t\t\telse {\n\t\t\t\tresourceList = client.pods().withLabel(\"task-name\", taskName.get()).list();\n\t\t\t}\n\t\t} else {\n\t\t\tif (isCreateJob) {\n\t\t\t\tresourceList = client.batch().v1().jobs().withLabel(\"task-name\").list();\n\t\t\t}\n\t\t\telse {\n\t\t\t\tresourceList = client.pods().withLabel(\"task-name\").list();\n\t\t\t}\n\t\t}\n\t\treturn resourceList;\n\t}\n\n\tTaskStatus buildTaskStatus(String id) {\n\n\t\tif (properties.isCreateJob()) {\n\t\t\tJob job = getJob(id);\n\n\t\t\tif (job == null) {\n\t\t\t\treturn new TaskStatus(id, LaunchState.unknown, new HashMap<>());\n\t\t\t}\n\n\t\t\tJobStatus jobStatus = job.getStatus();\n\n\t\t\tif (jobStatus == null) {\n\t\t\t\treturn new TaskStatus(id, LaunchState.unknown, new HashMap<>());\n\t\t\t}\n\n\t\t\tboolean failed = jobStatus.getFailed() != null && jobStatus.getFailed() > 0;\n\t\t\tboolean succeeded = jobStatus.getSucceeded() != null && jobStatus.getSucceeded() > 0;\n\t\t\tif (failed) {\n\t\t\t\treturn new TaskStatus(id, LaunchState.failed, new HashMap<>());\n\t\t\t}\n\t\t\tif (succeeded) {\n\t\t\t\treturn new TaskStatus(id, LaunchState.complete, new HashMap<>());\n\t\t\t}\n\t\t\treturn new TaskStatus(id, LaunchState.launching, new HashMap<>());\n\n\t\t} else {\n\t\t\treturn buildPodStatus(id);\n\t\t}\n\t}\n\n\tprivate TaskStatus buildPodStatus(String id) {\n\t\tPod pod = getPodByName(id);\n\t\tif (pod == null) {\n\t\t\treturn new TaskStatus(id, LaunchState.unknown, new HashMap<>());\n\t\t}\n\n\t\tPodStatus podStatus = pod.getStatus();\n\t\tif (podStatus == null) {\n\t\t\treturn new TaskStatus(id, LaunchState.unknown, new HashMap<>());\n\t\t}\n\n\t\tString phase = podStatus.getPhase();\n\n\t\treturn switch (phase) {\n\t\t\tcase \"Pending\" -> new TaskStatus(id, LaunchState.launching, new HashMap<>());\n\t\t\tcase \"Failed\" -> new TaskStatus(id, LaunchState.failed, new HashMap<>());\n\t\t\tcase \"Succeeded\" -> new TaskStatus(id, LaunchState.complete, new HashMap<>());\n\t\t\tdefault -> new TaskStatus(id, LaunchState.running, new HashMap<>());\n\t\t};\n\t}\n\n\n\tprivate void deleteJob(String id) {\n\t\tFilterWatchListDeletable<Job, JobList, ScalableResource<Job>> jobsToDelete = client.batch().v1().jobs()\n\t\t\t.withLabel(SPRING_APP_KEY, id);\n\n\t\tif (jobsToDelete == null || ObjectUtils.isEmpty(jobsToDelete.list().getItems())) {\n\t\t\tlogger.warn(String.format(\"Cannot delete job for task \\\"%s\\\" (reason: job does not exist)\", id));\n\t\t} else {\n\t\t\tlogger.debug(String.format(\"Deleting job for task: %s\", id));\n\t\t\tList<StatusDetails> deleted = jobsToDelete.delete();\n\t\t\tif(logger.isDebugEnabled()) {\n\t\t\t\tlogger.debug(String.format(\"Job was%s deleted for task: %s\", id, (deleted != null && !deleted.isEmpty() ? \"\" : \" not\")));\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate void deletePod(String id) {\n\t\tFilterWatchListDeletable<Pod, PodList, PodResource> podsToDelete = client.pods()\n\t\t\t.withLabel(SPRING_APP_KEY, id);\n\n\t\tif (podsToDelete == null || ObjectUtils.isEmpty(podsToDelete.list().getItems())) {\n\t\t\tlogger.warn(String.format(\"Cannot delete pod for task \\\"%s\\\" (reason: pod does not exist)\", id));\n\t\t} else {\n\t\t\tlogger.debug(String.format(\"Deleting pod for task: %s\", id));\n\t\t\tList<StatusDetails> deleted = podsToDelete.delete();\n\t\t\tif(logger.isDebugEnabled()) {\n\t\t\t\tlogger.debug(String.format(\"Pod was%s deleted for task: %s\", id, (deleted != null && !deleted.isEmpty() ? \"\" : \" not\")));\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate Job getJob(String jobName) {\n\t\tList<Job> jobs = client.batch().v1().jobs().withLabel(SPRING_APP_KEY, jobName).list().getItems();\n\n\t\tfor (Job job : jobs) {\n\t\t\tif (jobName.equals(job.getMetadata().getName())) {\n\t\t\t\treturn job;\n\t\t\t}\n\t\t}\n\n\t\treturn null;\n\t}\n\n\tprivate Pod getPodByName(String name) {\n\t\tPodResource podResource = client.pods().withName(name);\n\t\treturn podResource == null ? null : client.pods().withName(name).get();\n\t}\n\n\t/**\n\t * Get the RestartPolicy setting for the deployment request.\n\t *\n\t * @param request The deployment request.\n\t * @return Whether RestartPolicy is requested\n\t */\n\tprotected RestartPolicy getRestartPolicy(AppDeploymentRequest request) {\n\t\tString restartPolicyString =\n\t\t\tPropertyParserUtils.getDeploymentPropertyValue(request.getDeploymentProperties(),\n\t\t\t\t\"spring.cloud.deployer.kubernetes.restartPolicy\");\n\t\tRestartPolicy restartPolicy = (!StringUtils.hasText(restartPolicyString)) ? this.taskLauncherProperties.getRestartPolicy() :\n\t\t\tRestartPolicy.valueOf(restartPolicyString);\n\t\tif (this.properties.isCreateJob()) {\n\t\t\tAssert.isTrue(!restartPolicy.equals(RestartPolicy.Always), \"RestartPolicy should not be 'Always' when the JobSpec is used.\");\n\t\t}\n\t\treturn restartPolicy;\n\t}\n\n\t/**\n\t * Get the BackoffLimit setting for the deployment request.\n\t *\n\t * @param request The deployment request.\n\t * @return the backoffLimit\n\t */\n\tprotected Integer getBackoffLimit(AppDeploymentRequest request) {\n\t\tString backoffLimitString = PropertyParserUtils.getDeploymentPropertyValue(request.getDeploymentProperties(),\n\t\t\t\"spring.cloud.deployer.kubernetes.backoffLimit\");\n\t\tif (StringUtils.hasText(backoffLimitString)) {\n\t\t\treturn Integer.valueOf(backoffLimitString);\n\t\t}\n\t\telse {\n\t\t\treturn this.taskLauncherProperties.getBackoffLimit();\n\t\t}\n\t}\n\n\t/**\n\t * Get the ttlSecondsAfterFinihsed setting for the deployment request.\n\t *\n\t * @param request The deployment request.\n\t * @return the ttlSecondsAfterFinished\n\t */\n\tprotected Integer getTtlSecondsAfterFinished(AppDeploymentRequest request) {\n\t\tString ttlSecondsAfterFinished = PropertyParserUtils.getDeploymentPropertyValue(request.getDeploymentProperties(),\n\t\t\t\"spring.cloud.deployer.kubernetes.ttlSecondsAfterFinished\");\n\t\tif (StringUtils.hasText(ttlSecondsAfterFinished)) {\n\t\t\treturn Integer.valueOf(ttlSecondsAfterFinished);\n\t\t}\n\t\telse {\n\t\t\treturn this.taskLauncherProperties.getTtlSecondsAfterFinished();\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "spring-cloud-deployer-kubernetes/src/main/java/org/springframework/cloud/deployer/spi/kubernetes/KubernetesTaskLauncherProperties.java",
    "content": "/*\n * Copyright 2020 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.kubernetes;\n\nimport org.springframework.boot.context.properties.ConfigurationProperties;\n\n/**\n * Configuration properties for the Kubernetes Task Launcher.\n *\n * @author Ilayaperumal Gopinathan\n */\n@ConfigurationProperties(prefix = \"spring.cloud.deployer.kubernetes\")\npublic class KubernetesTaskLauncherProperties {\n\t/**\n\t * The {@link RestartPolicy} to use. Defaults to {@link RestartPolicy#Never}.\n\t */\n\tprivate RestartPolicy restartPolicy = RestartPolicy.Never;\n\n\t/**\n\t * The backoff limit to specify the number of retries before considering a Job as failed.\n\t */\n\tprivate Integer backoffLimit;\n\n\t/**\n\t * The number of seconds after a job has finished before it is eligible to be automatically\n\t * removed by the TTL controller - note that logs from removed jobs will not be able to\n\t * be retrieved.\n\t */\n\tprivate Integer ttlSecondsAfterFinished;\n\n\t/**\n\t * Obtains the {@link RestartPolicy} to use. Defaults to\n\t * {@link KubernetesTaskLauncherProperties#restartPolicy}.\n\t *\n\t * @return the {@link RestartPolicy} to use\n\t */\n\tpublic RestartPolicy getRestartPolicy() {\n\t\treturn restartPolicy;\n\t}\n\n\t/**\n\t * Sets the {@link RestartPolicy} to use.\n\t *\n\t * @param restartPolicy the {@link RestartPolicy} to use\n\t */\n\tpublic void setRestartPolicy(RestartPolicy restartPolicy) {\n\t\tthis.restartPolicy = restartPolicy;\n\t}\n\n\t/**\n\t * Get the BackoffLimit value\n\t * @return the integer value of BackoffLimit\n\t */\n\tpublic Integer getBackoffLimit() {\n\t\treturn backoffLimit;\n\t}\n\n\t/**\n\t * Sets the BackoffLimit.\n\t *\n\t * @param backoffLimit the integer value of BackoffLimit\n\t */\n\tpublic void setBackoffLimit(Integer backoffLimit) {\n\t\tthis.backoffLimit = backoffLimit;\n\t}\n\n\t/**\n\t * Get the ttlSecondsAfterFinished value\n\t * @return the integer value of ttlSecondsAfterFinished\n\t */\n\tpublic Integer getTtlSecondsAfterFinished() {\n\t\treturn ttlSecondsAfterFinished;\n\t}\n\n\t/**\n\t * Sets the ttlSecondsAfterFinished.\n\t *\n\t * @param ttlSecondsAfterFinished the integer value of ttlSecondsAfterFinished\n\t */\n\tpublic void setTtlSecondsAfterFinished(Integer ttlSecondsAfterFinished) {\n\t\tthis.ttlSecondsAfterFinished = ttlSecondsAfterFinished;\n\t}\n}\n"
  },
  {
    "path": "spring-cloud-deployer-kubernetes/src/main/java/org/springframework/cloud/deployer/spi/kubernetes/LivenessCommandProbeCreator.java",
    "content": "/*\n * Copyright 2020 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.kubernetes;\n\nimport org.springframework.cloud.deployer.spi.util.CommandLineTokenizer;\nimport org.springframework.util.StringUtils;\n\n/**\n * Creates a command based liveness probe\n *\n * @author Chris Schaefer\n * @author Corneil du Plessis\n * @since 2.5\n */\nclass LivenessCommandProbeCreator extends CommandProbeCreator {\n    LivenessCommandProbeCreator(KubernetesDeployerProperties kubernetesDeployerProperties, ContainerConfiguration containerConfiguration) {\n        super(kubernetesDeployerProperties, containerConfiguration);\n    }\n\n    @Override\n    int getInitialDelay() {\n        return getProbeIntProperty(LIVENESS_DEPLOYER_PROPERTY_PREFIX, \"Command\", \"ProbeDelay\",\n                getKubernetesDeployerProperties().getLivenessCommandProbeDelay());\n    }\n\n    @Override\n    int getPeriod() {\n        return getProbeIntProperty(LIVENESS_DEPLOYER_PROPERTY_PREFIX, \"Command\", \"ProbePeriod\",\n                getKubernetesDeployerProperties().getLivenessCommandProbePeriod());\n    }\n\n    @Override\n    int getFailure() {\n        return getProbeIntProperty(LIVENESS_DEPLOYER_PROPERTY_PREFIX, \"Command\", \"ProbeFailure\",\n                getKubernetesDeployerProperties().getLivenessCommandProbeFailure());\n    }\n\n    @Override\n    int getSuccess() {\n        return getProbeIntProperty(LIVENESS_DEPLOYER_PROPERTY_PREFIX, \"Command\", \"ProbeSuccess\",\n                getKubernetesDeployerProperties().getLivenessCommandProbeSuccess());\n    }\n\n    @Override\n    String[] getCommand() {\n        String probeCommandValue = getDeploymentPropertyValue(LIVENESS_DEPLOYER_PROPERTY_PREFIX + \"CommandProbeCommand\");\n\n        if (StringUtils.hasText(probeCommandValue)) {\n            return new CommandLineTokenizer(probeCommandValue).getArgs().toArray(new String[0]);\n        }\n\n        if (getKubernetesDeployerProperties().getLivenessCommandProbeCommand() != null) {\n            return new CommandLineTokenizer(getKubernetesDeployerProperties().getLivenessCommandProbeCommand())\n                    .getArgs().toArray(new String[0]);\n        }\n\n        throw new IllegalArgumentException(\"The livenessCommandProbeCommand property must be set.\");\n    }\n}\n"
  },
  {
    "path": "spring-cloud-deployer-kubernetes/src/main/java/org/springframework/cloud/deployer/spi/kubernetes/LivenessHttpProbeCreator.java",
    "content": "/*\n * Copyright 2018-2020 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.kubernetes;\n\nimport org.springframework.util.StringUtils;\n\n/**\n * Creates an HTTP Liveness probe\n *\n * @author Chris Schaefer\n * @author Ilayaperumal Gopinathan\n * @author Corneil du Plessis\n */\nclass LivenessHttpProbeCreator extends HttpProbeCreator {\n    LivenessHttpProbeCreator(KubernetesDeployerProperties kubernetesDeployerProperties,\n                             ContainerConfiguration containerConfiguration) {\n        super(kubernetesDeployerProperties, containerConfiguration);\n    }\n\n    @Override\n    public Integer getPort() {\n        String probePortValue = getProbeProperty(LIVENESS_DEPLOYER_PROPERTY_PREFIX, \"Http\", \"ProbePort\");\n\n        if (StringUtils.hasText(probePortValue)) {\n            return Integer.parseInt(probePortValue);\n        }\n\n        if (getKubernetesDeployerProperties().getLivenessHttpProbePort() != null) {\n            return getKubernetesDeployerProperties().getLivenessHttpProbePort();\n        }\n\n        if (getDefaultPort() != null) {\n            return getDefaultPort();\n        }\n\n        return null;\n    }\n\n    @Override\n    protected String getProbePath() {\n        String probePathValue = getProbeProperty(LIVENESS_DEPLOYER_PROPERTY_PREFIX, \"Http\", \"ProbePath\",\n                getKubernetesDeployerProperties().getLivenessHttpProbePath());\n\n        if (StringUtils.hasText(probePathValue)) {\n            return probePathValue;\n        }\n\n        if (useBoot1ProbePath()) {\n            return BOOT_1_LIVENESS_PROBE_PATH;\n        }\n\n        return BOOT_2_LIVENESS_PROBE_PATH;\n    }\n\n    @Override\n    protected String getScheme() {\n        String probeSchemeValue = getProbeProperty(LIVENESS_DEPLOYER_PROPERTY_PREFIX, \"Http\", \"ProbeScheme\",\n                getKubernetesDeployerProperties().getLivenessHttpProbeScheme());\n\n        if (StringUtils.hasText(probeSchemeValue)) {\n            return probeSchemeValue;\n        }\n\n        return DEFAULT_PROBE_SCHEME;\n    }\n\n    @Override\n    protected int getTimeout() {\n        return getProbeIntProperty(LIVENESS_DEPLOYER_PROPERTY_PREFIX, \"Http\", \"ProbeTimeout\",\n                getKubernetesDeployerProperties().getLivenessHttpProbeTimeout());\n    }\n\n    @Override\n    protected int getInitialDelay() {\n        return getProbeIntProperty(LIVENESS_DEPLOYER_PROPERTY_PREFIX, \"Http\", \"ProbeDelay\",\n                getKubernetesDeployerProperties().getLivenessHttpProbeDelay());\n    }\n\n    @Override\n    protected int getPeriod() {\n        return getProbeIntProperty(LIVENESS_DEPLOYER_PROPERTY_PREFIX, \"Http\", \"ProbePeriod\",\n                getKubernetesDeployerProperties().getLivenessHttpProbePeriod());\n    }\n\n    @Override\n    int getFailure() {\n        return getProbeIntProperty(LIVENESS_DEPLOYER_PROPERTY_PREFIX, \"Http\", \"ProbeFailure\",\n                getKubernetesDeployerProperties().getLivenessHttpProbeFailure());\n    }\n\n    @Override\n    int getSuccess() {\n        return getProbeIntProperty(LIVENESS_DEPLOYER_PROPERTY_PREFIX, \"Http\", \"ProbeSuccess\",\n                getKubernetesDeployerProperties().getLivenessHttpProbeSuccess());\n    }\n}\n"
  },
  {
    "path": "spring-cloud-deployer-kubernetes/src/main/java/org/springframework/cloud/deployer/spi/kubernetes/LivenessTcpProbeCreator.java",
    "content": "/*\n * Copyright 2020 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.kubernetes;\n\nimport org.springframework.util.StringUtils;\n\n/**\n * Creates a TCP liveness probe\n *\n * @author Chris Schaefer\n * @author Corneil du Plessis\n * @since 2.5\n */\nclass LivenessTcpProbeCreator extends TcpProbeCreator {\n    LivenessTcpProbeCreator(KubernetesDeployerProperties kubernetesDeployerProperties, ContainerConfiguration containerConfiguration) {\n        super(kubernetesDeployerProperties, containerConfiguration);\n    }\n\n    @Override\n    int getInitialDelay() {\n        return getProbeIntProperty(LIVENESS_DEPLOYER_PROPERTY_PREFIX, \"Tcp\", \"ProbeDelay\",\n                getKubernetesDeployerProperties().getLivenessTcpProbeDelay());\n    }\n\n    @Override\n    int getPeriod() {\n        return getProbeIntProperty(LIVENESS_DEPLOYER_PROPERTY_PREFIX, \"Tcp\", \"ProbePeriod\",\n                getKubernetesDeployerProperties().getLivenessTcpProbePeriod());\n    }\n\n    @Override\n    protected int getTimeout() {\n        return getProbeIntProperty(LIVENESS_DEPLOYER_PROPERTY_PREFIX, \"Tcp\", \"ProbeTimeout\",\n                getKubernetesDeployerProperties().getLivenessTcpProbeTimeout());\n    }\n\n    @Override\n    Integer getPort() {\n        String probePortValue = getProbeProperty(LIVENESS_DEPLOYER_PROPERTY_PREFIX, \"Tcp\", \"ProbePort\");\n\n        if (StringUtils.hasText(probePortValue)) {\n            if (!probePortValue.chars().allMatch(Character::isDigit)) {\n                throw new IllegalArgumentException(\"LivenessTcpProbePort must contain all digits\");\n            }\n\n            return Integer.parseInt(probePortValue);\n        }\n\n        if (getKubernetesDeployerProperties().getLivenessTcpProbePort() != null) {\n            return getKubernetesDeployerProperties().getLivenessTcpProbePort();\n        }\n\n        throw new IllegalArgumentException(\"The livenessTcpProbePort property must be set.\");\n    }\n\n    @Override\n    int getFailure() {\n        return getProbeIntProperty(LIVENESS_DEPLOYER_PROPERTY_PREFIX, \"Tcp\", \"ProbeFailure\",\n                getKubernetesDeployerProperties().getLivenessTcpProbeFailure());\n    }\n\n    @Override\n    int getSuccess() {\n        return getProbeIntProperty(LIVENESS_DEPLOYER_PROPERTY_PREFIX, \"Tcp\", \"ProbeSuccess\",\n                getKubernetesDeployerProperties().getLivenessTcpProbeSuccess());\n    }\n}\n"
  },
  {
    "path": "spring-cloud-deployer-kubernetes/src/main/java/org/springframework/cloud/deployer/spi/kubernetes/PredicateRunningPhaseDeploymentStateResolver.java",
    "content": "/*\n * Copyright 2017 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.kubernetes;\n\nimport java.util.function.Predicate;\nimport java.util.stream.Stream;\n\nimport io.fabric8.kubernetes.api.model.ContainerStatus;\nimport org.apache.commons.logging.Log;\nimport org.apache.commons.logging.LogFactory;\n\nimport org.springframework.cloud.deployer.spi.app.DeploymentState;\n\n/**\n * @author David Turanski\n **/\npublic class PredicateRunningPhaseDeploymentStateResolver implements RunningPhaseDeploymentStateResolver {\n\tprivate static Log logger = LogFactory.getLog(PredicateRunningPhaseDeploymentStateResolver.class);\n\tprivate final ContainerStatusCondition[] conditions;\n\tprivate final DeploymentState resolvedState;\n\tprotected final KubernetesDeployerProperties properties;\n\n\tPredicateRunningPhaseDeploymentStateResolver(\n\t\tKubernetesDeployerProperties properties,\n\t\tDeploymentState resolvedState,\n\t\tContainerStatusCondition... conditions) {\n\t\tthis.conditions = conditions;\n\t\tthis.resolvedState = resolvedState;\n\t\tthis.properties = properties;\n\t}\n\n\tpublic DeploymentState resolve(ContainerStatus containerStatus) {\n\n\t\tStream<Predicate<ContainerStatus>> conditionsStream = Stream.of(conditions);\n\t\tBoolean allConditionsMet = conditionsStream.reduce((x, y) -> x.and(y)).get().test(containerStatus);\n\n\t\tif (allConditionsMet) {\n\t\t\tlogger.debug(\"deployment state is \" + resolvedState.name());\n\t\t\treturn this.resolvedState;\n\t\t}\n\t\telse {\n\t\t\tStream<ContainerStatusCondition> report = Stream.of(conditions);\n\t\t\treport.filter(c -> {\n\t\t\t\tboolean result= false;\n\t\t\t\ttry {\n\t\t\t\t\tresult = c.test(containerStatus);\n\t\t\t\t}\n\t\t\t\tcatch (NullPointerException e) {\n\n\t\t\t\t}\n\t\t\t\treturn !result;\n\n\t\t\t}).forEach(c -> logger.debug(c + \" is not satisfied\"));\n\n\t\t}\n\t\treturn null;\n\t}\n\n\tstatic abstract class ContainerStatusCondition implements Predicate<ContainerStatus> {\n\t\tprivate final String description;\n\n\t\tContainerStatusCondition(String description) {\n\t\t\tthis.description = description;\n\t\t}\n\t\tpublic String toString() {\n\t\t\tString className = this.getClass().getName();\n\n\t\t\treturn className.substring(className.lastIndexOf(\".\") + 1) + \":\" + description;\n\t\t}\n\t}\n\n\tstatic class ContainerReady extends PredicateRunningPhaseDeploymentStateResolver {\n\t\tContainerReady(KubernetesDeployerProperties properties) {\n\t\t\tsuper(properties, DeploymentState.deployed, new ContainerStatusCondition(\"container ready\") {\n\t\t\t\t@Override\n\t\t\t\tpublic boolean test(ContainerStatus containerStatus) {\n\t\t\t\t\treturn containerStatus.getReady();\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\t}\n\n\tstatic class ContainerCrashed extends PredicateRunningPhaseDeploymentStateResolver {\n\t\tContainerCrashed(KubernetesDeployerProperties properties) {\n\t\t\tsuper(properties,\n\t\t\t\tDeploymentState.failed,\n\t\t\t\tnew ContainerStatusCondition(\"restart count > maxTerminatedErrorRestarts\") {\n\t\t\t\t\t@Override\n\t\t\t\t\tpublic boolean test(ContainerStatus containerStatus) {\n\t\t\t\t\t\treturn containerStatus.getRestartCount() > properties.getMaxTerminatedErrorRestarts();\n\t\t\t\t\t}\n\t\t\t\t}, new ContainerStatusCondition(\"exit code in (1, 137, 143)\") {\n\t\t\t\t\t@Override\n\t\t\t\t\tpublic boolean test(ContainerStatus containerStatus) {\n\t\t\t\t\t\t// if we are being killed repeatedly due to OOM or using too much CPU, or abnormal termination.\n\t\t\t\t\t\treturn\n\t\t\t\t\t\t\tcontainerStatus.getLastState() != null &&\n\t\t\t\t\t\t\t\tcontainerStatus.getLastState().getTerminated() != null &&\n\t\t\t\t\t\t\t\t(containerStatus.getLastState().getTerminated().getExitCode() == 137 ||\n\t\t\t\t\t\t\t\t\tcontainerStatus.getLastState().getTerminated().getExitCode() == 143 ||\n\t\t\t\t\t\t\t\t\tcontainerStatus.getLastState().getTerminated().getExitCode() == 1);\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t}\n\t}\n\n\t// if we are being restarted repeatedly due to the same error, consider the app crashed\n\tstatic class RestartsDueToTheSameError extends PredicateRunningPhaseDeploymentStateResolver {\n\n\t\tRestartsDueToTheSameError(KubernetesDeployerProperties properties) {\n\t\t\tsuper(properties, DeploymentState.failed, new ContainerStatusCondition(\"restart count > \"\n\t\t\t\t+ \"maxTerminatedErrorRestarts\") {\n\t\t\t\t@Override\n\t\t\t\tpublic boolean test(ContainerStatus containerStatus) {\n\t\t\t\t\treturn containerStatus.getRestartCount() > properties.getMaxTerminatedErrorRestarts();\n\t\t\t\t}\n\t\t\t}, new ContainerStatusCondition(\"last state termination reason == 'Error' and termination reason == \"\n\t\t\t\t+ \"'Error'\") {\n\t\t\t\tpublic boolean test(ContainerStatus containerStatus) {\n\t\t\t\t\treturn\n\t\t\t\t\t\tcontainerStatus.getLastState() != null && containerStatus.getState() != null &&\n\t\t\t\t\t\t\tcontainerStatus.getLastState().getTerminated() != null &&\n\t\t\t\t\t\t\tcontainerStatus.getLastState().getTerminated().getReason() != null &&\n\t\t\t\t\t\t\tcontainerStatus.getLastState().getTerminated().getReason().contains(\"Error\") &&\n\t\t\t\t\t\t\tcontainerStatus.getState().getTerminated() != null &&\n\t\t\t\t\t\t\tcontainerStatus.getState().getTerminated().getReason() != null &&\n\t\t\t\t\t\t\tcontainerStatus.getState().getTerminated().getReason().contains(\"Error\");\n\t\t\t\t}\n\t\t\t}, new ContainerStatusCondition(\"last state exit code == exit code\") {\n\t\t\t\t@Override\n\t\t\t\tpublic boolean test(ContainerStatus containerStatus) {\n\t\t\t\t\treturn containerStatus.getLastState().getTerminated().getExitCode().equals(\n\t\t\t\t\t\tcontainerStatus.getState().getTerminated().getExitCode());\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\t}\n\n\tstatic class CrashLoopBackOffRestarts extends PredicateRunningPhaseDeploymentStateResolver {\n\t\tCrashLoopBackOffRestarts(KubernetesDeployerProperties properties) {\n\t\t\tsuper(properties, DeploymentState.failed, new ContainerStatusCondition(\"restart count > \"\n\t\t\t\t+ \"CrashLoopBackOffRestarts\") {\n\t\t\t\t@Override\n\t\t\t\tpublic boolean test(ContainerStatus containerStatus) {\n\t\t\t\t\treturn containerStatus.getRestartCount() > properties.getMaxCrashLoopBackOffRestarts();\n\t\t\t\t}\n\t\t\t}, new ContainerStatusCondition(\"waiting in CrashLoopBackOff\") {\n\t\t\t\t@Override\n\t\t\t\tpublic boolean test(ContainerStatus containerStatus) {\n\t\t\t\t\treturn\n\t\t\t\t\t\tcontainerStatus.getLastState() != null &&\n\t\t\t\t\t\t\tcontainerStatus.getState() != null &&\n\t\t\t\t\t\t\tcontainerStatus.getLastState().getTerminated() != null &&\n\t\t\t\t\t\t\tcontainerStatus.getState().getWaiting() != null &&\n\t\t\t\t\t\t\tcontainerStatus.getState().getWaiting().getReason() != null &&\n\t\t\t\t\t\t\tcontainerStatus.getState().getWaiting().getReason().contains(\"CrashLoopBackOff\");\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\t}\n\n\tstatic class ContainerTerminated extends PredicateRunningPhaseDeploymentStateResolver {\n\n\t\tContainerTerminated(KubernetesDeployerProperties properties) {\n\t\t\tsuper(properties, DeploymentState.undeployed, new ContainerStatusCondition(\"restart count == 0\") {\n\t\t\t\t@Override\n\t\t\t\tpublic boolean test(ContainerStatus containerStatus) {\n\t\t\t\t\treturn containerStatus.getRestartCount() == 0;\n\t\t\t\t}\n\t\t\t}, new ContainerStatusCondition(\"state is terminated\") {\n\t\t\t\t@Override\n\t\t\t\tpublic boolean test(ContainerStatus containerStatus) {\n\t\t\t\t\treturn containerStatus.getState() != null &&\n\t\t\t\t\t\tcontainerStatus.getState().getTerminated() != null;\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\t}\n}\n\n"
  },
  {
    "path": "spring-cloud-deployer-kubernetes/src/main/java/org/springframework/cloud/deployer/spi/kubernetes/ProbeAuthenticationType.java",
    "content": "/*\n * Copyright 2018 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.kubernetes;\n\n/**\n * Defines supported authentication types to use when accessing secured\n * probe endpoints.\n *\n * @author Chris Schaefer\n */\npublic enum ProbeAuthenticationType {\n\tBasic\n}\n"
  },
  {
    "path": "spring-cloud-deployer-kubernetes/src/main/java/org/springframework/cloud/deployer/spi/kubernetes/ProbeCreator.java",
    "content": "/*\n * Copyright 2018-2020 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.kubernetes;\n\nimport java.util.Map;\n\nimport io.fabric8.kubernetes.api.model.Probe;\n\nimport org.springframework.cloud.deployer.spi.kubernetes.support.PropertyParserUtils;\nimport org.springframework.util.StringUtils;\n\n/**\n * Base class for creating Probe's\n *\n * @author Chris Schaefer\n * @author Ilayaperumal Gopinathan\n */\nabstract class ProbeCreator {\n    static final String KUBERNETES_DEPLOYER_PREFIX = \"spring.cloud.deployer.kubernetes\";\n    static final String LIVENESS_DEPLOYER_PROPERTY_PREFIX = KUBERNETES_DEPLOYER_PREFIX + \".liveness\";\n    static final String READINESS_DEPLOYER_PROPERTY_PREFIX = KUBERNETES_DEPLOYER_PREFIX + \".readiness\";\n    static final String STARTUP_DEPLOYER_PROPERTY_PREFIX = KUBERNETES_DEPLOYER_PREFIX + \".startup\";\n\n    private ContainerConfiguration containerConfiguration;\n    private KubernetesDeployerProperties kubernetesDeployerProperties;\n\n    ProbeCreator(KubernetesDeployerProperties kubernetesDeployerProperties,\n                 ContainerConfiguration containerConfiguration) {\n        this.containerConfiguration = containerConfiguration;\n        this.kubernetesDeployerProperties = kubernetesDeployerProperties;\n    }\n\n    abstract Probe create();\n\n    abstract int getInitialDelay();\n\n    abstract int getPeriod();\n\n    abstract int getFailure();\n\n    abstract int getSuccess();\n\n    KubernetesDeployerProperties getKubernetesDeployerProperties() {\n        return kubernetesDeployerProperties;\n    }\n\n    private Map<String, String> getDeploymentProperties() {\n        return this.containerConfiguration.getAppDeploymentRequest().getDeploymentProperties();\n    }\n\n    protected String getDeploymentPropertyValue(String propertyName) {\n        return PropertyParserUtils.getDeploymentPropertyValue(getDeploymentProperties(), propertyName);\n    }\n    protected String getDeploymentPropertyValue(String propertyName, String defaultValue) {\n        return PropertyParserUtils.getDeploymentPropertyValue(getDeploymentProperties(), propertyName, defaultValue);\n    }\n\n    ContainerConfiguration getContainerConfiguration() {\n        return containerConfiguration;\n    }\n\n    // used to resolve deprecated HTTP probe property names that do not include \"Http\" in them\n    // can be removed when deprecated HTTP probes without \"Http\" in them get removed\n    String getProbeProperty(String propertyPrefix, String probeName, String propertySuffix) {\n        String defaultValue = getDeploymentPropertyValue(propertyPrefix + probeName + propertySuffix);\n        return StringUtils.hasText(defaultValue) ? defaultValue :\n                getDeploymentPropertyValue(propertyPrefix + propertySuffix);\n    }\n\n    String getProbeProperty(String propertyPrefix, String probeName, String propertySuffix, String defaultValue) {\n        return getDeploymentPropertyValue(propertyPrefix + probeName + propertySuffix,\n                getDeploymentPropertyValue(propertyPrefix + propertySuffix, defaultValue)\n        );\n    }\n    int getProbeIntProperty(String propertyPrefix, String probeName, String propertySuffix, int defaultValue) {\n        String propertyValue = getDeploymentPropertyValue(propertyPrefix + probeName + propertySuffix,\n                getDeploymentPropertyValue(propertyPrefix + propertySuffix)\n        );\n        return StringUtils.hasText(propertyValue) ? Integer.parseInt(propertyValue) : defaultValue;\n    }\n}\n"
  },
  {
    "path": "spring-cloud-deployer-kubernetes/src/main/java/org/springframework/cloud/deployer/spi/kubernetes/ProbeCreatorFactory.java",
    "content": "/*\n * Copyright 2020 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.kubernetes;\n\nimport io.fabric8.kubernetes.api.model.Probe;\n\n/**\n * Creates health check probes\n *\n * @author Chris Schaefer\n * @since 2.5\n */\nclass ProbeCreatorFactory {\n    static Probe createStartupProbe(ContainerConfiguration containerConfiguration,\n                                    KubernetesDeployerProperties kubernetesDeployerProperties, ProbeType probeType) {\n        switch (probeType) {\n            case HTTP:\n                return new StartupHttpProbeCreator(kubernetesDeployerProperties, containerConfiguration).create();\n            case TCP:\n                return new StartupTcpProbeCreator(kubernetesDeployerProperties, containerConfiguration).create();\n            case COMMAND:\n                return new StartupCommandProbeCreator(kubernetesDeployerProperties, containerConfiguration).create();\n            default:\n                throw new IllegalArgumentException(\"Unknown startup probe type: \" + probeType);\n        }\n    }\n\n    static Probe createReadinessProbe(ContainerConfiguration containerConfiguration,\n                                      KubernetesDeployerProperties kubernetesDeployerProperties, ProbeType probeType) {\n        switch (probeType) {\n            case HTTP:\n                return new ReadinessHttpProbeCreator(kubernetesDeployerProperties, containerConfiguration).create();\n            case TCP:\n                return new ReadinessTcpProbeCreator(kubernetesDeployerProperties, containerConfiguration).create();\n            case COMMAND:\n                return new ReadinessCommandProbeCreator(kubernetesDeployerProperties, containerConfiguration).create();\n            default:\n                throw new IllegalArgumentException(\"Unknown readiness probe type: \" + probeType);\n        }\n    }\n\n    static Probe createLivenessProbe(ContainerConfiguration containerConfiguration,\n                                     KubernetesDeployerProperties kubernetesDeployerProperties, ProbeType probeType) {\n        switch (probeType) {\n            case HTTP:\n                return new LivenessHttpProbeCreator(kubernetesDeployerProperties, containerConfiguration).create();\n            case TCP:\n                return new LivenessTcpProbeCreator(kubernetesDeployerProperties, containerConfiguration).create();\n            case COMMAND:\n                return new LivenessCommandProbeCreator(kubernetesDeployerProperties, containerConfiguration).create();\n            default:\n                throw new IllegalArgumentException(\"Unknown liveness probe type: \" + probeType);\n        }\n    }\n}\n"
  },
  {
    "path": "spring-cloud-deployer-kubernetes/src/main/java/org/springframework/cloud/deployer/spi/kubernetes/ProbeType.java",
    "content": "/*\n * Copyright 2020 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage org.springframework.cloud.deployer.spi.kubernetes;\n\n/**\n * Defines probe types.\n *\n * @author Chris Schaefer\n * @since 2.5\n */\nenum ProbeType {\n\tHTTP,\n\tTCP,\n\tCOMMAND\n}\n"
  },
  {
    "path": "spring-cloud-deployer-kubernetes/src/main/java/org/springframework/cloud/deployer/spi/kubernetes/ReadinessCommandProbeCreator.java",
    "content": "/*\n * Copyright 2020 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.kubernetes;\n\nimport org.springframework.cloud.deployer.spi.util.CommandLineTokenizer;\nimport org.springframework.util.StringUtils;\n\n/**\n * Creates a command based readiness probe\n *\n * @author Chris Schaefer\n * @author Corneil du Plessis\n * @since 2.5\n */\nclass ReadinessCommandProbeCreator extends CommandProbeCreator {\n    ReadinessCommandProbeCreator(KubernetesDeployerProperties kubernetesDeployerProperties, ContainerConfiguration containerConfiguration) {\n        super(kubernetesDeployerProperties, containerConfiguration);\n    }\n\n    @Override\n    int getInitialDelay() {\n        return getProbeIntProperty(READINESS_DEPLOYER_PROPERTY_PREFIX, \"Command\", \"ProbeDelay\",\n                getKubernetesDeployerProperties().getReadinessCommandProbeDelay());\n    }\n\n    @Override\n    int getPeriod() {\n        return getProbeIntProperty(READINESS_DEPLOYER_PROPERTY_PREFIX, \"Command\", \"ProbePeriod\",\n                getKubernetesDeployerProperties().getReadinessCommandProbePeriod());\n    }\n\n    @Override\n    int getFailure() {\n        return getProbeIntProperty(READINESS_DEPLOYER_PROPERTY_PREFIX, \"Command\", \"ProbeFailure\",\n                getKubernetesDeployerProperties().getReadinessCommandProbeFailure());\n    }\n\n    @Override\n    int getSuccess() {\n        return getProbeIntProperty(READINESS_DEPLOYER_PROPERTY_PREFIX, \"Command\", \"ProbeSuccess\",\n                getKubernetesDeployerProperties().getReadinessCommandProbeSuccess());\n    }\n\n    @Override\n    String[] getCommand() {\n        String probeCommandValue = getDeploymentPropertyValue(READINESS_DEPLOYER_PROPERTY_PREFIX + \"CommandProbeCommand\");\n\n        if (StringUtils.hasText(probeCommandValue)) {\n            return new CommandLineTokenizer(probeCommandValue).getArgs().toArray(new String[0]);\n        }\n\n        if (getKubernetesDeployerProperties().getReadinessCommandProbeCommand() != null) {\n            return new CommandLineTokenizer(getKubernetesDeployerProperties().getReadinessCommandProbeCommand())\n                    .getArgs().toArray(new String[0]);\n        }\n\n        throw new IllegalArgumentException(\"The readinessCommandProbeCommand property must be set.\");\n    }\n}\n"
  },
  {
    "path": "spring-cloud-deployer-kubernetes/src/main/java/org/springframework/cloud/deployer/spi/kubernetes/ReadinessHttpProbeCreator.java",
    "content": "/*\n * Copyright 2018-2020 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.kubernetes;\n\nimport org.springframework.util.StringUtils;\n\n/**\n * Creates an HTTP Readiness Probe.\n *\n * @author Chris Schaefer\n * @author Ilayaperumal Gopinathan\n * @author Corneil du Plessis\n */\nclass ReadinessHttpProbeCreator extends HttpProbeCreator {\n    ReadinessHttpProbeCreator(KubernetesDeployerProperties kubernetesDeployerProperties,\n                              ContainerConfiguration containerConfiguration) {\n        super(kubernetesDeployerProperties, containerConfiguration);\n    }\n\n    @Override\n    public Integer getPort() {\n        String probePortValue = getProbeProperty(READINESS_DEPLOYER_PROPERTY_PREFIX, \"Http\", \"ProbePort\");\n\n        if (StringUtils.hasText(probePortValue)) {\n            if (!probePortValue.chars().allMatch(Character::isDigit)) {\n                throw new IllegalArgumentException(\"ReadinessHttpProbeCreator must contain all digits\");\n            }\n\n            return Integer.parseInt(probePortValue);\n        }\n\n        if (getKubernetesDeployerProperties().getReadinessHttpProbePort() != null) {\n            return getKubernetesDeployerProperties().getReadinessHttpProbePort();\n        }\n\n        if (getDefaultPort() != null) {\n            return getDefaultPort();\n        }\n\n        return null;\n    }\n\n    @Override\n    protected String getProbePath() {\n        String probePathValue = getProbeProperty(READINESS_DEPLOYER_PROPERTY_PREFIX, \"Http\", \"ProbePath\");\n\n        if (StringUtils.hasText(probePathValue)) {\n            return probePathValue;\n        }\n\n        if (getKubernetesDeployerProperties().getReadinessHttpProbePath() != null) {\n            return getKubernetesDeployerProperties().getReadinessHttpProbePath();\n        }\n\n        if (useBoot1ProbePath()) {\n            return BOOT_1_READINESS_PROBE_PATH;\n        }\n\n        return BOOT_2_READINESS_PROBE_PATH;\n    }\n\n    @Override\n    protected String getScheme() {\n        String probeSchemeValue = getProbeProperty(READINESS_DEPLOYER_PROPERTY_PREFIX, \"Http\", \"ProbeScheme\");\n\n        if (StringUtils.hasText(probeSchemeValue)) {\n            return probeSchemeValue;\n        }\n\n        if (getKubernetesDeployerProperties().getReadinessHttpProbeScheme() != null) {\n            return getKubernetesDeployerProperties().getReadinessHttpProbeScheme();\n        }\n\n        return DEFAULT_PROBE_SCHEME;\n    }\n\n    @Override\n    protected int getTimeout() {\n        return getProbeIntProperty(READINESS_DEPLOYER_PROPERTY_PREFIX, \"Http\", \"ProbeTimeout\",\n                getKubernetesDeployerProperties().getReadinessHttpProbeTimeout());\n    }\n\n    @Override\n    protected int getInitialDelay() {\n        return getProbeIntProperty(READINESS_DEPLOYER_PROPERTY_PREFIX, \"Http\", \"ProbeDelay\",\n                getKubernetesDeployerProperties().getReadinessHttpProbeDelay());\n    }\n\n    @Override\n    protected int getPeriod() {\n        return getProbeIntProperty(READINESS_DEPLOYER_PROPERTY_PREFIX, \"Http\", \"ProbePeriod\",\n                getKubernetesDeployerProperties().getReadinessHttpProbePeriod());\n    }\n\n    @Override\n    int getFailure() {\n        return getProbeIntProperty(READINESS_DEPLOYER_PROPERTY_PREFIX, \"Http\", \"ProbeFailure\",\n                getKubernetesDeployerProperties().getReadinessHttpProbeFailure());\n    }\n\n    @Override\n    int getSuccess() {\n        return getProbeIntProperty(READINESS_DEPLOYER_PROPERTY_PREFIX, \"Http\", \"ProbeSuccess\",\n                getKubernetesDeployerProperties().getReadinessHttpProbeSuccess());\n    }\n}\n"
  },
  {
    "path": "spring-cloud-deployer-kubernetes/src/main/java/org/springframework/cloud/deployer/spi/kubernetes/ReadinessTcpProbeCreator.java",
    "content": "/*\n * Copyright 2020 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.kubernetes;\n\nimport org.springframework.util.StringUtils;\n\n/**\n * Creates a TCP readiness probe\n *\n * @author Chris Schaefer\n * @author Corneil du Plessis\n * @since 2.5\n */\nclass ReadinessTcpProbeCreator extends TcpProbeCreator {\n    ReadinessTcpProbeCreator(KubernetesDeployerProperties kubernetesDeployerProperties, ContainerConfiguration containerConfiguration) {\n        super(kubernetesDeployerProperties, containerConfiguration);\n    }\n\n    @Override\n    int getInitialDelay() {\n        return getProbeIntProperty(READINESS_DEPLOYER_PROPERTY_PREFIX, \"Tcp\", \"ProbeDelay\",\n                getKubernetesDeployerProperties().getReadinessTcpProbeDelay());\n    }\n\n    @Override\n    int getPeriod() {\n        return getProbeIntProperty(READINESS_DEPLOYER_PROPERTY_PREFIX, \"Tcp\", \"ProbePeriod\",\n                getKubernetesDeployerProperties().getReadinessTcpProbePeriod());\n    }\n\n    @Override\n    protected int getTimeout() {\n        return getProbeIntProperty(READINESS_DEPLOYER_PROPERTY_PREFIX, \"Tcp\", \"ProbeTimeout\",\n                getKubernetesDeployerProperties().getReadinessTcpProbeTimeout());\n    }\n\n    @Override\n    Integer getPort() {\n        String probePortValue = getProbeProperty(READINESS_DEPLOYER_PROPERTY_PREFIX, \"Tcp\", \"ProbePort\");\n\n        if (StringUtils.hasText(probePortValue)) {\n            if (!probePortValue.chars().allMatch(Character::isDigit)) {\n                throw new IllegalArgumentException(\"ReadinessTcpProbePort must contain all digits\");\n            }\n\n            return Integer.parseInt(probePortValue);\n        }\n\n        if (getKubernetesDeployerProperties().getReadinessTcpProbePort() != null) {\n            return getKubernetesDeployerProperties().getReadinessTcpProbePort();\n        }\n\n        throw new IllegalArgumentException(\"A readinessTcpProbePort property must be set.\");\n    }\n\n    @Override\n    int getFailure() {\n        return getProbeIntProperty(READINESS_DEPLOYER_PROPERTY_PREFIX, \"Tcp\", \"ProbeFailure\",\n                getKubernetesDeployerProperties().getReadinessTcpProbeFailure());\n    }\n\n    @Override\n    int getSuccess() {\n        return getProbeIntProperty(READINESS_DEPLOYER_PROPERTY_PREFIX, \"Tcp\", \"ProbeSuccess\",\n                getKubernetesDeployerProperties().getReadinessTcpProbeSuccess());\n    }\n}\n"
  },
  {
    "path": "spring-cloud-deployer-kubernetes/src/main/java/org/springframework/cloud/deployer/spi/kubernetes/RestartPolicy.java",
    "content": "/*\n * Copyright 2018-2020 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.kubernetes;\n\n/**\n * Defines restart policies that are available.\n *\n * @author Chris Schaefer\n */\npublic enum RestartPolicy {\n\t/**\n\t * Always restart a failed container.\n\t */\n\tAlways,\n\n\t/**\n\t * Restart a failed container with an exponential back-off delay, capped at 5 minutes.\n\t */\n\tOnFailure,\n\n\t/**\n\t * Never restarts a successful or failed container.\n\t */\n\tNever\n}\n"
  },
  {
    "path": "spring-cloud-deployer-kubernetes/src/main/java/org/springframework/cloud/deployer/spi/kubernetes/RunningPhaseDeploymentStateResolver.java",
    "content": "/*\n * Copyright 2017 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.kubernetes;\n\nimport io.fabric8.kubernetes.api.model.ContainerStatus;\n\nimport org.springframework.cloud.deployer.spi.app.DeploymentState;\n\n/**\n * @author David Turanski\n **/\npublic interface RunningPhaseDeploymentStateResolver {\n\tDeploymentState resolve(ContainerStatus containerStatus);\n}\n"
  },
  {
    "path": "spring-cloud-deployer-kubernetes/src/main/java/org/springframework/cloud/deployer/spi/kubernetes/StartupCommandProbeCreator.java",
    "content": "/*\n * Copyright 2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.kubernetes;\n\nimport org.springframework.cloud.deployer.spi.util.CommandLineTokenizer;\nimport org.springframework.util.StringUtils;\n\n/**\n * Creates a command based startup probe\n *\n * @author Corneil du Plessis\n * @since 2.5\n */\nclass StartupCommandProbeCreator extends CommandProbeCreator {\n    StartupCommandProbeCreator(KubernetesDeployerProperties kubernetesDeployerProperties, ContainerConfiguration containerConfiguration) {\n        super(kubernetesDeployerProperties, containerConfiguration);\n    }\n\n    @Override\n    int getInitialDelay() {\n        return getProbeIntProperty(STARTUP_DEPLOYER_PROPERTY_PREFIX, \"Command\", \"ProbeDelay\",\n                getKubernetesDeployerProperties().getStartupCommandProbeDelay());\n    }\n\n    @Override\n    int getPeriod() {\n        return getProbeIntProperty(STARTUP_DEPLOYER_PROPERTY_PREFIX, \"Command\", \"ProbePeriod\",\n                getKubernetesDeployerProperties().getStartupCommandProbePeriod());\n    }\n\n    @Override\n    int getFailure() {\n        return getProbeIntProperty(STARTUP_DEPLOYER_PROPERTY_PREFIX, \"Command\", \"ProbeFailure\",\n                getKubernetesDeployerProperties().getLivenessCommandProbeFailure());\n    }\n\n    @Override\n    int getSuccess() {\n        return getProbeIntProperty(STARTUP_DEPLOYER_PROPERTY_PREFIX, \"Command\", \"ProbeSuccess\",\n                getKubernetesDeployerProperties().getLivenessCommandProbeSuccess());\n    }\n\n    @Override\n    String[] getCommand() {\n        String probeCommandValue = getDeploymentPropertyValue(STARTUP_DEPLOYER_PROPERTY_PREFIX + \"CommandProbeCommand\");\n\n        if (StringUtils.hasText(probeCommandValue)) {\n            return new CommandLineTokenizer(probeCommandValue).getArgs().toArray(new String[0]);\n        }\n\n        if (getKubernetesDeployerProperties().getStartupCommandProbeCommand() != null) {\n            return new CommandLineTokenizer(getKubernetesDeployerProperties().getStartupCommandProbeCommand())\n                    .getArgs().toArray(new String[0]);\n        }\n\n        throw new IllegalArgumentException(\"The startupCommandProbeCommand property must be set.\");\n    }\n}\n"
  },
  {
    "path": "spring-cloud-deployer-kubernetes/src/main/java/org/springframework/cloud/deployer/spi/kubernetes/StartupHttpProbeCreator.java",
    "content": "/*\n * Copyright 2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.kubernetes;\n\nimport org.springframework.util.StringUtils;\n\n/**\n * Creates an HTTP Startup Probe.\n *\n * @author Corneil du Plessis\n */\nclass StartupHttpProbeCreator extends HttpProbeCreator {\n    StartupHttpProbeCreator(KubernetesDeployerProperties kubernetesDeployerProperties,\n                            ContainerConfiguration containerConfiguration) {\n        super(kubernetesDeployerProperties, containerConfiguration);\n    }\n\n    @Override\n    public Integer getPort() {\n        String probePortValue = getProbeProperty(STARTUP_DEPLOYER_PROPERTY_PREFIX, \"Http\", \"ProbePort\");\n\n        if (StringUtils.hasText(probePortValue)) {\n            if (!probePortValue.chars().allMatch(Character::isDigit)) {\n                throw new IllegalArgumentException(\"StartupHttpProbeCreator must contain all digits\");\n            }\n\n            return Integer.parseInt(probePortValue);\n        }\n\n        if (getKubernetesDeployerProperties().getStartupHttpProbePort() != null) {\n            return getKubernetesDeployerProperties().getStartupHttpProbePort();\n        }\n\n        if (getDefaultPort() != null) {\n            return getDefaultPort();\n        }\n\n        return null;\n    }\n\n    @Override\n    protected String getProbePath() {\n        String probePathValue = getProbeProperty(STARTUP_DEPLOYER_PROPERTY_PREFIX, \"Http\", \"ProbePath\");\n\n        if (StringUtils.hasText(probePathValue)) {\n            return probePathValue;\n        }\n\n        if (getKubernetesDeployerProperties().getStartupHttpProbePath() != null) {\n            return getKubernetesDeployerProperties().getStartupHttpProbePath();\n        }\n\n        if (useBoot1ProbePath()) {\n            return BOOT_1_READINESS_PROBE_PATH;\n        }\n\n        return BOOT_2_READINESS_PROBE_PATH;\n    }\n\n    @Override\n    protected String getScheme() {\n        String probeSchemeValue = getProbeProperty(STARTUP_DEPLOYER_PROPERTY_PREFIX, \"Http\", \"ProbeScheme\");\n\n        if (StringUtils.hasText(probeSchemeValue)) {\n            return probeSchemeValue;\n        }\n\n        if (getKubernetesDeployerProperties().getStartupProbeScheme() != null) {\n            return getKubernetesDeployerProperties().getStartupProbeScheme();\n        }\n\n        return DEFAULT_PROBE_SCHEME;\n    }\n\n    @Override\n    protected int getTimeout() {\n        return getProbeIntProperty(STARTUP_DEPLOYER_PROPERTY_PREFIX, \"Http\", \"ProbeTimeout\",\n                getKubernetesDeployerProperties().getStartupHttpProbeTimeout());\n    }\n\n    @Override\n    protected int getInitialDelay() {\n        return getProbeIntProperty(STARTUP_DEPLOYER_PROPERTY_PREFIX, \"Http\", \"ProbeDelay\",\n                getKubernetesDeployerProperties().getStartupHttpProbeDelay());\n    }\n\n    @Override\n    protected int getPeriod() {\n        return getProbeIntProperty(STARTUP_DEPLOYER_PROPERTY_PREFIX, \"Http\", \"ProbePeriod\",\n                getKubernetesDeployerProperties().getStartupHttpProbePeriod());\n    }\n\n    @Override\n    int getFailure() {\n        return getProbeIntProperty(STARTUP_DEPLOYER_PROPERTY_PREFIX, \"Http\", \"ProbeFailure\",\n                getKubernetesDeployerProperties().getStartupHttpProbeFailure());\n    }\n\n    @Override\n    int getSuccess() {\n        return getProbeIntProperty(STARTUP_DEPLOYER_PROPERTY_PREFIX, \"Http\", \"ProbeSuccess\",\n                getKubernetesDeployerProperties().getStartupHttpProbeSuccess());\n    }\n}\n"
  },
  {
    "path": "spring-cloud-deployer-kubernetes/src/main/java/org/springframework/cloud/deployer/spi/kubernetes/StartupTcpProbeCreator.java",
    "content": "/*\n * Copyright 2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.kubernetes;\n\nimport org.springframework.util.StringUtils;\n\n/**\n * Creates a TCP startup probe\n *\n * @author Corneil du Plessis\n * @since 2.5\n */\nclass StartupTcpProbeCreator extends TcpProbeCreator {\n    StartupTcpProbeCreator(KubernetesDeployerProperties kubernetesDeployerProperties, ContainerConfiguration containerConfiguration) {\n        super(kubernetesDeployerProperties, containerConfiguration);\n    }\n\n    @Override\n    int getInitialDelay() {\n        return getProbeIntProperty(STARTUP_DEPLOYER_PROPERTY_PREFIX, \"Tcp\", \"ProbeDelay\",\n                getKubernetesDeployerProperties().getStartupTcpProbeDelay());\n    }\n\n    @Override\n    int getPeriod() {\n        return getProbeIntProperty(STARTUP_DEPLOYER_PROPERTY_PREFIX, \"Tcp\", \"ProbePeriod\",\n                getKubernetesDeployerProperties().getStartupTcpProbePeriod());\n    }\n\n    @Override\n    protected int getTimeout() {\n        return getProbeIntProperty(STARTUP_DEPLOYER_PROPERTY_PREFIX, \"Tcp\", \"ProbeTimeout\",\n                getKubernetesDeployerProperties().getStartupTcpProbeTimeout());\n    }\n\n    @Override\n    Integer getPort() {\n        String probePortValue = getProbeProperty(STARTUP_DEPLOYER_PROPERTY_PREFIX, \"Tcp\", \"ProbePort\");\n\n        if (StringUtils.hasText(probePortValue)) {\n            if (!probePortValue.chars().allMatch(Character::isDigit)) {\n                throw new IllegalArgumentException(\"StartupTcpProbePort must contain all digits\");\n            }\n\n            return Integer.parseInt(probePortValue);\n        }\n\n        if (getKubernetesDeployerProperties().getStartupTcpProbePort() != null) {\n            return getKubernetesDeployerProperties().getStartupTcpProbePort();\n        }\n\n        throw new IllegalArgumentException(\"A startupTcpProbePort property must be set.\");\n    }\n\n    @Override\n    int getFailure() {\n        return getProbeIntProperty(STARTUP_DEPLOYER_PROPERTY_PREFIX, \"Tcp\", \"ProbeFailure\",\n                getKubernetesDeployerProperties().getStartupTcpProbeFailure());\n    }\n\n    @Override\n    int getSuccess() {\n        return getProbeIntProperty(STARTUP_DEPLOYER_PROPERTY_PREFIX, \"Tcp\", \"ProbeSuccess\",\n                getKubernetesDeployerProperties().getStartupTcpProbeSuccess());\n    }\n}\n"
  },
  {
    "path": "spring-cloud-deployer-kubernetes/src/main/java/org/springframework/cloud/deployer/spi/kubernetes/TcpProbeCreator.java",
    "content": "/*\n * Copyright 2020 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.kubernetes;\n\nimport io.fabric8.kubernetes.api.model.Probe;\nimport io.fabric8.kubernetes.api.model.ProbeBuilder;\n\n/**\n * Base class for TCP based probe creators\n *\n * @author Chris Schaefer\n * @since 2.5\n */\nabstract class TcpProbeCreator extends ProbeCreator {\n    TcpProbeCreator(KubernetesDeployerProperties kubernetesDeployerProperties,\n                    ContainerConfiguration containerConfiguration) {\n        super(kubernetesDeployerProperties, containerConfiguration);\n    }\n\n    abstract Integer getPort();\n\n    protected abstract int getTimeout();\n\n    protected Probe create() {\n        return new ProbeBuilder()\n                .withNewTcpSocket()\n                .withNewPort(getPort())\n                .endTcpSocket()\n                .withInitialDelaySeconds(getInitialDelay())\n                .withPeriodSeconds(getPeriod())\n                .withFailureThreshold(getFailure())\n                .withSuccessThreshold(getSuccess())\n                .build();\n    }\n}\n"
  },
  {
    "path": "spring-cloud-deployer-kubernetes/src/main/java/org/springframework/cloud/deployer/spi/kubernetes/support/ArgumentSanitizer.java",
    "content": "/*\n * Copyright 2017-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.kubernetes.support;\n\nimport java.util.ArrayList;\nimport java.util.LinkedHashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.regex.Pattern;\n\nimport org.springframework.util.CollectionUtils;\nimport org.springframework.util.StringUtils;\n\n/**\n * Sanitizes potentially sensitive keys for a specific command line arg.\n *\n * @author Glenn Renfro\n * @author Gunnar Hillert\n * @author Ilayaperumal Gopinathan\n * @author Corneil du Plessis\n */\npublic class ArgumentSanitizer {\n\n\tprivate static final String[] REGEX_PARTS = { \"*\", \"$\", \"^\", \"+\" };\n\n\tprivate static final String REDACTION_STRING = \"******\";\n\n\tprivate static final String[] KEYS_TO_SANITIZE = { \"username\", \"password\", \"secret\", \"key\", \"token\", \".*credentials.*\",\n\t\t\t\"vcap_services\", \"url\" };\n\n\tprivate Pattern[] keysToSanitize;\n\n\tpublic ArgumentSanitizer() {\n\t\tthis.keysToSanitize = new Pattern[KEYS_TO_SANITIZE.length];\n\t\tfor (int i = 0; i < keysToSanitize.length; i++) {\n\t\t\tthis.keysToSanitize[i] = getPattern(KEYS_TO_SANITIZE[i]);\n\t\t}\n\t}\n\n\tprivate Pattern getPattern(String value) {\n\t\tif (isRegex(value)) {\n\t\t\treturn Pattern.compile(value, Pattern.CASE_INSENSITIVE);\n\t\t}\n\t\treturn Pattern.compile(\".*\" + value + \"$\", Pattern.CASE_INSENSITIVE);\n\t}\n\n\tprivate boolean isRegex(String value) {\n\t\tfor (String part : REGEX_PARTS) {\n\t\t\tif (value.contains(part)) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\t\treturn false;\n\t}\n\n\t/**\n\t * Replaces a potential secure value with \"******\".\n\t *\n\t * @param argument the argument to cleanse.\n\t * @return the argument with a potentially sanitized value\n\t */\n\tpublic String sanitize(String argument) {\n\t\tint indexOfFirstEqual = argument.indexOf(\"=\");\n\t\tif (indexOfFirstEqual == -1) {\n\t\t\treturn argument;\n\t\t}\n\t\tString key = argument.substring(0, indexOfFirstEqual);\n\t\tString value = argument.substring(indexOfFirstEqual + 1);\n\n\t\tvalue = sanitize(key, value);\n\n\t\treturn String.format(\"%s=%s\", key, value);\n\t}\n\n\t/**\n\t * Replaces a potential secure value with \"******\".\n\t *\n\t * @param key to check for sensitive words.\n\t * @param value the argument to cleanse.\n\t * @return the argument with a potentially sanitized value\n\t */\n\tpublic String sanitize(String key, String value) {\n\t\tif (StringUtils.hasText(value)) {\n\t\t\tfor (Pattern pattern : this.keysToSanitize) {\n\t\t\t\tif (pattern.matcher(key).matches()) {\n\t\t\t\t\tvalue = REDACTION_STRING;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn value;\n\t}\n\n\t/**\n\t * For all sensitive properties (e.g. key names containing words like password, secret,\n\t * key, token) replace the value with '*****' string\n\t * @param properties to be sanitized\n\t * @return sanitized properties\n\t */\n\tpublic Map<String, String> sanitizeProperties(Map<String, String> properties) {\n\t\tif (!CollectionUtils.isEmpty(properties)) {\n\t\t\tfinal Map<String, String> sanitizedProperties = new LinkedHashMap<>(properties.size());\n\t\t\tfor (Map.Entry<String, String > property : properties.entrySet()) {\n\t\t\t\tsanitizedProperties.put(property.getKey(), this.sanitize(property.getKey(), property.getValue()));\n\t\t\t}\n\t\t\treturn sanitizedProperties;\n\t\t}\n\t\treturn properties;\n\t}\n\n\t/**\n\t * For all sensitive arguments (e.g. key names containing words like password, secret,\n\t * key, token) replace the value with '*****' string\n\t * @param arguments to be sanitized\n\t * @return sanitized arguments\n\t */\n\tpublic List<String> sanitizeArguments(List<String> arguments) {\n\t\tif (!CollectionUtils.isEmpty(arguments)) {\n\t\t\tfinal List<String> sanitizedArguments = new ArrayList<>(arguments.size());\n\t\t\tfor (String argument : arguments) {\n\t\t\t\tsanitizedArguments.add(this.sanitize(argument));\n\t\t\t}\n\t\t\treturn sanitizedArguments;\n\t\t}\n\t\treturn arguments;\n\t}\n\n}\n"
  },
  {
    "path": "spring-cloud-deployer-kubernetes/src/main/java/org/springframework/cloud/deployer/spi/kubernetes/support/PropertyParserUtils.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.kubernetes.support;\n\nimport java.util.HashMap;\nimport java.util.Iterator;\nimport java.util.Map;\n\nimport org.springframework.util.Assert;\nimport org.springframework.util.StringUtils;\n\n/**\n * Utility methods for formatting and parsing properties\n *\n * @author Chris Schaefer\n * @author Ilayaperumal Gopinathan\n * @author Glenn Renfro\n */\npublic class PropertyParserUtils {\n\t/**\n\t * Extracts annotations from the provided value\n\t *\n\t * @param stringPairs The deployment request annotations\n\t * @return {@link Map} of annotations\n\t */\n\tpublic static Map<String, String> getStringPairsToMap(String stringPairs) {\n\t\tMap<String, String> mapValue = new HashMap<>();\n\n\t\tif (StringUtils.hasText(stringPairs)) {\n\t\t\t/**\n\t\t\t * Positive look ahead that into a non capturing group that will skip all commas in quotes.\n\t\t\t * Even number quotes will be ignored by the non capturing group.\n\t\t\t */\n\t\t\tString[] pairs = stringPairs.split(\",(?=(?:[^\\\"]*\\\"[^\\\"]*\\\")*[^\\\"]*$)\", -1);\n\t\t\tfor (String pair : pairs) {\n\t\t\t\tString[] splitString = pair.split(\":\", 2);\n\t\t\t\tAssert.isTrue(splitString.length == 2, String.format(\"Invalid annotation value: %s\", pair));\n\t\t\t\tString value = splitString[1].trim();\n\t\t\t\tmapValue.put(splitString[0].trim(), value);\n\t\t\t}\n\t\t}\n\t\treturn mapValue;\n\t}\n\n\tpublic static String getDeploymentPropertyValue(Map<String, String> deploymentProperties, String propertyName) {\n\t\treturn getDeploymentPropertyValue(deploymentProperties, propertyName, null);\n\t}\n\n\tpublic static String getDeploymentPropertyValue(Map<String, String> deploymentProperties, String propertyName,\n\t\t\tString defaultValue) {\n\t\tRelaxedNames relaxedNames = new RelaxedNames(propertyName);\n\t\tfor (Iterator<String> itr = relaxedNames.iterator(); itr.hasNext();) {\n\t\t\tString relaxedName = itr.next();\n\t\t\tif (deploymentProperties.containsKey(relaxedName)) {\n\t\t\t\treturn deploymentProperties.get(relaxedName);\n\t\t\t}\n\t\t}\n\t\treturn defaultValue;\n\t}\n}\n"
  },
  {
    "path": "spring-cloud-deployer-kubernetes/src/main/java/org/springframework/cloud/deployer/spi/kubernetes/support/RelaxedNames.java",
    "content": "/*\n * Copyright 2012-2019 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.kubernetes.support;\n\nimport java.util.Iterator;\nimport java.util.LinkedHashSet;\nimport java.util.Locale;\nimport java.util.Set;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\n\nimport org.springframework.util.StringUtils;\n\n/**\n * Generates relaxed name variations from a given source.\n *\n * @author Phillip Webb\n * @author Dave Syer\n */\npublic final class RelaxedNames implements Iterable<String> {\n\n\tprivate static final Pattern CAMEL_CASE_PATTERN = Pattern.compile(\"([^A-Z-])([A-Z])\");\n\n\tprivate static final Pattern SEPARATED_TO_CAMEL_CASE_PATTERN = Pattern\n\t\t\t.compile(\"[_\\\\-.]\");\n\n\tprivate final String name;\n\n\tprivate final Set<String> values = new LinkedHashSet<String>();\n\n\t/**\n\t * Create a new {@link RelaxedNames} instance.\n\t * @param name the source name. For the maximum number of variations specify the name\n\t * using dashed notation (e.g. {@literal my-property-name}\n\t */\n\tpublic RelaxedNames(String name) {\n\t\tthis.name = (name != null ? name : \"\");\n\t\tinitialize(RelaxedNames.this.name, this.values);\n\t}\n\n\t@Override\n\tpublic Iterator<String> iterator() {\n\t\treturn this.values.iterator();\n\t}\n\n\tprivate void initialize(String name, Set<String> values) {\n\t\tif (values.contains(name)) {\n\t\t\treturn;\n\t\t}\n\t\tfor (Variation variation : Variation.values()) {\n\t\t\tfor (Manipulation manipulation : Manipulation.values()) {\n\t\t\t\tString result = name;\n\t\t\t\tresult = manipulation.apply(result);\n\t\t\t\tresult = variation.apply(result);\n\t\t\t\tvalues.add(result);\n\t\t\t\tinitialize(result, values);\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Name variations.\n\t */\n\tenum Variation {\n\n\t\tNONE {\n\n\t\t\t@Override\n\t\t\tpublic String apply(String value) {\n\t\t\t\treturn value;\n\t\t\t}\n\n\t\t},\n\n\t\tLOWERCASE {\n\n\t\t\t@Override\n\t\t\tpublic String apply(String value) {\n\t\t\t\treturn (value.isEmpty() ? value : value.toLowerCase(Locale.ROOT));\n\t\t\t}\n\n\t\t},\n\n\t\tUPPERCASE {\n\n\t\t\t@Override\n\t\t\tpublic String apply(String value) {\n\t\t\t\treturn (value.isEmpty() ? value : value.toUpperCase(Locale.ROOT));\n\t\t\t}\n\n\t\t};\n\n\t\tpublic abstract String apply(String value);\n\n\t}\n\n\t/**\n\t * Name manipulations.\n\t */\n\tenum Manipulation {\n\n\t\tNONE {\n\n\t\t\t@Override\n\t\t\tpublic String apply(String value) {\n\t\t\t\treturn value;\n\t\t\t}\n\n\t\t},\n\n\t\tHYPHEN_TO_UNDERSCORE {\n\n\t\t\t@Override\n\t\t\tpublic String apply(String value) {\n\t\t\t\treturn (value.indexOf('-') != -1 ? value.replace('-', '_') : value);\n\t\t\t}\n\n\t\t},\n\n\t\tUNDERSCORE_TO_PERIOD {\n\n\t\t\t@Override\n\t\t\tpublic String apply(String value) {\n\t\t\t\treturn (value.indexOf('_') != -1 ? value.replace('_', '.') : value);\n\t\t\t}\n\n\t\t},\n\n\t\tPERIOD_TO_UNDERSCORE {\n\n\t\t\t@Override\n\t\t\tpublic String apply(String value) {\n\t\t\t\treturn (value.indexOf('.') != -1 ? value.replace('.', '_') : value);\n\t\t\t}\n\n\t\t},\n\n\t\tCAMELCASE_TO_UNDERSCORE {\n\n\t\t\t@Override\n\t\t\tpublic String apply(String value) {\n\t\t\t\tif (value.isEmpty()) {\n\t\t\t\t\treturn value;\n\t\t\t\t}\n\t\t\t\tMatcher matcher = CAMEL_CASE_PATTERN.matcher(value);\n\t\t\t\tif (!matcher.find()) {\n\t\t\t\t\treturn value;\n\t\t\t\t}\n\t\t\t\tmatcher = matcher.reset();\n\t\t\t\tStringBuffer result = new StringBuffer();\n\t\t\t\twhile (matcher.find()) {\n\t\t\t\t\tmatcher.appendReplacement(result, matcher.group(1) + '_'\n\t\t\t\t\t\t\t+ StringUtils.uncapitalize(matcher.group(2)));\n\t\t\t\t}\n\t\t\t\tmatcher.appendTail(result);\n\t\t\t\treturn result.toString();\n\t\t\t}\n\n\t\t},\n\n\t\tCAMELCASE_TO_HYPHEN {\n\n\t\t\t@Override\n\t\t\tpublic String apply(String value) {\n\t\t\t\tif (value.isEmpty()) {\n\t\t\t\t\treturn value;\n\t\t\t\t}\n\t\t\t\tMatcher matcher = CAMEL_CASE_PATTERN.matcher(value);\n\t\t\t\tif (!matcher.find()) {\n\t\t\t\t\treturn value;\n\t\t\t\t}\n\t\t\t\tmatcher = matcher.reset();\n\t\t\t\tStringBuffer result = new StringBuffer();\n\t\t\t\twhile (matcher.find()) {\n\t\t\t\t\tmatcher.appendReplacement(result, matcher.group(1) + '-'\n\t\t\t\t\t\t\t+ StringUtils.uncapitalize(matcher.group(2)));\n\t\t\t\t}\n\t\t\t\tmatcher.appendTail(result);\n\t\t\t\treturn result.toString();\n\t\t\t}\n\n\t\t},\n\n\t\tSEPARATED_TO_CAMELCASE {\n\n\t\t\t@Override\n\t\t\tpublic String apply(String value) {\n\t\t\t\treturn separatedToCamelCase(value, false);\n\t\t\t}\n\n\t\t},\n\n\t\tCASE_INSENSITIVE_SEPARATED_TO_CAMELCASE {\n\n\t\t\t@Override\n\t\t\tpublic String apply(String value) {\n\t\t\t\treturn separatedToCamelCase(value, true);\n\t\t\t}\n\n\t\t};\n\n\t\tprivate static final char[] SUFFIXES = new char[] { '_', '-', '.' };\n\n\t\tpublic abstract String apply(String value);\n\n\t\tprivate static String separatedToCamelCase(String value,\n\t\t\t\tboolean caseInsensitive) {\n\t\t\tif (value.isEmpty()) {\n\t\t\t\treturn value;\n\t\t\t}\n\t\t\tStringBuilder builder = new StringBuilder();\n\t\t\tfor (String field : SEPARATED_TO_CAMEL_CASE_PATTERN.split(value)) {\n\t\t\t\tfield = (caseInsensitive ? field.toLowerCase(Locale.ROOT) : field);\n\t\t\t\tbuilder.append(\n\t\t\t\t\t\tbuilder.length() != 0 ? StringUtils.capitalize(field) : field);\n\t\t\t}\n\t\t\tchar lastChar = value.charAt(value.length() - 1);\n\t\t\tfor (char suffix : SUFFIXES) {\n\t\t\t\tif (lastChar == suffix) {\n\t\t\t\t\tbuilder.append(suffix);\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn builder.toString();\n\t\t}\n\n\t}\n\n\t/**\n\t * Return a {@link RelaxedNames} for the given source camelCase source name.\n\t * @param name the source name in camelCase\n\t * @return the relaxed names\n\t */\n\tpublic static RelaxedNames forCamelCase(String name) {\n\t\tStringBuilder result = new StringBuilder();\n\t\tfor (char c : name.toCharArray()) {\n\t\t\tresult.append(Character.isUpperCase(c) && result.length() > 0\n\t\t\t\t\t&& result.charAt(result.length() - 1) != '-'\n\t\t\t\t\t\t\t? \"-\" + Character.toLowerCase(c) : c);\n\t\t}\n\t\treturn new RelaxedNames(result.toString());\n\t}\n\n}\n"
  },
  {
    "path": "spring-cloud-deployer-kubernetes/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports",
    "content": "org.springframework.cloud.deployer.spi.kubernetes.KubernetesAutoConfiguration\n"
  },
  {
    "path": "spring-cloud-deployer-kubernetes/src/main/resources/META-INF/spring.factories",
    "content": "org.springframework.boot.autoconfigure.EnableAutoConfiguration=org.springframework.cloud.deployer.spi.kubernetes.KubernetesAutoConfiguration\n"
  },
  {
    "path": "spring-cloud-deployer-kubernetes/src/test/java/org/springframework/cloud/deployer/spi/kubernetes/AbstractKubernetesTaskLauncherIntegrationTests.java",
    "content": "/*\n * Copyright 2021-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.kubernetes;\n\nimport java.lang.reflect.Method;\nimport java.time.Duration;\nimport java.util.List;\nimport java.util.UUID;\n\nimport io.fabric8.kubernetes.api.model.Pod;\nimport io.fabric8.kubernetes.api.model.batch.v1.Job;\nimport io.fabric8.kubernetes.client.KubernetesClient;\nimport org.awaitility.core.ConditionFactory;\nimport org.junit.jupiter.api.Disabled;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.TestInfo;\n\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.cloud.deployer.resource.docker.DockerResource;\nimport org.springframework.cloud.deployer.spi.task.TaskLauncher;\nimport org.springframework.cloud.deployer.spi.test.AbstractTaskLauncherIntegrationJUnit5Tests;\nimport org.springframework.cloud.deployer.spi.test.Timeout;\nimport org.springframework.core.io.Resource;\n\nimport static org.awaitility.Awaitility.await;\n\n/**\n * Abstract base class for integration tests for {@link KubernetesTaskLauncher}.\n *\n * @author Chris Bono\n * @author Corneil du Plessis\n */\nabstract class AbstractKubernetesTaskLauncherIntegrationTests extends AbstractTaskLauncherIntegrationJUnit5Tests {\n\n\t@Autowired\n\t@SuppressWarnings(\"SpringJavaInjectionPointsAutowiringInspection\")\n\tprotected TaskLauncher taskLauncher;\n\n\t@Autowired\n\t@SuppressWarnings(\"SpringJavaInjectionPointsAutowiringInspection\")\n\tprotected KubernetesClient kubernetesClient;\n\n\t@Override\n\tprotected TaskLauncher provideTaskLauncher() {\n\t\treturn taskLauncher;\n\t}\n\n\t@Override\n\tprotected String randomName() {\n\t\treturn \"task-\" + UUID.randomUUID().toString().substring(0, 18);\n\t}\n\n\t@Override\n\tprotected Resource testApplication() {\n\t\treturn new DockerResource(\"springcloud/spring-cloud-deployer-spi-test-app:latest\");\n\t}\n\n\t@Override\n\tprotected Timeout deploymentTimeout() {\n\t\treturn new Timeout(30, 5000);\n\t}\n\n\t@Test\n\t@Override\n\t@Disabled(\"Currently reported as failed instead of cancelled\")\n\tpublic void testSimpleCancel() throws InterruptedException {\n\t\tsuper.testSimpleCancel();\n\t}\n\n\tprotected void logTestInfo(TestInfo testInfo) {\n\t\tif (log.isInfoEnabled()) {\n\t\t\tlog.info(\"Testing {}...\", testInfo.getTestMethod().map(Method::getName).orElse(testInfo.getDisplayName()));\n\t\t}\n\t}\n\n\tprotected ConditionFactory awaitWithPollAndTimeout(Timeout timeout) {\n\t\treturn await().pollInterval(Duration.ofMillis(timeout.pause))\n\t\t\t\t.atMost(Duration.ofMillis(timeout.totalTime));\n\t}\n\n\tprotected List<Pod> getPodsForTask(String taskName) {\n\t\treturn kubernetesClient.pods().withLabel(\"task-name\", taskName).list().getItems();\n\t}\n\n\tprotected List<Job> getJobsForTask(String taskName) {\n\t\treturn kubernetesClient.batch().v1().jobs().withLabel(\"task-name\", taskName).list().getItems();\n\t}\n}\n"
  },
  {
    "path": "spring-cloud-deployer-kubernetes/src/test/java/org/springframework/cloud/deployer/spi/kubernetes/DefaultContainerFactoryTests.java",
    "content": "/*\n * Copyright 2016-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.kubernetes;\n\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.UUID;\nimport java.util.stream.Collectors;\nimport java.util.stream.Stream;\n\nimport com.fasterxml.jackson.core.JsonProcessingException;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport io.fabric8.kubernetes.api.model.Container;\nimport io.fabric8.kubernetes.api.model.ContainerPort;\nimport io.fabric8.kubernetes.api.model.ContainerPortBuilder;\nimport io.fabric8.kubernetes.api.model.EnvVar;\nimport io.fabric8.kubernetes.api.model.HTTPHeader;\nimport io.fabric8.kubernetes.api.model.ObjectMeta;\nimport io.fabric8.kubernetes.api.model.Probe;\nimport io.fabric8.kubernetes.api.model.Secret;\nimport io.fabric8.kubernetes.api.model.VolumeMount;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\n\nimport org.springframework.boot.test.context.SpringBootTest;\nimport org.springframework.cloud.deployer.resource.docker.DockerResource;\nimport org.springframework.cloud.deployer.spi.core.AppDefinition;\nimport org.springframework.cloud.deployer.spi.core.AppDeploymentRequest;\nimport org.springframework.core.io.Resource;\nimport org.springframework.test.context.junit.jupiter.SpringExtension;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.assertThatThrownBy;\n\n/**\n * Unit tests for {@link DefaultContainerFactory}.\n *\n * @author Will Kennedy\n * @author Donovan Muller\n * @author Chris Schaefer\n * @author David Turanski\n * @author Ilayaperumal Gopinathan\n * @author Glenn Renfro\n */\n@ExtendWith(SpringExtension.class)\n@SpringBootTest(classes = {KubernetesAutoConfiguration.class})\npublic class DefaultContainerFactoryTests {\n\n    @Test\n    public void create() {\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n        DefaultContainerFactory defaultContainerFactory = new DefaultContainerFactory(\n                kubernetesDeployerProperties);\n\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        Resource resource = getResource();\n        Map<String, String> props = new HashMap<>();\n        props.put(\"spring.cloud.deployer.kubernetes.limits.memory\", \"128Mi\");\n        props.put(\"spring.cloud.deployer.kubernetes.environment-variables\",\n                \"JAVA_OPTIONS=-Xmx64m,KUBERNETES_NAMESPACE=test-space\");\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition,\n                resource, props);\n\n        ContainerConfiguration containerConfiguration = new ContainerConfiguration(\"app-test\", appDeploymentRequest);\n        Container container = defaultContainerFactory.create(containerConfiguration);\n        assertThat(container).isNotNull();\n        assertThat(container.getEnv().size()).isEqualTo(3);\n        EnvVar envVar1 = container.getEnv().get(0);\n        EnvVar envVar2 = container.getEnv().get(1);\n        assertThat(envVar1.getName()).isEqualTo(\"JAVA_OPTIONS\");\n        assertThat(envVar1.getValue()).isEqualTo(\"-Xmx64m\");\n        assertThat(envVar2.getName()).isEqualTo(\"KUBERNETES_NAMESPACE\");\n        assertThat(envVar2.getValue()).isEqualTo(\"test-space\");\n    }\n\n    @Test\n    public void createWithContainerCommand() {\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n        DefaultContainerFactory defaultContainerFactory = new DefaultContainerFactory(\n                kubernetesDeployerProperties);\n\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        Resource resource = getResource();\n        Map<String, String> props = new HashMap<>();\n        props.put(\"spring.cloud.deployer.kubernetes.containerCommand\",\n                \"echo arg1 'arg2'\");\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition,\n                resource, props);\n\n        ContainerConfiguration containerConfiguration = new ContainerConfiguration(\"app-test\", appDeploymentRequest);\n        Container container = defaultContainerFactory.create(containerConfiguration);\n        assertThat(container).isNotNull();\n        assertThat(container.getCommand()).containsExactly(\"echo\", \"arg1\", \"arg2\");\n    }\n\n    @Test\n    public void createWithPorts() {\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n        DefaultContainerFactory defaultContainerFactory = new DefaultContainerFactory(\n                kubernetesDeployerProperties);\n\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        Resource resource = getResource();\n        Map<String, String> props = new HashMap<>();\n        props.put(\"spring.cloud.deployer.kubernetes.containerPorts\",\n                \"8081, 8082, 65535\");\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition,\n                resource, props);\n\n        ContainerConfiguration containerConfiguration = new ContainerConfiguration(\"app-test\", appDeploymentRequest);\n        Container container = defaultContainerFactory.create(containerConfiguration);\n        assertThat(container).isNotNull();\n        List<ContainerPort> containerPorts = container.getPorts();\n        assertThat(containerPorts).isNotNull();\n        assertThat(containerPorts.size()).isEqualTo(3);\n        assertThat(containerPorts.get(0).getContainerPort()).isEqualTo(8081);\n        assertThat(containerPorts.get(1).getContainerPort()).isEqualTo(8082);\n        assertThat(containerPorts.get(2).getContainerPort()).isEqualTo(65535);\n    }\n\n    @Test\n    public void createWithInvalidPort() {\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n        DefaultContainerFactory defaultContainerFactory = new DefaultContainerFactory(\n                kubernetesDeployerProperties);\n\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        Resource resource = getResource();\n        Map<String, String> props = new HashMap<>();\n        props.put(\"spring.cloud.deployer.kubernetes.containerPorts\",\n                \"8081, 8082, invalid, 9212\");\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition,\n                resource, props);\n\n        //Attempting to create with an invalid integer set for a port should cause an exception to bubble up.\n        ContainerConfiguration containerConfiguration = new ContainerConfiguration(\"app-test\", appDeploymentRequest);\n        assertThatThrownBy(() -> {\n            defaultContainerFactory.create(containerConfiguration);\n        }).isInstanceOf(NumberFormatException.class);\n    }\n\n    @Test\n    public void createWithPortAndHostNetwork() {\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n        DefaultContainerFactory defaultContainerFactory = new DefaultContainerFactory(\n                kubernetesDeployerProperties);\n\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        Resource resource = getResource();\n        Map<String, String> props = new HashMap<>();\n        props.put(\"spring.cloud.deployer.kubernetes.containerPorts\",\n                \"8081, 8082, 65535\");\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition,\n                resource, props);\n\n        ContainerConfiguration containerConfiguration = new ContainerConfiguration(\"app-test\", appDeploymentRequest)\n                .withHostNetwork(true);\n        Container container = defaultContainerFactory.create(containerConfiguration);\n        assertThat(container).isNotNull();\n        List<ContainerPort> containerPorts = container.getPorts();\n        assertThat(containerPorts).isNotNull();\n        assertThat(containerPorts.size()).isEqualTo(3);\n        assertThat(containerPorts.get(0).getContainerPort()).isEqualTo(8081);\n        assertThat(containerPorts.get(0).getHostPort()).isEqualTo(8081);\n        assertThat(containerPorts.get(1).getContainerPort()).isEqualTo(8082);\n        assertThat(containerPorts.get(1).getHostPort()).isEqualTo(8082);\n        assertThat(containerPorts.get(2).getContainerPort()).isEqualTo(65535);\n        assertThat(containerPorts.get(2).getHostPort()).isEqualTo(65535);\n    }\n\n    @Test\n    public void createWithEntryPointStyle() throws JsonProcessingException {\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n        DefaultContainerFactory defaultContainerFactory = new DefaultContainerFactory(\n                kubernetesDeployerProperties);\n\n        Map<String, String> appProps = new HashMap<>();\n        appProps.put(\"foo.bar.baz\", \"test\");\n        AppDefinition definition = new AppDefinition(\"app-test\", appProps);\n        Resource resource = getResource();\n        Map<String, String> props = new HashMap<>();\n\n        props.put(\"spring.cloud.deployer.kubernetes.entryPointStyle\", \"shell\");\n        AppDeploymentRequest appDeploymentRequestShell = new AppDeploymentRequest(definition,\n                resource, props);\n        ContainerConfiguration shellContainerConfiguration = new ContainerConfiguration(\"app-test\",\n                appDeploymentRequestShell);\n        Container containerShell = defaultContainerFactory.create(shellContainerConfiguration);\n        assertThat(containerShell).isNotNull();\n\n        assertThat(containerShell.getEnv().get(0).getName()).isEqualTo(\"FOO_BAR_BAZ\");\n        assertThat(containerShell.getArgs()).isEmpty();\n\n        List<String> cmdLineArgs = new ArrayList<>();\n        cmdLineArgs.add(\"--foo.bar=value1\");\n        cmdLineArgs.add(\"--spring.cloud.task.executionid=1\");\n        cmdLineArgs.add(\"--spring.cloud.data.flow.platformname=platform1\");\n        cmdLineArgs.add(\"--spring.cloud.data.flow.taskappname==a1\");\n        cmdLineArgs.add(\"blah=chacha\");\n        appDeploymentRequestShell = new AppDeploymentRequest(definition,\n                resource, props, cmdLineArgs);\n        shellContainerConfiguration = new ContainerConfiguration(\"app-test\",\n                appDeploymentRequestShell);\n        containerShell = defaultContainerFactory.create(shellContainerConfiguration);\n        assertThat(containerShell).isNotNull();\n        assertThat(containerShell.getEnv()).hasSize(7);\n        assertThat(containerShell.getArgs()).isEmpty();\n        String envVarString = containerShell.getEnv().toString();\n        assertThat(envVarString.contains(\"name=FOO_BAR_BAZ, value=test\")).isTrue();\n        assertThat(envVarString.contains(\"name=FOO_BAR, value=value1\")).isTrue();\n        assertThat(envVarString.contains(\"name=SPRING_CLOUD_TASK_EXECUTIONID, value=1\")).isTrue();\n        assertThat(envVarString.contains(\"name=SPRING_CLOUD_DATA_FLOW_TASKAPPNAME, value==a1\")).isTrue();\n        assertThat(envVarString.contains(\"name=SPRING_CLOUD_DATA_FLOW_PLATFORMNAME, value=platform1\")).isTrue();\n        assertThat(envVarString.contains(\"name=BLAH, value=chacha\")).isTrue();\n\n        props.put(\"spring.cloud.deployer.kubernetes.entryPointStyle\", \"exec\");\n        AppDeploymentRequest appDeploymentRequestExec = new AppDeploymentRequest(definition,\n                resource, props);\n        ContainerConfiguration execContainerConfiguration = new ContainerConfiguration(\"app-test\",\n                appDeploymentRequestExec);\n        Container containerExec = defaultContainerFactory.create(execContainerConfiguration);\n        assertThat(containerExec).isNotNull();\n        assertThat(containerExec.getEnv()).hasSize(1);\n        assertThat(containerExec.getArgs().get(0)).isEqualTo(\"--foo.bar.baz=test\");\n\n        props.put(\"spring.cloud.deployer.kubernetes.entryPointStyle\", \"boot\");\n        AppDeploymentRequest appDeploymentRequestBoot = new AppDeploymentRequest(definition,\n                resource, props, Arrays.asList(\"--arg1=val1\", \"--arg2=val2\"));\n        ContainerConfiguration bootContainerConfiguration = new ContainerConfiguration(\"app-test\",\n                appDeploymentRequestBoot);\n        Container containerBoot = defaultContainerFactory.create(bootContainerConfiguration);\n        assertThat(containerBoot).isNotNull();\n        assertThat(containerBoot.getEnv().get(0).getName()).isEqualTo(\"SPRING_APPLICATION_JSON\");\n        assertThat(containerBoot.getEnv().get(0).getValue()).isEqualTo(new ObjectMapper().writeValueAsString(appProps));\n        assertThat(containerBoot.getArgs()).hasSize(2);\n        assertThat(containerBoot.getArgs().get(0)).isEqualTo(\"--arg1=val1\");\n        assertThat(containerBoot.getArgs().get(1)).isEqualTo(\"--arg2=val2\");\n    }\n\n    @Test\n    public void createWithVolumeMounts() {\n        // test volume mounts defined as deployer properties\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n        DefaultContainerFactory defaultContainerFactory = new DefaultContainerFactory(kubernetesDeployerProperties);\n\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        Resource resource = getResource();\n        Map<String, String> props = new HashMap<>();\n        props.put(\"spring.cloud.deployer.kubernetes.volumeMounts\",\n                \"[\"\n                        + \"{name: 'testhostpath', mountPath: '/test/hostPath'}, \"\n                        + \"{name: 'testpvc', mountPath: '/test/pvc', readOnly: 'true'}, \"\n                        + \"{name: 'testnfs', mountPath: '/test/nfs'}\"\n                        + \"]\");\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, resource, props);\n\n        ContainerConfiguration containerConfiguration = new ContainerConfiguration(\"app-test\", appDeploymentRequest);\n        Container container = defaultContainerFactory.create(containerConfiguration);\n\n        assertThat(container.getVolumeMounts()).containsOnly(\n                new VolumeMount(\"/test/hostPath\", null, \"testhostpath\", null, null, null, null),\n                new VolumeMount(\"/test/pvc\", null, \"testpvc\", true, null, null, null),\n                new VolumeMount(\"/test/nfs\", null, \"testnfs\", null, null, null, null));\n\n        // test volume mounts defined as app deployment property, overriding the deployer property\n        kubernetesDeployerProperties = new KubernetesDeployerProperties();\n        kubernetesDeployerProperties\n                .setVolumeMounts(Stream.of(\n                                new VolumeMount(\"/test/hostPath\", null, \"testhostpath\", false, null, null, null),\n                                new VolumeMount(\"/test/pvc\", null, \"testpvc\", true, null, null, null),\n                                new VolumeMount(\"/test/nfs\", null, \"testnfs\", false, null, null, null))\n                        .collect(Collectors.toList()));\n        defaultContainerFactory = new DefaultContainerFactory(kubernetesDeployerProperties);\n\n        props.clear();\n        props.put(\"spring.cloud.deployer.kubernetes.volumeMounts\",\n                \"[\"\n                        + \"{name: 'testpvc', mountPath: '/test/pvc/overridden'}, \"\n                        + \"{name: 'testnfs', mountPath: '/test/nfs/overridden', readOnly: 'true'}\"\n                        + \"]\");\n\n        containerConfiguration = new ContainerConfiguration(\"app-test\", appDeploymentRequest);\n        container = defaultContainerFactory.create(containerConfiguration);\n\n        assertThat(container.getVolumeMounts()).containsOnly(\n                new VolumeMount(\"/test/hostPath\", null, \"testhostpath\", false, null, null, null),\n                new VolumeMount(\"/test/pvc/overridden\", null, \"testpvc\", null, null, null, null),\n                new VolumeMount(\"/test/nfs/overridden\", null, \"testnfs\", true, null, null, null));\n    }\n\n    /**\n     * @deprecated {@see {@link #createCustomLivenessHttpPortFromProperties()}}\n     */\n    @Test\n    @Deprecated\n    public void createCustomLivenessPortFromProperties() {\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n        kubernetesDeployerProperties.setLivenessHttpProbePort(8090);\n        DefaultContainerFactory defaultContainerFactory = new DefaultContainerFactory(\n                kubernetesDeployerProperties);\n\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        Resource resource = getResource();\n        Map<String, String> props = new HashMap<>();\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition,\n                resource, props);\n\n        ContainerConfiguration containerConfiguration = new ContainerConfiguration(\"app-test\", appDeploymentRequest)\n                .withExternalPort(8080)\n                .withHostNetwork(true);\n        Container container = defaultContainerFactory.create(containerConfiguration);\n        assertThat(container).isNotNull();\n\n        List<ContainerPort> containerPorts = container.getPorts();\n        assertThat(containerPorts).isNotNull();\n\n        assertThat(containerPorts).as(\"Only two container ports should be set\").hasSize(2);\n        assertThat((int) containerPorts.get(0).getContainerPort()).isEqualTo(8080);\n        assertThat((int) containerPorts.get(0).getHostPort()).isEqualTo(8080);\n        assertThat((int) containerPorts.get(1).getContainerPort()).isEqualTo(8090);\n        assertThat((int) containerPorts.get(1).getHostPort()).isEqualTo(8090);\n        assertThat((int) container.getLivenessProbe().getHttpGet().getPort().getIntVal()).isEqualTo(8090);\n    }\n\n    @Test\n    public void createCustomLivenessHttpPortFromProperties() {\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n        kubernetesDeployerProperties.setLivenessHttpProbePort(8090);\n        kubernetesDeployerProperties.setStartupHttpProbePort(kubernetesDeployerProperties.getLivenessHttpProbePort());\n        DefaultContainerFactory defaultContainerFactory = new DefaultContainerFactory(\n                kubernetesDeployerProperties);\n\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        Resource resource = getResource();\n        Map<String, String> props = new HashMap<>();\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition,\n                resource, props);\n\n        ContainerConfiguration containerConfiguration = new ContainerConfiguration(\"app-test\", appDeploymentRequest)\n                .withExternalPort(8080)\n                .withHostNetwork(true);\n        Container container = defaultContainerFactory.create(containerConfiguration);\n        assertThat(container).isNotNull();\n\n        List<ContainerPort> containerPorts = container.getPorts();\n        assertThat(containerPorts).isNotNull();\n\n        assertThat(containerPorts).as(\"Only two container ports should be set\").hasSize(2);\n        assertThat((int) containerPorts.get(0).getContainerPort()).isEqualTo(8080);\n        assertThat((int) containerPorts.get(0).getHostPort()).isEqualTo(8080);\n        assertThat((int) containerPorts.get(1).getContainerPort()).isEqualTo(8090);\n        assertThat((int) containerPorts.get(1).getHostPort()).isEqualTo(8090);\n        assertThat((int) container.getLivenessProbe().getHttpGet().getPort().getIntVal()).isEqualTo(8090);\n    }\n\n    @Test\n    public void createCustomLivenessHttpProbeSchemeFromProperties() {\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n        kubernetesDeployerProperties.setLivenessHttpProbeScheme(\"HTTPS\");\n        DefaultContainerFactory defaultContainerFactory = new DefaultContainerFactory(\n                kubernetesDeployerProperties);\n\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        Resource resource = getResource();\n        Map<String, String> props = new HashMap<>();\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition,\n                resource, props);\n\n        ContainerConfiguration containerConfiguration = new ContainerConfiguration(\"app-test\", appDeploymentRequest)\n                .withExternalPort(8080)\n                .withHostNetwork(true);\n        Container container = defaultContainerFactory.create(containerConfiguration);\n        assertThat(container).isNotNull();\n\n        List<ContainerPort> containerPorts = container.getPorts();\n        assertThat(containerPorts).isNotNull();\n\n        assertThat((int) containerPorts.get(0).getContainerPort()).isEqualTo(8080);\n        assertThat((int) containerPorts.get(0).getHostPort()).isEqualTo(8080);\n        assertThat((int) container.getLivenessProbe().getHttpGet().getPort().getIntVal()).isEqualTo(8080);\n        assertThat(container.getLivenessProbe().getHttpGet().getScheme()).isEqualTo(\"HTTPS\");\n    }\n\n    @Test\n    public void createCustomReadinessHttpSchemeFromProperties() {\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n        kubernetesDeployerProperties.setReadinessHttpProbeScheme(\"HTTPS\");\n        DefaultContainerFactory defaultContainerFactory = new DefaultContainerFactory(\n                kubernetesDeployerProperties);\n\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        Resource resource = getResource();\n        Map<String, String> props = new HashMap<>();\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition,\n                resource, props);\n\n        ContainerConfiguration containerConfiguration = new ContainerConfiguration(\"app-test\", appDeploymentRequest)\n                .withHostNetwork(true)\n                .withExternalPort(8080);\n        Container container = defaultContainerFactory.create(containerConfiguration);\n        assertThat(container).isNotNull();\n\n        List<ContainerPort> containerPorts = container.getPorts();\n        assertThat(containerPorts).isNotNull();\n\n        assertThat((int) containerPorts.get(0).getContainerPort()).isEqualTo(8080);\n        assertThat((int) containerPorts.get(0).getHostPort()).isEqualTo(8080);\n        assertThat((int) container.getReadinessProbe().getHttpGet().getPort().getIntVal()).isEqualTo(8080);\n        assertThat(container.getReadinessProbe().getHttpGet().getScheme()).isEqualTo(\"HTTPS\");\n    }\n\n    @Test\n    public void createCustomLivenessAndReadinessHttpProbeSchemeFromAppRequest() {\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n        DefaultContainerFactory defaultContainerFactory = new DefaultContainerFactory(\n                kubernetesDeployerProperties);\n\n        Map<String, String> appProperties = new HashMap<>();\n        appProperties.put(\"spring.cloud.deployer.kubernetes.livenessHttpProbeScheme\", \"HTTPS\");\n        appProperties.put(\"spring.cloud.deployer.kubernetes.readinessHttpProbeScheme\", \"HTTPS\");\n\n        AppDefinition definition = new AppDefinition(\"app-test\", appProperties);\n        Resource resource = getResource();\n\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition,\n                resource, appProperties);\n\n        ContainerConfiguration containerConfiguration = new ContainerConfiguration(\"app-test\", appDeploymentRequest)\n                .withHostNetwork(true)\n                .withExternalPort(8080);\n\n        Container container = defaultContainerFactory.create(containerConfiguration);\n\n        assertThat(container).isNotNull();\n\n        assertThat(container.getReadinessProbe().getHttpGet().getPath()).isNotNull();\n        assertThat(container.getReadinessProbe().getHttpGet().getScheme()).isEqualTo(\"HTTPS\");\n\n        assertThat(container.getLivenessProbe().getHttpGet().getPath()).isNotNull();\n        assertThat(container.getLivenessProbe().getHttpGet().getScheme()).isEqualTo(\"HTTPS\");\n    }\n\n    /**\n     * @deprecated {@see {@link #createCustomLivenessHttpPortFromAppRequest()}}\n     */\n    @Test\n    @Deprecated\n    public void createCustomLivenessPortFromAppRequest() {\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n        DefaultContainerFactory defaultContainerFactory = new DefaultContainerFactory(\n                kubernetesDeployerProperties);\n\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        Resource resource = getResource();\n        Map<String, String> props = new HashMap<>();\n        props.put(\"spring.cloud.deployer.kubernetes.liveness-probe-port\", Integer.toString(8090));\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition,\n                resource, props);\n\n        ContainerConfiguration containerConfiguration = new ContainerConfiguration(\"app-test\", appDeploymentRequest)\n                .withHostNetwork(true)\n                .withExternalPort(8080);\n        Container container = defaultContainerFactory.create(containerConfiguration);\n        assertThat(container).isNotNull();\n\n        List<ContainerPort> containerPorts = container.getPorts();\n        assertThat(containerPorts).isNotNull();\n\n        assertThat(containerPorts).hasSize(2);\n        assertThat(containerPorts.get(0).getContainerPort()).isEqualTo(8080);\n        assertThat(containerPorts.get(0).getHostPort()).isEqualTo(8080);\n        assertThat(containerPorts.get(1).getContainerPort()).isEqualTo(8090);\n        assertThat(containerPorts.get(1).getHostPort()).isEqualTo(8090);\n        assertThat(container.getLivenessProbe().getHttpGet().getPort().getIntVal()).isEqualTo(8090);\n    }\n\n    @Test\n    public void createCustomLivenessHttpPortFromAppRequest() {\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n        DefaultContainerFactory defaultContainerFactory = new DefaultContainerFactory(\n                kubernetesDeployerProperties);\n\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        Resource resource = getResource();\n        Map<String, String> props = new HashMap<>();\n        props.put(\"spring.cloud.deployer.kubernetes.liveness-http-probe-port\", Integer.toString(8090));\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition,\n                resource, props);\n\n        ContainerConfiguration containerConfiguration = new ContainerConfiguration(\"app-test\", appDeploymentRequest)\n                .withHostNetwork(true)\n                .withExternalPort(8080);\n        Container container = defaultContainerFactory.create(containerConfiguration);\n        assertThat(container).isNotNull();\n\n        List<ContainerPort> containerPorts = container.getPorts();\n        assertThat(containerPorts).isNotNull();\n\n        assertThat(containerPorts).hasSize(2);\n        assertThat(containerPorts.get(0).getContainerPort()).isEqualTo(8080);\n        assertThat(containerPorts.get(0).getHostPort()).isEqualTo(8080);\n        assertThat(containerPorts.get(1).getContainerPort()).isEqualTo(8090);\n        assertThat(containerPorts.get(1).getHostPort()).isEqualTo(8090);\n        assertThat(container.getLivenessProbe().getHttpGet().getPort().getIntVal()).isEqualTo(8090);\n    }\n\n    /**\n     * @deprecated {@see {@link #createCustomReadinessHttpPortFromAppRequest()}}\n     */\n    @Test\n    @Deprecated\n    public void createCustomReadinessPortFromAppRequest() {\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n        DefaultContainerFactory defaultContainerFactory = new DefaultContainerFactory(\n                kubernetesDeployerProperties);\n\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        Resource resource = getResource();\n        Map<String, String> props = new HashMap<>();\n        props.put(\"spring.cloud.deployer.kubernetes.readinessProbePort\", Integer.toString(8090));\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition,\n                resource, props);\n\n        ContainerConfiguration containerConfiguration = new ContainerConfiguration(\"app-test\", appDeploymentRequest)\n                .withHostNetwork(true)\n                .withExternalPort(8080);\n        Container container = defaultContainerFactory.create(containerConfiguration);\n        assertThat(container).isNotNull();\n\n        List<ContainerPort> containerPorts = container.getPorts();\n        assertThat(containerPorts).isNotNull();\n\n        assertThat(containerPorts).hasSize(2);\n        assertThat(containerPorts.get(0).getContainerPort()).isEqualTo(8080);\n        assertThat(containerPorts.get(0).getHostPort()).isEqualTo(8080);\n        assertThat(containerPorts.get(1).getContainerPort()).isEqualTo(8090);\n        assertThat(containerPorts.get(1).getHostPort()).isEqualTo(8090);\n        assertThat(container.getReadinessProbe().getHttpGet().getPort().getIntVal()).isEqualTo(8090);\n    }\n\n    @Test\n    public void createCustomReadinessHttpPortFromAppRequest() {\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n        DefaultContainerFactory defaultContainerFactory = new DefaultContainerFactory(\n                kubernetesDeployerProperties);\n\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        Resource resource = getResource();\n        Map<String, String> props = new HashMap<>();\n        props.put(\"spring.cloud.deployer.kubernetes.readinessHttpProbePort\", Integer.toString(8090));\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition,\n                resource, props);\n\n        ContainerConfiguration containerConfiguration = new ContainerConfiguration(\"app-test\", appDeploymentRequest)\n                .withHostNetwork(true)\n                .withExternalPort(8080);\n        Container container = defaultContainerFactory.create(containerConfiguration);\n        assertThat(container).isNotNull();\n\n        List<ContainerPort> containerPorts = container.getPorts();\n        assertThat(containerPorts).isNotNull();\n\n        assertThat(containerPorts).hasSize(2);\n        assertThat(containerPorts.get(0).getContainerPort()).isEqualTo(8080);\n        assertThat(containerPorts.get(0).getHostPort()).isEqualTo(8080);\n        assertThat(containerPorts.get(1).getContainerPort()).isEqualTo(8090);\n        assertThat(containerPorts.get(1).getHostPort()).isEqualTo(8090);\n        assertThat(container.getReadinessProbe().getHttpGet().getPort().getIntVal()).isEqualTo(8090);\n    }\n\n    /**\n     * @deprecated {@see {@link #createCustomReadinessHttpPortFromProperties()}}\n     */\n    @Test\n    @Deprecated\n    public void createCustomReadinessPortFromProperties() {\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n        kubernetesDeployerProperties.setReadinessHttpProbePort(8090);\n        DefaultContainerFactory defaultContainerFactory = new DefaultContainerFactory(\n                kubernetesDeployerProperties);\n\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        Resource resource = getResource();\n        Map<String, String> props = new HashMap<>();\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition,\n                resource, props);\n\n        ContainerConfiguration containerConfiguration = new ContainerConfiguration(\"app-test\", appDeploymentRequest)\n                .withHostNetwork(true)\n                .withExternalPort(8080);\n        Container container = defaultContainerFactory.create(containerConfiguration);\n        assertThat(container).isNotNull();\n\n        List<ContainerPort> containerPorts = container.getPorts();\n        assertThat(containerPorts).isNotNull();\n\n        assertThat(containerPorts).hasSize(2);\n        assertThat(containerPorts.get(0).getContainerPort()).isEqualTo(8080);\n        assertThat(containerPorts.get(0).getHostPort()).isEqualTo(8080);\n        assertThat(containerPorts.get(1).getContainerPort()).isEqualTo(8090);\n        assertThat(containerPorts.get(1).getHostPort()).isEqualTo(8090);\n        assertThat(container.getReadinessProbe().getHttpGet().getPort().getIntVal()).isEqualTo(8090);\n    }\n\n    @Test\n    public void createCustomReadinessHttpPortFromProperties() {\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n        kubernetesDeployerProperties.setReadinessHttpProbePort(8090);\n        DefaultContainerFactory defaultContainerFactory = new DefaultContainerFactory(\n                kubernetesDeployerProperties);\n\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        Resource resource = getResource();\n        Map<String, String> props = new HashMap<>();\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition,\n                resource, props);\n\n        ContainerConfiguration containerConfiguration = new ContainerConfiguration(\"app-test\", appDeploymentRequest)\n                .withHostNetwork(true)\n                .withExternalPort(8080);\n        Container container = defaultContainerFactory.create(containerConfiguration);\n        assertThat(container).isNotNull();\n\n        List<ContainerPort> containerPorts = container.getPorts();\n        assertThat(containerPorts).isNotNull();\n\n        assertThat(containerPorts).hasSize(2);\n        assertThat(containerPorts.get(0).getContainerPort()).isEqualTo(8080);\n        assertThat(containerPorts.get(0).getHostPort()).isEqualTo(8080);\n        assertThat(containerPorts.get(1).getContainerPort()).isEqualTo(8090);\n        assertThat(containerPorts.get(1).getHostPort()).isEqualTo(8090);\n        assertThat(container.getReadinessProbe().getHttpGet().getPort().getIntVal()).isEqualTo(8090);\n    }\n\n    @Test\n    public void createDefaultHttpProbePorts() {\n        int defaultPort = 8080;\n\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n        DefaultContainerFactory defaultContainerFactory = new DefaultContainerFactory(\n                kubernetesDeployerProperties);\n\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        Resource resource = getResource();\n        Map<String, String> props = new HashMap<>();\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition,\n                resource, props);\n\n        ContainerConfiguration containerConfiguration = new ContainerConfiguration(\"app-test\", appDeploymentRequest)\n                .withHostNetwork(true)\n                .withExternalPort(defaultPort);\n        Container container = defaultContainerFactory.create(containerConfiguration);\n        assertThat(container).isNotNull();\n        List<ContainerPort> containerPorts = container.getPorts();\n        assertThat(containerPorts).isNotNull();\n        assertThat(containerPorts).as(\"Only the default container port should set\").hasSize(1);\n        assertThat((int) containerPorts.get(0).getContainerPort()).isEqualTo(8080);\n        assertThat((int) containerPorts.get(0).getHostPort()).isEqualTo(8080);\n        assertThat((int) container.getLivenessProbe().getHttpGet().getPort().getIntVal()).isEqualTo(8080);\n        assertThat((int) container.getReadinessProbe().getHttpGet().getPort().getIntVal()).isEqualTo(8080);\n    }\n\n    @Test\n    public void createHttpProbesWithDefaultEndpoints() {\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n        DefaultContainerFactory defaultContainerFactory = new DefaultContainerFactory(\n                kubernetesDeployerProperties);\n\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        Resource resource = getResource();\n\n        Map<String, String> props = new HashMap<>();\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition,\n                resource, props);\n\n        ContainerConfiguration containerConfiguration = new ContainerConfiguration(\"app-test\", appDeploymentRequest)\n                .withHostNetwork(true)\n                .withExternalPort(8080);\n\n        Container container = defaultContainerFactory.create(containerConfiguration);\n\n        assertThat(container).isNotNull();\n\n        assertThat(container.getReadinessProbe().getHttpGet().getPath()).isNotNull();\n        assertThat(container.getReadinessProbe().getHttpGet().getPath()).isEqualTo(HttpProbeCreator.BOOT_2_READINESS_PROBE_PATH);\n\n        assertThat(container.getLivenessProbe().getHttpGet().getPath()).isNotNull();\n        assertThat(container.getLivenessProbe().getHttpGet().getPath()).isEqualTo(HttpProbeCreator.BOOT_2_LIVENESS_PROBE_PATH);\n    }\n\n    @Test\n    public void createHttpProbesWithBoot1Endpoints() {\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n        DefaultContainerFactory defaultContainerFactory = new DefaultContainerFactory(\n                kubernetesDeployerProperties);\n\n        Map<String, String> appProperties = new HashMap<>();\n        appProperties.put(\"spring.cloud.deployer.kubernetes.boot-major-version\", \"1\");\n\n        AppDefinition definition = new AppDefinition(\"app-test\", appProperties);\n        Resource resource = getResource();\n\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition,\n                resource, appProperties);\n\n        ContainerConfiguration containerConfiguration = new ContainerConfiguration(\"app-test\", appDeploymentRequest)\n                .withHostNetwork(true)\n                .withExternalPort(8080);\n\n        Container container = defaultContainerFactory.create(containerConfiguration);\n\n        assertThat(container).isNotNull();\n\n        assertThat(container.getReadinessProbe().getHttpGet().getPath()).isNotNull();\n        assertThat(container.getReadinessProbe().getHttpGet().getPath()).isEqualTo(HttpProbeCreator.BOOT_1_READINESS_PROBE_PATH);\n\n        assertThat(container.getLivenessProbe().getHttpGet().getPath()).isNotNull();\n        assertThat(container.getLivenessProbe().getHttpGet().getPath()).isEqualTo(HttpProbeCreator.BOOT_1_LIVENESS_PROBE_PATH);\n    }\n\n    /**\n     * @deprecated {@see {@link #createHttpProbesWithOverrides()}}\n     */\n    @Test\n    @Deprecated\n    public void createProbesWithOverrides() {\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n        DefaultContainerFactory defaultContainerFactory = new DefaultContainerFactory(\n                kubernetesDeployerProperties);\n\n        Map<String, String> appProperties = new HashMap<>();\n        appProperties.put(\"spring.cloud.deployer.kubernetes.livenessProbePath\", \"/liveness\");\n        appProperties.put(\"spring.cloud.deployer.kubernetes.readinessProbePath\", \"/readiness\");\n\n        AppDefinition definition = new AppDefinition(\"app-test\", appProperties);\n        Resource resource = getResource();\n\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition,\n                resource, appProperties);\n\n        ContainerConfiguration containerConfiguration = new ContainerConfiguration(\"app-test\", appDeploymentRequest)\n                .withHostNetwork(true)\n                .withExternalPort(8080);\n\n        Container container = defaultContainerFactory.create(containerConfiguration);\n\n        assertThat(container).isNotNull();\n\n        assertThat(container.getReadinessProbe().getHttpGet().getPath()).isNotNull();\n        assertThat(container.getReadinessProbe().getHttpGet().getPath()).isEqualTo(\"/readiness\");\n\n        assertThat(container.getLivenessProbe().getHttpGet().getPath()).isNotNull();\n        assertThat(container.getLivenessProbe().getHttpGet().getPath()).isEqualTo(\"/liveness\");\n    }\n\n    @Test\n    public void createHttpProbesWithOverrides() {\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n        DefaultContainerFactory defaultContainerFactory = new DefaultContainerFactory(\n                kubernetesDeployerProperties);\n\n        Map<String, String> appProperties = new HashMap<>();\n        appProperties.put(\"spring.cloud.deployer.kubernetes.livenessHttpProbePath\", \"/liveness\");\n        appProperties.put(\"spring.cloud.deployer.kubernetes.readinessHttpProbePath\", \"/readiness\");\n\n        AppDefinition definition = new AppDefinition(\"app-test\", appProperties);\n        Resource resource = getResource();\n\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition,\n                resource, appProperties);\n\n        ContainerConfiguration containerConfiguration = new ContainerConfiguration(\"app-test\", appDeploymentRequest)\n                .withHostNetwork(true)\n                .withExternalPort(8080);\n\n        Container container = defaultContainerFactory.create(containerConfiguration);\n\n        assertThat(container).isNotNull();\n\n        assertThat(container.getReadinessProbe().getHttpGet().getPath()).isNotNull();\n        assertThat(container.getReadinessProbe().getHttpGet().getPath()).isEqualTo(\"/readiness\");\n\n        assertThat(container.getLivenessProbe().getHttpGet().getPath()).isNotNull();\n        assertThat(container.getLivenessProbe().getHttpGet().getPath()).isEqualTo(\"/liveness\");\n    }\n\n    /**\n     * @deprecated {@see {@link #createHttpProbesWithPropertyOverrides()}}\n     */\n    @Test\n    @Deprecated\n    public void createProbesWithPropertyOverrides() {\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n        kubernetesDeployerProperties.setReadinessHttpProbePath(\"/readiness\");\n        kubernetesDeployerProperties.setLivenessHttpProbePath(\"/liveness\");\n        DefaultContainerFactory defaultContainerFactory = new DefaultContainerFactory(\n                kubernetesDeployerProperties);\n\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        Resource resource = getResource();\n\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition,\n                resource, null);\n\n        ContainerConfiguration containerConfiguration = new ContainerConfiguration(\"app-test\", appDeploymentRequest)\n                .withHostNetwork(true)\n                .withExternalPort(8080);\n\n        Container container = defaultContainerFactory.create(containerConfiguration);\n\n        assertThat(container).isNotNull();\n\n        assertThat(container.getReadinessProbe().getHttpGet().getPath()).isNotNull();\n        assertThat(container.getReadinessProbe().getHttpGet().getPath()).isEqualTo(\"/readiness\");\n\n        assertThat(container.getLivenessProbe().getHttpGet().getPath()).isNotNull();\n        assertThat(container.getLivenessProbe().getHttpGet().getPath()).isEqualTo(\"/liveness\");\n    }\n\n    @Test\n    public void createHttpProbesWithPropertyOverrides() {\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n        kubernetesDeployerProperties.setReadinessHttpProbePath(\"/readiness\");\n        kubernetesDeployerProperties.setLivenessHttpProbePath(\"/liveness\");\n        DefaultContainerFactory defaultContainerFactory = new DefaultContainerFactory(\n                kubernetesDeployerProperties);\n\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        Resource resource = getResource();\n\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition,\n                resource, null);\n\n        ContainerConfiguration containerConfiguration = new ContainerConfiguration(\"app-test\", appDeploymentRequest)\n                .withHostNetwork(true)\n                .withExternalPort(8080);\n\n        Container container = defaultContainerFactory.create(containerConfiguration);\n\n        assertThat(container).isNotNull();\n\n        assertThat(container.getReadinessProbe().getHttpGet().getPath()).isNotNull();\n        assertThat(container.getReadinessProbe().getHttpGet().getPath()).isEqualTo(\"/readiness\");\n\n        assertThat(container.getLivenessProbe().getHttpGet().getPath()).isNotNull();\n        assertThat(container.getLivenessProbe().getHttpGet().getPath()).isEqualTo(\"/liveness\");\n    }\n\n    @Test\n    public void testHttpProbeCredentialsSecret() {\n        Secret secret = randomSecret();\n        String secretName = secret.getMetadata().getName();\n\n        Map<String, String> appProperties = new HashMap<>();\n        appProperties.put(\"spring.cloud.deployer.kubernetes.probeCredentialsSecret\", secretName);\n\n        AppDefinition definition = new AppDefinition(\"app-test\", appProperties);\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), appProperties);\n\n        ContainerConfiguration containerConfiguration = new ContainerConfiguration(\"app-test\", appDeploymentRequest)\n                .withExternalPort(8080)\n                .withProbeCredentialsSecret(secret);\n\n        ContainerFactory containerFactory = new DefaultContainerFactory(new KubernetesDeployerProperties());\n        Container container = containerFactory.create(containerConfiguration);\n\n        String credentials = containerConfiguration.getProbeCredentialsSecret().getData()\n                .get(HttpProbeCreator.PROBE_CREDENTIALS_SECRET_KEY_NAME);\n\n        HTTPHeader livenessProbeHeader = container.getLivenessProbe().getHttpGet().getHttpHeaders().get(0);\n        assertThat(livenessProbeHeader.getName()).isEqualTo(HttpProbeCreator.AUTHORIZATION_HEADER_NAME);\n        assertThat(livenessProbeHeader.getValue()).isEqualTo(ProbeAuthenticationType.Basic.name() + \" \" + credentials);\n\n        HTTPHeader readinessProbeHeader = container.getReadinessProbe().getHttpGet().getHttpHeaders().get(0);\n        assertThat(readinessProbeHeader.getName()).isEqualTo(HttpProbeCreator.AUTHORIZATION_HEADER_NAME);\n        assertThat(readinessProbeHeader.getValue()).isEqualTo(ProbeAuthenticationType.Basic.name() + \" \" + credentials);\n    }\n\n    @Test\n    public void testHttpProbeCredentialsInvalidSecret() {\n        Secret secret = randomSecret();\n        secret.setData(Collections.singletonMap(\"unexpectedkey\", \"dXNlcjpwYXNz\"));\n\n        String secretName = secret.getMetadata().getName();\n\n        Map<String, String> appProperties = new HashMap<>();\n        appProperties.put(\"spring.cloud.deployer.kubernetes.probeCredentialsSecret\", secretName);\n\n        AppDefinition definition = new AppDefinition(\"app-test\", appProperties);\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), appProperties);\n\n        ContainerConfiguration containerConfiguration = new ContainerConfiguration(\"app-test\", appDeploymentRequest)\n                .withExternalPort(8080)\n                .withProbeCredentialsSecret(secret);\n\n        ContainerFactory containerFactory = new DefaultContainerFactory(new KubernetesDeployerProperties());\n        assertThatThrownBy(() -> {\n            containerFactory.create(containerConfiguration);\n        }).isInstanceOf(IllegalArgumentException.class);\n    }\n\n    @Test\n    public void testHttpProbeHeadersWithoutAuth() {\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource());\n\n        ContainerConfiguration containerConfiguration = new ContainerConfiguration(\"app-test\", appDeploymentRequest)\n                .withExternalPort(8080);\n\n        ContainerFactory containerFactory = new DefaultContainerFactory(new KubernetesDeployerProperties());\n        Container container = containerFactory.create(containerConfiguration);\n\n        assertThat(container.getLivenessProbe().getHttpGet().getHttpHeaders()).isEmpty();\n        assertThat(container.getReadinessProbe().getHttpGet().getHttpHeaders()).isEmpty();\n    }\n\n    @Test\n    public void createTcpProbe() {\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n        DefaultContainerFactory defaultContainerFactory = new DefaultContainerFactory(\n                kubernetesDeployerProperties);\n\n        Map<String, String> appProperties = new HashMap<>();\n        appProperties.put(\"spring.cloud.deployer.kubernetes.probeType\", ProbeType.TCP.name());\n        appProperties.put(\"spring.cloud.deployer.kubernetes.readinessTcpProbePort\", \"5050\");\n        appProperties.put(\"spring.cloud.deployer.kubernetes.livenessTcpProbePort\", \"9090\");\n        appProperties.put(\"spring.cloud.deployer.kubernetes.startupTcpProbePort\", \"9090\");\n\n        AppDefinition definition = new AppDefinition(\"app-test\", appProperties);\n        Resource resource = getResource();\n\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, resource, appProperties);\n\n        ContainerConfiguration containerConfiguration = new ContainerConfiguration(\"app-test\", appDeploymentRequest)\n                .withHostNetwork(true)\n                .withExternalPort(8080);\n\n        Container container = defaultContainerFactory.create(containerConfiguration);\n\n        assertThat(container).isNotNull();\n\n        assertThat(container.getPorts())\n                .contains(new ContainerPortBuilder().withHostPort(5050).withContainerPort(5050).build());\n        assertThat(container.getPorts())\n                .contains(new ContainerPortBuilder().withHostPort(9090).withContainerPort(9090).build());\n\n        Probe livenessProbe = container.getLivenessProbe();\n        Probe readinessProbe = container.getReadinessProbe();\n\n        assertThat(livenessProbe).isNotNull();\n        assertThat(readinessProbe).isNotNull();\n\n        Integer livenessTcpProbePort = livenessProbe.getTcpSocket().getPort().getIntVal();\n        assertThat(livenessTcpProbePort).isNotNull();\n        assertThat(livenessTcpProbePort.intValue()).isEqualTo(9090);\n\n        Integer readinessTcpProbePort = readinessProbe.getTcpSocket().getPort().getIntVal();\n        assertThat(readinessTcpProbePort).isNotNull();\n        assertThat(readinessTcpProbePort.intValue()).isEqualTo(5050);\n\n        assertThat(livenessProbe.getPeriodSeconds()).isNotNull();\n        assertThat(readinessProbe.getPeriodSeconds()).isNotNull();\n\n        assertThat(livenessProbe.getInitialDelaySeconds()).isNotNull();\n        assertThat(readinessProbe.getInitialDelaySeconds()).isNotNull();\n    }\n\n    @Test\n    public void createTcpProbeMissingLivenessPort() {\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n        DefaultContainerFactory defaultContainerFactory = new DefaultContainerFactory(\n                kubernetesDeployerProperties);\n\n        Map<String, String> appProperties = new HashMap<>();\n        appProperties.put(\"spring.cloud.deployer.kubernetes.probeType\", ProbeType.TCP.name());\n        appProperties.put(\"spring.cloud.deployer.kubernetes.readinessTcpProbePort\", \"5050\");\n        appProperties.put(\"spring.cloud.deployer.kubernetes.startupTcpProbePort\", \"9090\");\n\n        AppDefinition definition = new AppDefinition(\"app-test\", appProperties);\n        Resource resource = getResource();\n\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, resource, appProperties);\n\n        ContainerConfiguration containerConfiguration = new ContainerConfiguration(\"app-test\", appDeploymentRequest)\n                .withHostNetwork(true)\n                .withExternalPort(8080);\n\n        assertThatThrownBy(() -> {\n            defaultContainerFactory.create(containerConfiguration);\n        }).isInstanceOf(IllegalArgumentException.class)\n                .hasMessageContaining(\"The livenessTcpProbePort property must be set\");\n    }\n\n    @Test\n    public void createTcpProbeMissingReadinessPort() {\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n        DefaultContainerFactory defaultContainerFactory = new DefaultContainerFactory(\n                kubernetesDeployerProperties);\n\n        Map<String, String> appProperties = new HashMap<>();\n        appProperties.put(\"spring.cloud.deployer.kubernetes.probeType\", ProbeType.TCP.name());\n        appProperties.put(\"spring.cloud.deployer.kubernetes.livenessTcpProbePort\", \"5050\");\n        appProperties.put(\"spring.cloud.deployer.kubernetes.startupTcpProbePort\", \"5050\");\n\n        AppDefinition definition = new AppDefinition(\"app-test\", appProperties);\n        Resource resource = getResource();\n\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, resource, appProperties);\n\n        ContainerConfiguration containerConfiguration = new ContainerConfiguration(\"app-test\", appDeploymentRequest)\n                .withHostNetwork(true)\n                .withExternalPort(8080);\n\n        assertThatThrownBy(() -> {\n            defaultContainerFactory.create(containerConfiguration);\n        }).isInstanceOf(IllegalArgumentException.class)\n                .hasMessageContaining(\"A readinessTcpProbePort property must be set\");\n    }\n\n    @Test\n    public void createReadinessTcpProbeWithNonDigitPort() {\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n        DefaultContainerFactory defaultContainerFactory = new DefaultContainerFactory(\n                kubernetesDeployerProperties);\n\n        Map<String, String> appProperties = new HashMap<>();\n        appProperties.put(\"spring.cloud.deployer.kubernetes.probeType\", ProbeType.TCP.name());\n        appProperties.put(\"spring.cloud.deployer.kubernetes.livenessTcpProbePort\", \"9090\");\n        appProperties.put(\"spring.cloud.deployer.kubernetes.readinessTcpProbePort\", \"somePort\");\n        appProperties.put(\"spring.cloud.deployer.kubernetes.startupTcpProbePort\", \"9090\");\n\n        AppDefinition definition = new AppDefinition(\"app-test\", appProperties);\n        Resource resource = getResource();\n\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, resource, appProperties);\n\n        ContainerConfiguration containerConfiguration = new ContainerConfiguration(\"app-test\", appDeploymentRequest)\n                .withHostNetwork(true)\n                .withExternalPort(8080);\n\n        assertThatThrownBy(() -> {\n            defaultContainerFactory.create(containerConfiguration);\n        }).isInstanceOf(IllegalArgumentException.class)\n                .hasMessageContaining(\"ReadinessTcpProbePort must contain all digits\");\n    }\n\n    @Test\n    public void createLivenessTcpProbeWithNonDigitPort() {\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n        DefaultContainerFactory defaultContainerFactory = new DefaultContainerFactory(\n                kubernetesDeployerProperties);\n\n        Map<String, String> appProperties = new HashMap<>();\n        appProperties.put(\"spring.cloud.deployer.kubernetes.probeType\", ProbeType.TCP.name());\n        appProperties.put(\"spring.cloud.deployer.kubernetes.livenessTcpProbePort\", \"somePort\");\n        appProperties.put(\"spring.cloud.deployer.kubernetes.readinessTcpProbePort\", \"5050\");\n        appProperties.put(\"spring.cloud.deployer.kubernetes.startupTcpProbePort\", \"9090\");\n\n        AppDefinition definition = new AppDefinition(\"app-test\", appProperties);\n        Resource resource = getResource();\n\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, resource, appProperties);\n\n        ContainerConfiguration containerConfiguration = new ContainerConfiguration(\"app-test\", appDeploymentRequest)\n                .withHostNetwork(true)\n                .withExternalPort(8080);\n\n        assertThatThrownBy(() -> {\n            defaultContainerFactory.create(containerConfiguration);\n        }).isInstanceOf(IllegalArgumentException.class)\n                .hasMessageContaining(\"LivenessTcpProbePort must contain all digits\");\n    }\n\n    @Test\n    public void createTcpProbeGlobalProperties() {\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n        kubernetesDeployerProperties.setProbeType(ProbeType.TCP);\n        kubernetesDeployerProperties.setReadinessTcpProbePort(5050);\n        kubernetesDeployerProperties.setReadinessTcpProbeDelay(1);\n        kubernetesDeployerProperties.setReadinessTcpProbePeriod(2);\n        kubernetesDeployerProperties.setLivenessTcpProbePort(9090);\n        kubernetesDeployerProperties.setLivenessTcpProbeDelay(3);\n        kubernetesDeployerProperties.setLivenessTcpProbePeriod(4);\n        kubernetesDeployerProperties.setStartupTcpProbePort(9090);\n\n        DefaultContainerFactory defaultContainerFactory = new DefaultContainerFactory(\n                kubernetesDeployerProperties);\n\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        Resource resource = getResource();\n\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, resource, null);\n\n        ContainerConfiguration containerConfiguration = new ContainerConfiguration(\"app-test\", appDeploymentRequest)\n                .withHostNetwork(true)\n                .withExternalPort(8080);\n\n        Container container = defaultContainerFactory.create(containerConfiguration);\n\n        assertThat(container).isNotNull();\n\n        assertThat(container.getPorts())\n                .contains(new ContainerPortBuilder().withHostPort(5050).withContainerPort(5050).build());\n        assertThat(container.getPorts())\n                .contains(new ContainerPortBuilder().withHostPort(9090).withContainerPort(9090).build());\n\n        Probe livenessProbe = container.getLivenessProbe();\n        Probe readinessProbe = container.getReadinessProbe();\n\n        assertThat(livenessProbe).isNotNull();\n        assertThat(readinessProbe).isNotNull();\n\n        Integer livenessTcpProbePort = livenessProbe.getTcpSocket().getPort().getIntVal();\n        assertThat(livenessTcpProbePort).isNotNull();\n        assertThat(livenessTcpProbePort.intValue()).isEqualTo(9090);\n\n        Integer readinessTcpProbePort = readinessProbe.getTcpSocket().getPort().getIntVal();\n        assertThat(readinessTcpProbePort).isNotNull();\n        assertThat(readinessTcpProbePort.intValue()).isEqualTo(5050);\n\n        Integer livenessProbePeriodSeconds = livenessProbe.getPeriodSeconds();\n        assertThat(livenessProbePeriodSeconds).isNotNull();\n        assertThat(livenessProbePeriodSeconds.intValue()).isEqualTo(4);\n\n        Integer readinessProbePeriodSeconds = readinessProbe.getPeriodSeconds();\n        assertThat(readinessProbePeriodSeconds).isNotNull();\n        assertThat(readinessProbePeriodSeconds.intValue()).isEqualTo(2);\n\n        Integer livenessProbeInitialDelaySeconds = livenessProbe.getInitialDelaySeconds();\n        assertThat(livenessProbeInitialDelaySeconds).isNotNull();\n        assertThat(livenessProbeInitialDelaySeconds.intValue()).isEqualTo(3);\n\n        Integer readinessProbeInitialDelaySeconds = readinessProbe.getInitialDelaySeconds();\n        assertThat(readinessProbeInitialDelaySeconds).isNotNull();\n        assertThat(readinessProbeInitialDelaySeconds.intValue()).isEqualTo(1);\n    }\n\n    @Test\n    public void createTcpProbeGlobalPropertyOverride() {\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n        kubernetesDeployerProperties.setProbeType(ProbeType.TCP);\n        kubernetesDeployerProperties.setReadinessTcpProbePort(5050);\n        kubernetesDeployerProperties.setReadinessTcpProbeDelay(1);\n        kubernetesDeployerProperties.setReadinessTcpProbePeriod(2);\n        kubernetesDeployerProperties.setLivenessTcpProbePort(9090);\n        kubernetesDeployerProperties.setLivenessTcpProbeDelay(3);\n        kubernetesDeployerProperties.setLivenessTcpProbePeriod(4);\n\n        DefaultContainerFactory defaultContainerFactory = new DefaultContainerFactory(\n                kubernetesDeployerProperties);\n\n        Map<String, String> appProperties = new HashMap<>();\n        appProperties.put(\"spring.cloud.deployer.kubernetes.probeType\", ProbeType.TCP.name());\n        appProperties.put(\"spring.cloud.deployer.kubernetes.readinessTcpProbePort\", \"5050\");\n        appProperties.put(\"spring.cloud.deployer.kubernetes.readinessProbePeriod\", \"11\");\n        appProperties.put(\"spring.cloud.deployer.kubernetes.readinessProbeDelay\", \"12\");\n        appProperties.put(\"spring.cloud.deployer.kubernetes.livenessTcpProbePort\", \"9090\");\n        appProperties.put(\"spring.cloud.deployer.kubernetes.livenessProbePeriod\", \"13\");\n        appProperties.put(\"spring.cloud.deployer.kubernetes.livenessProbeDelay\", \"14\");\n        appProperties.put(\"spring.cloud.deployer.kubernetes.startupTcpProbePort\", \"9090\");\n\n        AppDefinition definition = new AppDefinition(\"app-test\", appProperties);\n        Resource resource = getResource();\n\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, resource, appProperties);\n\n        ContainerConfiguration containerConfiguration = new ContainerConfiguration(\"app-test\", appDeploymentRequest)\n                .withHostNetwork(true)\n                .withExternalPort(8080);\n\n        Container container = defaultContainerFactory.create(containerConfiguration);\n\n        assertThat(container).isNotNull();\n\n        assertThat(container.getPorts())\n                .contains(new ContainerPortBuilder().withHostPort(5050).withContainerPort(5050).build());\n        assertThat(container.getPorts())\n                .contains(new ContainerPortBuilder().withHostPort(9090).withContainerPort(9090).build());\n\n        Probe livenessProbe = container.getLivenessProbe();\n        Probe readinessProbe = container.getReadinessProbe();\n\n        assertThat(livenessProbe).isNotNull();\n        assertThat(readinessProbe).isNotNull();\n\n        Integer livenessTcpProbePort = livenessProbe.getTcpSocket().getPort().getIntVal();\n        assertThat(livenessTcpProbePort).isNotNull();\n        assertThat(livenessTcpProbePort.intValue()).isEqualTo(9090);\n\n        Integer readinessTcpProbePort = readinessProbe.getTcpSocket().getPort().getIntVal();\n        assertThat(readinessTcpProbePort).isNotNull();\n        assertThat(readinessTcpProbePort.intValue()).isEqualTo(5050);\n\n        Integer livenessProbePeriodSeconds = livenessProbe.getPeriodSeconds();\n        assertThat(livenessProbePeriodSeconds).isNotNull();\n        assertThat(livenessProbePeriodSeconds.intValue()).isEqualTo(13);\n\n        Integer readinessProbePeriodSeconds = readinessProbe.getPeriodSeconds();\n        assertThat(readinessProbePeriodSeconds).isNotNull();\n        assertThat(readinessProbePeriodSeconds.intValue()).isEqualTo(11);\n\n        Integer livenessProbeInitialDelaySeconds = livenessProbe.getInitialDelaySeconds();\n        assertThat(livenessProbeInitialDelaySeconds).isNotNull();\n        assertThat(livenessProbeInitialDelaySeconds.intValue()).isEqualTo(14);\n\n        Integer readinessProbeInitialDelaySeconds = readinessProbe.getInitialDelaySeconds();\n        assertThat(readinessProbeInitialDelaySeconds).isNotNull();\n        assertThat(readinessProbeInitialDelaySeconds.intValue()).isEqualTo(12);\n    }\n\n    @Test\n    public void createCommandProbe() {\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n        DefaultContainerFactory defaultContainerFactory = new DefaultContainerFactory(\n                kubernetesDeployerProperties);\n\n        Map<String, String> appProperties = new HashMap<>();\n        appProperties.put(\"spring.cloud.deployer.kubernetes.probeType\", ProbeType.COMMAND.name());\n        appProperties.put(\"spring.cloud.deployer.kubernetes.readinessCommandProbeCommand\", \"ls /\");\n        appProperties.put(\"spring.cloud.deployer.kubernetes.livenessCommandProbeCommand\", \"ls /dev\");\n        appProperties.put(\"spring.cloud.deployer.kubernetes.startupCommandProbeCommand\", \"ls /dev\");\n\n        AppDefinition definition = new AppDefinition(\"app-test\", appProperties);\n        Resource resource = getResource();\n\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, resource, appProperties);\n\n        ContainerConfiguration containerConfiguration = new ContainerConfiguration(\"app-test\", appDeploymentRequest)\n                .withHostNetwork(true)\n                .withExternalPort(8080);\n\n        Container container = defaultContainerFactory.create(containerConfiguration);\n\n        assertThat(container).isNotNull();\n\n        Probe livenessProbe = container.getLivenessProbe();\n        Probe readinessProbe = container.getReadinessProbe();\n\n        assertThat(livenessProbe).isNotNull();\n        assertThat(readinessProbe).isNotNull();\n\n        List<String> livenessCommandProbeCommand = livenessProbe.getExec().getCommand();\n        assertThat(livenessCommandProbeCommand).containsExactly(\"ls\", \"/dev\");\n\n        List<String> readinessCommandProbeCommand = readinessProbe.getExec().getCommand();\n        assertThat(readinessCommandProbeCommand).containsExactly(\"ls\", \"/\");\n\n        assertThat(livenessProbe.getPeriodSeconds()).isNotNull();\n        assertThat(readinessProbe.getPeriodSeconds()).isNotNull();\n\n        assertThat(livenessProbe.getInitialDelaySeconds()).isNotNull();\n        assertThat(readinessProbe.getInitialDelaySeconds()).isNotNull();\n    }\n\n    @Test\n    public void createCommandProbeMissingCommand() {\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n        DefaultContainerFactory defaultContainerFactory = new DefaultContainerFactory(\n                kubernetesDeployerProperties);\n\n        Map<String, String> appProperties = new HashMap<>();\n        appProperties.put(\"spring.cloud.deployer.kubernetes.probeType\", ProbeType.COMMAND.name());\n        appProperties.put(\"spring.cloud.deployer.kubernetes.startupCommandProbeCommand\", \"ls /\");\n\n        AppDefinition definition = new AppDefinition(\"app-test\", appProperties);\n        Resource resource = getResource();\n\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, resource, appProperties);\n\n        ContainerConfiguration containerConfiguration = new ContainerConfiguration(\"app-test\", appDeploymentRequest)\n                .withHostNetwork(true)\n                .withExternalPort(8080);\n\n        assertThatThrownBy(() -> {\n            defaultContainerFactory.create(containerConfiguration);\n        }).isInstanceOf(IllegalArgumentException.class)\n                .hasMessageContaining(\"The readinessCommandProbeCommand property must be set\");\n    }\n\n    @Test\n    public void createCommandProbeGlobalProperties() {\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n        kubernetesDeployerProperties.setProbeType(ProbeType.COMMAND);\n        kubernetesDeployerProperties.setReadinessCommandProbeCommand(\"ls /\");\n        kubernetesDeployerProperties.setReadinessCommandProbeDelay(1);\n        kubernetesDeployerProperties.setReadinessCommandProbePeriod(2);\n        kubernetesDeployerProperties.setLivenessCommandProbeCommand(\"ls /dev\");\n        kubernetesDeployerProperties.setLivenessCommandProbeDelay(3);\n        kubernetesDeployerProperties.setLivenessCommandProbePeriod(4);\n        kubernetesDeployerProperties.setStartupCommandProbeCommand(\"ls /dev\");\n\n        DefaultContainerFactory defaultContainerFactory = new DefaultContainerFactory(\n                kubernetesDeployerProperties);\n\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        Resource resource = getResource();\n\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, resource, null);\n\n        ContainerConfiguration containerConfiguration = new ContainerConfiguration(\"app-test\", appDeploymentRequest)\n                .withHostNetwork(true)\n                .withExternalPort(8080);\n\n        Container container = defaultContainerFactory.create(containerConfiguration);\n\n        assertThat(container).isNotNull();\n\n        Probe livenessProbe = container.getLivenessProbe();\n        Probe readinessProbe = container.getReadinessProbe();\n\n        assertThat(livenessProbe).isNotNull();\n        assertThat(readinessProbe).isNotNull();\n\n        String livenessTcpProbeCommand = String.join(\" \", livenessProbe.getExec().getCommand());\n        assertThat(livenessTcpProbeCommand).isNotNull();\n        assertThat(livenessTcpProbeCommand).isEqualTo(\"ls /dev\");\n\n        String readinessTcpProbeCommand = String.join(\" \", readinessProbe.getExec().getCommand());\n        assertThat(readinessTcpProbeCommand).isNotNull();\n        assertThat(readinessTcpProbeCommand).isEqualTo(\"ls /\");\n\n        Integer livenessProbePeriodSeconds = livenessProbe.getPeriodSeconds();\n        assertThat(livenessProbePeriodSeconds).isNotNull();\n        assertThat(livenessProbePeriodSeconds.intValue()).isEqualTo(4);\n\n        Integer readinessProbePeriodSeconds = readinessProbe.getPeriodSeconds();\n        assertThat(readinessProbePeriodSeconds).isNotNull();\n        assertThat(readinessProbePeriodSeconds.intValue()).isEqualTo(2);\n\n        Integer livenessProbeInitialDelaySeconds = livenessProbe.getInitialDelaySeconds();\n        assertThat(livenessProbeInitialDelaySeconds).isNotNull();\n        assertThat(livenessProbeInitialDelaySeconds.intValue()).isEqualTo(3);\n\n        Integer readinessProbeInitialDelaySeconds = readinessProbe.getInitialDelaySeconds();\n        assertThat(readinessProbeInitialDelaySeconds).isNotNull();\n        assertThat(readinessProbeInitialDelaySeconds.intValue()).isEqualTo(1);\n    }\n\n    @Test\n    public void createCommandProbeGlobalPropertyOverride() {\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n        kubernetesDeployerProperties.setProbeType(ProbeType.COMMAND);\n        kubernetesDeployerProperties.setReadinessCommandProbeCommand(\"ls /\");\n        kubernetesDeployerProperties.setReadinessCommandProbeDelay(1);\n        kubernetesDeployerProperties.setReadinessCommandProbePeriod(2);\n        kubernetesDeployerProperties.setLivenessCommandProbeCommand(\"ls /dev\");\n        kubernetesDeployerProperties.setLivenessCommandProbeDelay(3);\n        kubernetesDeployerProperties.setLivenessCommandProbePeriod(4);\n\n        DefaultContainerFactory defaultContainerFactory = new DefaultContainerFactory(\n                kubernetesDeployerProperties);\n\n        Map<String, String> appProperties = new HashMap<>();\n        appProperties.put(\"spring.cloud.deployer.kubernetes.probeType\", ProbeType.COMMAND.name());\n        appProperties.put(\"spring.cloud.deployer.kubernetes.readinessCommandProbeCommand\", \"ls /\");\n        appProperties.put(\"spring.cloud.deployer.kubernetes.readinessProbePeriod\", \"11\");\n        appProperties.put(\"spring.cloud.deployer.kubernetes.readinessProbeDelay\", \"12\");\n        appProperties.put(\"spring.cloud.deployer.kubernetes.livenessCommandProbeCommand\", \"ls /dev\");\n        appProperties.put(\"spring.cloud.deployer.kubernetes.livenessProbePeriod\", \"13\");\n        appProperties.put(\"spring.cloud.deployer.kubernetes.livenessProbeDelay\", \"14\");\n        appProperties.put(\"spring.cloud.deployer.kubernetes.startupCommandProbeCommand\", \"ls /dev\");\n\n        AppDefinition definition = new AppDefinition(\"app-test\", appProperties);\n        Resource resource = getResource();\n\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, resource, appProperties);\n\n        ContainerConfiguration containerConfiguration = new ContainerConfiguration(\"app-test\", appDeploymentRequest)\n                .withHostNetwork(true)\n                .withExternalPort(8080);\n\n        Container container = defaultContainerFactory.create(containerConfiguration);\n\n        assertThat(container).isNotNull();\n\n        Probe livenessProbe = container.getLivenessProbe();\n        Probe readinessProbe = container.getReadinessProbe();\n\n        assertThat(livenessProbe).isNotNull();\n        assertThat(readinessProbe).isNotNull();\n\n        String livenessTcpProbeCommand = String.join(\" \", livenessProbe.getExec().getCommand());\n        assertThat(livenessTcpProbeCommand).isNotNull();\n        assertThat(livenessTcpProbeCommand).isEqualTo(\"ls /dev\");\n\n        String readinessTcpProbeCommand = String.join(\" \", readinessProbe.getExec().getCommand());\n        assertThat(readinessTcpProbeCommand).isNotNull();\n        assertThat(readinessTcpProbeCommand).isEqualTo(\"ls /\");\n\n        Integer livenessProbePeriodSeconds = livenessProbe.getPeriodSeconds();\n        assertThat(livenessProbePeriodSeconds).isNotNull();\n        assertThat(livenessProbePeriodSeconds.intValue()).isEqualTo(13);\n\n        Integer readinessProbePeriodSeconds = readinessProbe.getPeriodSeconds();\n        assertThat(readinessProbePeriodSeconds).isNotNull();\n        assertThat(readinessProbePeriodSeconds.intValue()).isEqualTo(11);\n\n        Integer livenessProbeInitialDelaySeconds = livenessProbe.getInitialDelaySeconds();\n        assertThat(livenessProbeInitialDelaySeconds).isNotNull();\n        assertThat(livenessProbeInitialDelaySeconds.intValue()).isEqualTo(14);\n\n        Integer readinessProbeInitialDelaySeconds = readinessProbe.getInitialDelaySeconds();\n        assertThat(readinessProbeInitialDelaySeconds).isNotNull();\n        assertThat(readinessProbeInitialDelaySeconds.intValue()).isEqualTo(12);\n    }\n\n    @Test\n    public void testCommandLineArgsOverridesExistingProperties() {\n        AppDefinition definition = new AppDefinition(\"app-test\", Collections.singletonMap(\"foo\", \"bar\"));\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), null,\n                Collections.singletonList(\"--foo=newValue\"));\n\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n        DefaultContainerFactory defaultContainerFactory = new DefaultContainerFactory(\n                kubernetesDeployerProperties);\n        assertThat(defaultContainerFactory.createCommandArgs(appDeploymentRequest)).containsExactly(\"--foo=newValue\");\n    }\n\n    @Test\n    public void testCommandLineArgsNoAssignment() {\n        List<String> args = new ArrayList();\n        args.add(\"a\");\n        args.add(\"--b = c\");\n        args.add(\"d=e\");\n        args.add(\"f = g\");\n        AppDefinition definition = new AppDefinition(\"app-test\", Collections.singletonMap(\"b\", \"d\"));\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), null,\n                args);\n\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n        DefaultContainerFactory defaultContainerFactory = new DefaultContainerFactory(\n                kubernetesDeployerProperties);\n        assertThat(defaultContainerFactory.createCommandArgs(appDeploymentRequest)).containsExactly(\"a\", \"--b = c\", \"d=e\", \"f = g\");\n    }\n\n    @Test\n    public void testCommandLineArgsExcludesMalformedProperties() {\n        Map<String, String> properties = new HashMap<>();\n        properties.put(\"sun.cpu.isalist\", \"\");\n        properties.put(\"foo\", \"bar\");\n        AppDefinition definition = new AppDefinition(\"app-test\", properties);\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource());\n\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n        DefaultContainerFactory defaultContainerFactory = new DefaultContainerFactory(\n                kubernetesDeployerProperties);\n        assertThat(defaultContainerFactory.createCommandArgs(appDeploymentRequest)).containsExactly(\"--foo=bar\");\n    }\n\n    private Resource getResource() {\n        return new DockerResource(\n                \"springcloud/spring-cloud-deployer-spi-test-app:latest\");\n    }\n\n    private Secret randomSecret() {\n        String secretName = \"secret-\" + UUID.randomUUID().toString().substring(0, 18);\n        String secretValue = \"dXNlcjpwYXNz\"; // base64 encoded string of: user:pass\n\n        ObjectMeta objectMeta = new ObjectMeta();\n        objectMeta.setName(secretName);\n\n        Secret secret = new Secret();\n        secret.setData(Collections.singletonMap(HttpProbeCreator.PROBE_CREDENTIALS_SECRET_KEY_NAME, secretValue));\n        secret.setMetadata(objectMeta);\n\n        return secret;\n    }\n}\n"
  },
  {
    "path": "spring-cloud-deployer-kubernetes/src/test/java/org/springframework/cloud/deployer/spi/kubernetes/DeploymentPropertiesResolverTests.java",
    "content": "/*\n * Copyright 2021-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.kubernetes;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.ValueSource;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n\n/**\n * Test the {@link DeploymentPropertiesResolver} class\n * @author Glenn Renfro\n */\npublic class DeploymentPropertiesResolverTests {\n\n\t@ParameterizedTest\n\t@ValueSource(booleans = {true, false})\n\tpublic void testRestartPolicy(boolean isDeprecated) {\n\t\tKubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n\t\tDeploymentPropertiesResolver deploymentPropertiesResolver = getDeploymentPropertiesResolver(isDeprecated, kubernetesDeployerProperties);\n\t\tMap<String, String> properties = new HashMap<>();\n\t\tRestartPolicy restartPolicy = deploymentPropertiesResolver.getRestartPolicy(properties);\n\t\tassertThat(restartPolicy).isEqualTo(RestartPolicy.Always);\n\t\tif (isDeprecated) {\n\t\t\tproperties.put(KubernetesSchedulerProperties.KUBERNETES_SCHEDULER_PROPERTIES_PREFIX + \".restartPolicy\", RestartPolicy.Never.name());\n\t\t}\n\t\telse {\n\t\t\tproperties.put(KubernetesDeployerProperties.KUBERNETES_DEPLOYER_PROPERTIES_PREFIX + \".restartPolicy\", RestartPolicy.Never.name());\n\t\t}\n\t\trestartPolicy = deploymentPropertiesResolver.getRestartPolicy(properties);\n\t\tassertThat(restartPolicy).isEqualTo(RestartPolicy.Never);\n\n\t}\n\n\t@ParameterizedTest\n\t@ValueSource(booleans = {true, false})\n\tpublic void testTaskServiceAccountName(boolean isDeprecated) {\n\t\tKubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n\t\tDeploymentPropertiesResolver deploymentPropertiesResolver = getDeploymentPropertiesResolver(isDeprecated, kubernetesDeployerProperties);\n\t\tMap<String, String> properties = new HashMap<>();\n\t\tString taskServiceAccountName = deploymentPropertiesResolver.getTaskServiceAccountName(properties);\n\t\tassertThat(taskServiceAccountName).isEqualTo(\"default\");\n\t\tif (isDeprecated) {\n\t\t\tproperties.put(KubernetesSchedulerProperties.KUBERNETES_SCHEDULER_PROPERTIES_PREFIX + \".taskServiceAccountName\", \"FOO\");\n\t\t}\n\t\telse {\n\t\t\tproperties.put(KubernetesDeployerProperties.KUBERNETES_DEPLOYER_PROPERTIES_PREFIX + \".task-service-account-name\", \"FOO\");\n\t\t}\n\t\ttaskServiceAccountName = deploymentPropertiesResolver.getTaskServiceAccountName(properties);\n\t\tassertThat(taskServiceAccountName).isEqualTo(\"FOO\");\n\n\t}\n\n\tprivate DeploymentPropertiesResolver getDeploymentPropertiesResolver(boolean isDeprecated, KubernetesDeployerProperties properties) {\n\t\tString propertiesPrefix = (isDeprecated) ? KubernetesSchedulerProperties.KUBERNETES_SCHEDULER_PROPERTIES_PREFIX :\n\t\t\t\tKubernetesDeployerProperties.KUBERNETES_DEPLOYER_PROPERTIES_PREFIX;\n\t\treturn new DeploymentPropertiesResolver(propertiesPrefix, properties);\n\t}\n}\n"
  },
  {
    "path": "spring-cloud-deployer-kubernetes/src/test/java/org/springframework/cloud/deployer/spi/kubernetes/EntryPointStyleTests.java",
    "content": "/*\n * Copyright 2019-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage org.springframework.cloud.deployer.spi.kubernetes;\n\nimport org.junit.jupiter.api.Test;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * Tests for {@link EntryPointStyle}\n *\n * @author Chris Schaefer\n */\npublic class EntryPointStyleTests {\n\n\t@Test\n\tpublic void testInvalidEntryPointStyleDefaulting() {\n\t\tEntryPointStyle entryPointStyle = EntryPointStyle\n\t\t\t\t.relaxedValueOf(\"unknown\");\n\t\tassertThat(entryPointStyle).isEqualTo(EntryPointStyle.exec);\n\t}\n\n\t@Test\n\tpublic void testMatchEntryPointStyle() {\n\t\tEntryPointStyle entryPointStyle = EntryPointStyle\n\t\t\t\t.relaxedValueOf(\"shell\");\n\t\tassertThat(entryPointStyle).isEqualTo(EntryPointStyle.shell);\n\t}\n\n\t@Test\n\tpublic void testMixedCaseEntryPointStyle() {\n\t\tEntryPointStyle entryPointStyle = EntryPointStyle\n\t\t\t\t.relaxedValueOf(\"bOOt\");\n\t\tassertThat(entryPointStyle).isEqualTo(EntryPointStyle.boot);\n\t}\n}\n"
  },
  {
    "path": "spring-cloud-deployer-kubernetes/src/test/java/org/springframework/cloud/deployer/spi/kubernetes/ImagePullPolicyTests.java",
    "content": "/*\n * Copyright 2016-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.kubernetes;\n\nimport org.junit.jupiter.api.Test;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * Unit tests for {@link ImagePullPolicy}.\n *\n * @author Moritz Schulze\n */\npublic class ImagePullPolicyTests {\n\n\t@Test\n\tpublic void relaxedValueOf_ignoresCase() throws Exception {\n\t\tImagePullPolicy pullPolicy = ImagePullPolicy.relaxedValueOf(\"aLWays\");\n\t\tassertThat(pullPolicy).isEqualTo(ImagePullPolicy.Always);\n\t}\n\n\t@Test\n\tpublic void relaxedValueOf_parsesValueWithDashesInsteadOfCamelCase() throws Exception {\n\t\tImagePullPolicy pullPolicy = ImagePullPolicy.relaxedValueOf(\"if-not-present\");\n\t\tassertThat(pullPolicy).isEqualTo(ImagePullPolicy.IfNotPresent);\n\t}\n\n\t@Test\n\tpublic void relaxedValueOf_returnsNullIfValueNotParseable() throws Exception {\n\t\tImagePullPolicy pullPolicy = ImagePullPolicy.relaxedValueOf(\"not-a-real-policy\");\n\t\tassertThat(pullPolicy).isNull();\n\t}\n}\n"
  },
  {
    "path": "spring-cloud-deployer-kubernetes/src/test/java/org/springframework/cloud/deployer/spi/kubernetes/KubernetesActuatorTemplateTests.java",
    "content": "/*\n * Copyright 2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.kubernetes;\n\nimport java.io.IOException;\nimport java.net.ServerSocket;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.springframework.cloud.deployer.spi.app.ActuatorOperations;\nimport org.springframework.cloud.deployer.spi.app.AppAdmin;\nimport org.springframework.cloud.deployer.spi.app.AppDeployer;\nimport org.springframework.cloud.deployer.spi.app.AppInstanceStatus;\nimport org.springframework.cloud.deployer.spi.app.AppStatus;\nimport org.springframework.cloud.deployer.spi.app.DeploymentState;\nimport org.springframework.http.HttpEntity;\nimport org.springframework.http.HttpMethod;\nimport org.springframework.http.HttpStatus;\nimport org.springframework.http.ResponseEntity;\nimport org.springframework.util.LinkedMultiValueMap;\nimport org.springframework.util.MultiValueMap;\nimport org.springframework.web.client.RestTemplate;\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.assertThatThrownBy;\nimport static org.mockito.ArgumentMatchers.anyString;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\npublic class KubernetesActuatorTemplateTests {\n\n\tprivate final RestTemplate restTemplate = mock(RestTemplate.class);\n\tprivate final AppDeployer appDeployer = mock(AppDeployer.class);\n\tprivate final ActuatorOperations actuatorOperations = new KubernetesActuatorTemplate(\n\t\trestTemplate, appDeployer, new AppAdmin());\n\n\tprivate AppInstanceStatus appInstanceStatus;\n\n\t@BeforeEach\n\tvoid setUp() {\n\t\tappInstanceStatus = mock(AppInstanceStatus.class);\n\t\tint port = findRandomOpenPort();\n\n\t\tMap<String, String> attributes = new HashMap<>();\n\t\tattributes.put(\"pod.ip\", \"127.0.0.1\");\n\t\tattributes.put(\"actuator.port\", String.valueOf(port));\n\t\tattributes.put(\"actuator.path\", \"/actuator\");\n\t\tattributes.put(\"guid\", \"test-application-0\");\n\n\t\twhen(appInstanceStatus.getAttributes()).thenReturn(attributes);\n\t\twhen(appInstanceStatus.getState()).thenReturn(DeploymentState.deployed);\n\n\t\tAppStatus appStatus = AppStatus.of(\"test-application-id\")\n\t\t\t.with(appInstanceStatus)\n\t\t\t.build();\n\n\t\twhen(appDeployer.status(anyString())).thenReturn(appStatus);\n\t\t// Mock the actual call to RestTemplate\n\t\tMap<String, Object> appInfo = new HashMap<>();\n\t\tMap<String, Object> appDetails = new HashMap<>();\n\t\tappDetails.put(\"name\", \"log-sink-rabbit\");\n\t\tappInfo.put(\"app\", appDetails);\n//\t\trestTemplate.exchange(url, HttpMethod.GET, new HttpEntity(requestHeaders), responseType)\n\t\tMultiValueMap<String, String> header  = new LinkedMultiValueMap<>();\n\t\theader.add(\"Content-Type\", \"application/json\");\n\t\theader.add(\"Accept\", \"application/json\");\n\t\twhen(restTemplate.exchange(getUrl(port) + \"/actuator/info\",HttpMethod.GET,new HttpEntity<>(header), Map.class))\n\t\t\t.thenReturn(new ResponseEntity<>(Collections.singletonMap(\"app\", Collections.singletonMap(\"name\", \"log-sink-rabbit\")), HttpStatus.OK));\n\t\twhen(restTemplate.exchange(getUrl(port) + \"/actuator/health\",HttpMethod.GET,new HttpEntity<>(header), Map.class))\n\t\t\t.thenReturn(new ResponseEntity<>(Collections.singletonMap(\"app\", Collections.singletonMap(\"status\", \"UP\")), HttpStatus.OK));\n\t\twhen(restTemplate.exchange(getUrl(port) + \"/actuator/bindings\",HttpMethod.GET,new HttpEntity<>(header), List.class))\n\t\t\t.thenReturn(new ResponseEntity<>(Collections.singletonList(Collections.singletonMap(\"bindingName\", \"input\")), HttpStatus.OK));\n\t\twhen(restTemplate.exchange(getUrl(port) + \"/actuator/bindings/input\",HttpMethod.GET,new HttpEntity<>(header), Map.class))\n\t\t\t.thenReturn(new ResponseEntity<>(Collections.singletonMap(\"bindingName\", \"input\"), HttpStatus.OK));\n\t\twhen(restTemplate.exchange(getUrl(port) + \"/actuator/bindings/input\", HttpMethod.POST, new HttpEntity<>(Collections.singletonMap(\"state\", \"STOPPED\"), header), Map.class))\n\t\t\t.thenReturn(new ResponseEntity<>(Collections.singletonMap(\"state\", \"STOPPED\"), HttpStatus.OK));\n\t\t;\n\n\t}\n\n\t@Test\n\tvoid actuatorInfo() {\n\t\tMap<String, Object> info = actuatorOperations\n\t\t\t.getFromActuator(\"test-application-id\", \"test-application-0\", \"/info\", Map.class);\n\n\t\tassertThat(((Map<?, ?>) (info.get(\"app\"))).get(\"name\")).isEqualTo(\"log-sink-rabbit\");\n\t}\n\n\t@Test\n\tvoid actuatorBindings() {\n\t\tList<?> bindings = actuatorOperations\n\t\t\t\t.getFromActuator(\"test-application-id\", \"test-application-0\", \"/bindings\", List.class);\n\n\t\tassertThat(((Map<?, ?>) (bindings.get(0))).get(\"bindingName\")).isEqualTo(\"input\");\n\t}\n\n\t@Test\n\tvoid actuatorBindingInput() {\n\t\tMap<String, Object> binding = actuatorOperations\n\t\t\t\t.getFromActuator(\"test-application-id\", \"test-application-0\", \"/bindings/input\", Map.class);\n\t\tassertThat(binding.get(\"bindingName\")).isEqualTo(\"input\");\n\t}\n\n\t@Test\n\tvoid actuatorPostBindingInput() {\n\t\tMap<String, Object> state = actuatorOperations\n\t\t\t\t.postToActuator(\"test-application-id\", \"test-application-0\", \"/bindings/input\",\n\t\t\t\t\t\tCollections.singletonMap(\"state\", \"STOPPED\"), Map.class);\n\t\tassertThat(state.get(\"state\")).isEqualTo(\"STOPPED\");\n\t}\n\n\t@Test\n\tvoid noInstanceDeployed() {\n\t\twhen(appInstanceStatus.getState()).thenReturn(DeploymentState.failed);\n\t\tassertThatThrownBy(() -> {\n\t\t\tactuatorOperations\n\t\t\t\t\t.getFromActuator(\"test-application-id\", \"test-application-0\", \"/info\", Map.class);\n\n\t\t}).isInstanceOf(IllegalStateException.class).hasMessageContaining(\"not deployed\");\n\t}\n\n\tpublic static String getUrl(int port) {\n\t\treturn \"http://127.0.0.1:\" + port;\n\t}\n\tpublic static int findRandomOpenPort() {\n\t\ttry (ServerSocket socket = new ServerSocket(0)) {\n\t\t\tsocket.setReuseAddress(true);\n\t\t\treturn socket.getLocalPort();\n\t\t} catch (IOException e) {\n\t\t\tthrow new RuntimeException(\"Failed to find a random open port\", e);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "spring-cloud-deployer-kubernetes/src/test/java/org/springframework/cloud/deployer/spi/kubernetes/KubernetesAppDeployerIntegrationIT.java",
    "content": "/*\n * Copyright 2016-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.kubernetes;\n\nimport java.io.ByteArrayOutputStream;\nimport java.io.IOException;\nimport java.lang.reflect.Type;\nimport java.time.Duration;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Base64;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.LinkedHashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.UUID;\nimport java.util.concurrent.CountDownLatch;\nimport java.util.stream.Collectors;\nimport java.util.stream.Stream;\n\nimport io.fabric8.kubernetes.api.model.ConfigMap;\nimport io.fabric8.kubernetes.api.model.Container;\nimport io.fabric8.kubernetes.api.model.ContainerBuilder;\nimport io.fabric8.kubernetes.api.model.EnvFromSource;\nimport io.fabric8.kubernetes.api.model.EnvVar;\nimport io.fabric8.kubernetes.api.model.HostPathVolumeSource;\nimport io.fabric8.kubernetes.api.model.HostPathVolumeSourceBuilder;\nimport io.fabric8.kubernetes.api.model.ObjectMeta;\nimport io.fabric8.kubernetes.api.model.PersistentVolumeClaim;\nimport io.fabric8.kubernetes.api.model.PersistentVolumeClaimSpec;\nimport io.fabric8.kubernetes.api.model.Pod;\nimport io.fabric8.kubernetes.api.model.PodSecurityContext;\nimport io.fabric8.kubernetes.api.model.PodSecurityContextBuilder;\nimport io.fabric8.kubernetes.api.model.PodSpec;\nimport io.fabric8.kubernetes.api.model.SELinuxOptions;\nimport io.fabric8.kubernetes.api.model.Secret;\nimport io.fabric8.kubernetes.api.model.SecurityContext;\nimport io.fabric8.kubernetes.api.model.SecurityContextBuilder;\nimport io.fabric8.kubernetes.api.model.Service;\nimport io.fabric8.kubernetes.api.model.ServiceAccount;\nimport io.fabric8.kubernetes.api.model.ServiceAccountBuilder;\nimport io.fabric8.kubernetes.api.model.ServicePort;\nimport io.fabric8.kubernetes.api.model.Volume;\nimport io.fabric8.kubernetes.api.model.VolumeBuilder;\nimport io.fabric8.kubernetes.api.model.VolumeMount;\nimport io.fabric8.kubernetes.api.model.apps.Deployment;\nimport io.fabric8.kubernetes.api.model.apps.StatefulSet;\nimport io.fabric8.kubernetes.api.model.apps.StatefulSetSpec;\nimport io.fabric8.kubernetes.client.KubernetesClient;\nimport io.fabric8.kubernetes.client.dsl.ExecListener;\nimport io.fabric8.kubernetes.client.dsl.ExecWatch;\nimport org.assertj.core.api.InstanceOfAssertFactories;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Disabled;\nimport org.junit.jupiter.api.Nested;\nimport org.junit.jupiter.api.Test;\nimport org.mockito.Mockito;\nimport org.mockito.stubbing.Answer;\n\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.boot.test.context.SpringBootTest;\nimport org.springframework.cloud.deployer.resource.docker.DockerResource;\nimport org.springframework.cloud.deployer.spi.app.AppAdmin;\nimport org.springframework.cloud.deployer.spi.app.AppDeployer;\nimport org.springframework.cloud.deployer.spi.app.AppInstanceStatus;\nimport org.springframework.cloud.deployer.spi.app.AppScaleRequest;\nimport org.springframework.cloud.deployer.spi.app.AppStatus;\nimport org.springframework.cloud.deployer.spi.app.DeploymentState;\nimport org.springframework.cloud.deployer.spi.core.AppDefinition;\nimport org.springframework.cloud.deployer.spi.core.AppDeploymentRequest;\nimport org.springframework.cloud.deployer.spi.test.AbstractAppDeployerIntegrationJUnit5Tests;\nimport org.springframework.cloud.deployer.spi.test.Timeout;\nimport org.springframework.core.ParameterizedTypeReference;\nimport org.springframework.core.io.Resource;\nimport org.springframework.http.HttpEntity;\nimport org.springframework.http.HttpMethod;\nimport org.springframework.http.ResponseEntity;\nimport org.springframework.util.StringUtils;\nimport org.springframework.web.client.ResourceAccessException;\nimport org.springframework.web.client.RestTemplate;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.assertThatThrownBy;\nimport static org.assertj.core.api.Assertions.entry;\nimport static org.assertj.core.api.Assertions.tuple;\nimport static org.awaitility.Awaitility.await;\nimport static org.mockito.Mockito.doAnswer;\nimport static org.mockito.Mockito.mock;\n\n/**\n * Integration tests for {@link KubernetesAppDeployer}.\n *\n * @author Thomas Risberg\n * @author Donovan Muller\n * @author David Turanski\n * @author Chris Schaefer\n * @author Christian Tzolov\n * @author Chris Bono\n * @author Corneil du Plessis\n */\n@SpringBootTest(classes = {KubernetesAutoConfiguration.class}, properties = {\n        \"logging.level.org.springframework.cloud.deployer=DEBUG\"\n})\npublic class KubernetesAppDeployerIntegrationIT extends AbstractAppDeployerIntegrationJUnit5Tests {\n\n    @Autowired\n    private AppDeployer appDeployer;\n\n    @Autowired\n    private KubernetesClient kubernetesClient;\n\n    @Override\n    protected AppDeployer provideAppDeployer() {\n        return appDeployer;\n    }\n\n    @BeforeEach\n    public void setup() {\n        if (kubernetesClient.getNamespace() == null) {\n            kubernetesClient.getConfiguration().setNamespace(\"default\");\n        }\n    }\n\n    @Test\n    public void testScaleStatefulSet() {\n        log.info(\"Testing {}...\", \"ScaleStatefulSet\");\n        KubernetesAppDeployer appDeployer = kubernetesAppDeployer();\n\n        AppDefinition definition = new AppDefinition(randomName(), null);\n        Resource resource = testApplication();\n\n        Map<String, String> props = new HashMap<>();\n        props.put(AppDeployer.COUNT_PROPERTY_KEY, \"3\");\n        props.put(AppDeployer.INDEXED_PROPERTY_KEY, \"true\");\n\n        AppDeploymentRequest request = new AppDeploymentRequest(definition, resource, props);\n\n        log.info(\"Deploying {}...\", request.getDefinition().getName());\n        Timeout timeout = deploymentTimeout();\n        String deploymentId = appDeployer.deploy(request);\n\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.deployed));\n\n        // assertThat(deploymentId, eventually(appInstanceCount(is(3))));\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> assertThat(appDeployer().status(deploymentId).getInstances()).hasSize(3));\n\n        // Ensure that a StatefulSet is deployed\n        Map<String, String> selector = Collections.singletonMap(AbstractKubernetesDeployer.SPRING_APP_KEY, deploymentId);\n        List<StatefulSet> statefulSets = kubernetesClient.apps().statefulSets().withLabels(selector).list().getItems();\n        assertThat(statefulSets).isNotNull();\n        assertThat(statefulSets).hasSize(1);\n        StatefulSet statefulSet = statefulSets.get(0);\n        StatefulSetSpec statefulSetSpec = statefulSet.getSpec();\n        assertThat(statefulSetSpec.getPodManagementPolicy()).isEqualTo(\"Parallel\");\n        assertThat(statefulSetSpec.getReplicas()).isEqualTo(3);\n        assertThat(statefulSetSpec.getServiceName()).isEqualTo(deploymentId);\n        assertThat(statefulSet.getMetadata().getName()).isEqualTo(deploymentId);\n\n        log.info(\"Scale Down {}...\", request.getDefinition().getName());\n        appDeployer.scale(new AppScaleRequest(deploymentId, 1));\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> assertThat(appDeployer().status(deploymentId).getInstances()).hasSize(1));\n\n        statefulSets = kubernetesClient.apps().statefulSets().withLabels(selector).list().getItems();\n        assertThat(statefulSets).hasSize(1);\n        statefulSetSpec = statefulSets.get(0).getSpec();\n        assertThat(statefulSetSpec.getReplicas()).isEqualTo(1);\n        assertThat(statefulSetSpec.getServiceName()).isEqualTo(deploymentId);\n        assertThat(statefulSet.getMetadata().getName()).isEqualTo(deploymentId);\n\n        appDeployer.undeploy(deploymentId);\n    }\n\n    @Test\n    public void testScaleDeployment() {\n        log.info(\"Testing {}...\", \"ScaleDeployment\");\n        KubernetesAppDeployer appDeployer = kubernetesAppDeployer();\n\n        AppDefinition definition = new AppDefinition(randomName(), null);\n        Resource resource = testApplication();\n\n        AppDeploymentRequest request = new AppDeploymentRequest(definition, resource, Collections.emptyMap());\n\n        log.info(\"Deploying {}...\", request.getDefinition().getName());\n        Timeout timeout = deploymentTimeout();\n        String deploymentId = appDeployer.deploy(request);\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.deployed));\n\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> assertThat(appDeployer().status(deploymentId).getInstances()).hasSize(1));\n\n        log.info(\"Scale Up {}...\", request.getDefinition().getName());\n        appDeployer.scale(new AppScaleRequest(deploymentId, 3));\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> assertThat(appDeployer().status(deploymentId).getInstances()).hasSize(3));\n\n        log.info(\"Scale Down {}...\", request.getDefinition().getName());\n        appDeployer.scale(new AppScaleRequest(deploymentId, 1));\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> assertThat(appDeployer().status(deploymentId).getInstances()).hasSize(1));\n\n        appDeployer.undeploy(deploymentId);\n    }\n\n    @Test\n    public void testScaleWithNonExistingApps() {\n        assertThatThrownBy(() -> appDeployer.scale(new AppScaleRequest(\"Fake App\", 10)))\n            .isInstanceOf(IllegalStateException.class);\n    }\n\n    @Test\n    public void testFailedDeploymentWithLoadBalancer() {\n        log.info(\"Testing {}...\", \"FailedDeploymentWithLoadBalancer\");\n        KubernetesDeployerProperties deployProperties = new KubernetesDeployerProperties();\n        deployProperties.setCreateLoadBalancer(true);\n        deployProperties.setLivenessHttpProbePeriod(10);\n        deployProperties.setMaxTerminatedErrorRestarts(1);\n        deployProperties.setMaxCrashLoopBackOffRestarts(1);\n        KubernetesAppDeployer lbAppDeployer = kubernetesAppDeployer(deployProperties);\n\n        AppDefinition definition = new AppDefinition(randomName(), null);\n        Resource resource = testApplication();\n\n        Map<String, String> props = new HashMap<>();\n        props.put(\"spring.cloud.deployer.kubernetes.livenessHttpProbePath\", \"/invalidpath\");\n        props.put(\"spring.cloud.deployer.kubernetes.livenessHttpProbeDelay\", \"1\");\n        props.put(\"spring.cloud.deployer.kubernetes.livenessHttpProbePeriod\", \"1\");\n\n        AppDeploymentRequest request = new AppDeploymentRequest(definition, resource, props);\n\n        log.info(\"Deploying {}...\", request.getDefinition().getName());\n\n        String deploymentId = lbAppDeployer.deploy(request);\n        Timeout timeout = deploymentTimeout();\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.failed));\n\n        log.info(\"Undeploying {}...\", deploymentId);\n        timeout = undeploymentTimeout();\n        lbAppDeployer.undeploy(deploymentId);\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.unknown));\n    }\n\n    @Test\n    public void testGoodDeploymentWithLoadBalancer() {\n        log.info(\"Testing {}...\", \"GoodDeploymentWithLoadBalancer\");\n        KubernetesDeployerProperties deployProperties = new KubernetesDeployerProperties();\n        deployProperties.setCreateLoadBalancer(true);\n        deployProperties.setMinutesToWaitForLoadBalancer(1);\n        KubernetesAppDeployer lbAppDeployer = kubernetesAppDeployer(deployProperties);\n\n        AppDefinition definition = new AppDefinition(randomName(), null);\n        Resource resource = testApplication();\n        AppDeploymentRequest request = new AppDeploymentRequest(definition, resource);\n\n        log.info(\"Deploying {}...\", request.getDefinition().getName());\n        String deploymentId = lbAppDeployer.deploy(request);\n        Timeout timeout = deploymentTimeout();\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.deployed));\n\n        log.info(\"Undeploying {}...\", deploymentId);\n        timeout = undeploymentTimeout();\n        lbAppDeployer.undeploy(deploymentId);\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.unknown));\n\n    }\n\n    @Test\n    @Disabled(\"Disabled until we can test lbs\")\n    public void testDeploymentWithLoadBalancerHasUrlAndAnnotation() {\n        log.info(\"Testing {}...\", \"DeploymentWithLoadBalancerShowsUrl\");\n        KubernetesDeployerProperties deployProperties = new KubernetesDeployerProperties();\n        deployProperties.setCreateLoadBalancer(true);\n        deployProperties.setMinutesToWaitForLoadBalancer(1);\n        KubernetesAppDeployer lbAppDeployer = kubernetesAppDeployer(deployProperties);\n\n        AppDefinition definition = new AppDefinition(randomName(), null);\n        Resource resource = testApplication();\n        AppDeploymentRequest request = new AppDeploymentRequest(definition, resource,\n                Collections.singletonMap(\"spring.cloud.deployer.kubernetes.serviceAnnotations\", \"foo:bar,fab:baz\"));\n\n        log.info(\"Deploying {}...\", request.getDefinition().getName());\n        String deploymentId = lbAppDeployer.deploy(request);\n        Timeout timeout = deploymentTimeout();\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.deployed));\n\n        log.info(\"Checking instance attributes of {}...\", request.getDefinition().getName());\n        AppStatus status = lbAppDeployer.status(deploymentId);\n        for (String inst : status.getInstances().keySet()) {\n            appDeployer().status(deploymentId).getInstances().get(inst);\n            await().pollInterval(Duration.ofMillis(timeout.pause))\n                    .atMost(Duration.ofMillis(timeout.totalTime))\n                    .untilAsserted(() -> assertThat(appDeployer().status(deploymentId).getInstances().get(inst).getAttributes()).containsKey(\"url\"));\n\n        }\n        log.info(\"Checking service annotations of {}...\", request.getDefinition().getName());\n        Map<String, String> annotations = kubernetesClient.services().withName(request.getDefinition().getName()).get()\n                .getMetadata().getAnnotations();\n        assertThat(annotations).isNotNull();\n        assertThat(annotations).hasSize(2);\n        assertThat(annotations).containsKey(\"foo\");\n        assertThat(annotations.get(\"foo\")).isEqualTo(\"bar\");\n        assertThat(annotations).containsKey(\"fab\");\n        assertThat(annotations.get(\"fab\")).isEqualTo(\"baz\");\n\n        log.info(\"Undeploying {}...\", deploymentId);\n        timeout = undeploymentTimeout();\n        lbAppDeployer.undeploy(deploymentId);\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.unknown));\n    }\n\n    @Test\n    public void testAttributes() {\n        log.info(\"Testing {}...\", \"Attributes\");\n        KubernetesDeployerProperties properties = new KubernetesDeployerProperties();\n        AppAdmin appAdmin = new AppAdmin();\n        appAdmin.setUser(\"user\");\n        appAdmin.setPassword(\"password\");\n        properties.setAppAdmin(appAdmin);\n        KubernetesAppDeployer appDeployer = kubernetesAppDeployer(properties);\n\n        AppDefinition definition = new AppDefinition(randomName(), null);\n        Resource resource = testApplication();\n        AppDeploymentRequest request = new AppDeploymentRequest(definition, resource);\n\n        log.info(\"Deploying {}...\", request.getDefinition().getName());\n        String deploymentId = appDeployer.deploy(request);\n        Timeout timeout = deploymentTimeout();\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> {\n                    assertThat(appDeployer.status(deploymentId).getInstances().values().stream()\n                            .map(AppInstanceStatus::getAttributes).filter(a ->\n                                    StringUtils.hasText(a.get(\"pod.ip\")))\n                            .findFirst()).isPresent();\n                    assertThat(appDeployer.status(deploymentId).getInstances().values().stream()\n                            .map(AppInstanceStatus::getAttributes).filter(a ->\n                                    StringUtils.hasText(a.get(\"actuator.path\")))\n                            .map(a -> a.get(\"actuator.path\"))\n                            .findFirst().orElse(null)).isEqualTo(\"/actuator\");\n\n                    assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.deployed);\n                });\n\n        log.info(\"Undeploying {}...\", deploymentId);\n        timeout = undeploymentTimeout();\n        appDeployer.undeploy(deploymentId);\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.unknown));\n    }\n\n    @Test\n    public void testDeploymentWithPodAnnotation() {\n        log.info(\"Testing {}...\", \"DeploymentWithPodAnnotation\");\n        KubernetesAppDeployer appDeployer = kubernetesAppDeployer();\n\n        AppDefinition definition = new AppDefinition(randomName(), null);\n        Resource resource = testApplication();\n        AppDeploymentRequest request = new AppDeploymentRequest(definition, resource,\n                Collections.singletonMap(\"spring.cloud.deployer.kubernetes.podAnnotations\",\n                        \"iam.amazonaws.com/role:role-arn,foo:bar\"));\n\n        log.info(\"Deploying {}...\", request.getDefinition().getName());\n        String deploymentId = appDeployer.deploy(request);\n        Timeout timeout = deploymentTimeout();\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.deployed));\n\n        log.info(\"Checking pod spec annotations of {}...\", request.getDefinition().getName());\n\n        List<Pod> pods = kubernetesClient.pods().withLabel(\"spring-deployment-id\", request.getDefinition()\n                .getName()).list().getItems();\n\n        assertThat(pods).hasSize(1);\n\n        Pod pod = pods.get(0);\n        Map<String, String> annotations = pod.getMetadata().getAnnotations();\n        log.info(\"Number of annotations found\" + annotations.size());\n        for (Map.Entry<String, String> annotationsEntry : annotations.entrySet()) {\n            log.info(\"Annotation key: \" + annotationsEntry.getKey());\n        }\n        assertThat(annotations.get(\"iam.amazonaws.com/role\")).isEqualTo(\"role-arn\");\n        assertThat(annotations.get(\"foo\")).isEqualTo(\"bar\");\n\n        log.info(\"Undeploying {}...\", deploymentId);\n        timeout = undeploymentTimeout();\n        appDeployer.undeploy(deploymentId);\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.unknown));\n    }\n\n    @Test\n    public void testDeploymentWithMountedHostPathVolume() throws IOException {\n        log.info(\"Testing {}...\", \"DeploymentWithMountedVolume\");\n        String containerPath = \"/tmp/\";\n        String subPath = randomName();\n        String mountName = \"mount\";\n\n        HostPathVolumeSource hostPathVolumeSource = new HostPathVolumeSourceBuilder()\n                .withPath(\"/tmp/\" + randomName() + '/').build();\n\n        KubernetesDeployerProperties deployProperties = new KubernetesDeployerProperties();\n        deployProperties.setVolumes(Collections.singletonList(new VolumeBuilder()\n                .withHostPath(hostPathVolumeSource)\n                .withName(mountName)\n                .build()));\n        deployProperties.setVolumeMounts(Collections.singletonList(new VolumeMount(hostPathVolumeSource.getPath(), null,\n                mountName, false, null, null, null)));\n        KubernetesAppDeployer lbAppDeployer = kubernetesAppDeployer(deployProperties);\n\n        AppDefinition definition = new AppDefinition(randomName(),\n                Collections.singletonMap(\"logging.file\", containerPath + subPath));\n        Resource resource = testApplication();\n        AppDeploymentRequest request = new AppDeploymentRequest(definition, resource);\n\n        log.info(\"Deploying {}...\", request.getDefinition().getName());\n        String deploymentId = lbAppDeployer.deploy(request);\n        Timeout timeout = deploymentTimeout();\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.deployed));\n\n        Map<String, String> selector = Collections.singletonMap(AbstractKubernetesDeployer.SPRING_APP_KEY, deploymentId);\n        PodSpec spec = kubernetesClient.pods().withLabels(selector).list().getItems().get(0).getSpec();\n        assertThat(spec.getVolumes()).isNotNull();\n        Volume volume = spec.getVolumes().stream()\n                .filter(v -> mountName.equals(v.getName()))\n                .findAny()\n                .orElseThrow(() -> new AssertionError(\"Volume not mounted\"));\n        assertThat(volume.getHostPath()).isNotNull();\n        assertThat(hostPathVolumeSource.getPath()).isEqualTo(volume.getHostPath().getPath());\n\n        log.info(\"Undeploying {}...\", deploymentId);\n        timeout = undeploymentTimeout();\n        lbAppDeployer.undeploy(deploymentId);\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.unknown));\n    }\n\n    private void verifyAppEnv(String appId) {\n        String ip = \"\";\n        int port = 0;\n\n        KubernetesDeployerProperties properties = new KubernetesDeployerProperties();\n        boolean success = false;\n\n        Service svc = kubernetesClient.services().withName(appId).get();\n        RestTemplate restTemplate = new RestTemplate();\n\n        if (svc != null && \"LoadBalancer\".equals(svc.getSpec().getType())) {\n            int tries = 0;\n            int maxWait = properties.getMinutesToWaitForLoadBalancer() * 6; // we check 6 times per minute\n            while (tries++ < maxWait && !success) {\n                if (svc.getStatus() != null && svc.getStatus().getLoadBalancer() != null &&\n                        svc.getStatus().getLoadBalancer().getIngress() != null &&\n                        !(svc.getStatus().getLoadBalancer().getIngress().isEmpty())) {\n                    ip = svc.getStatus().getLoadBalancer().getIngress().get(0).getIp();\n                    if (ip == null) {\n                        ip = svc.getStatus().getLoadBalancer().getIngress().get(0).getHostname();\n                    }\n                    port = svc.getSpec().getPorts().get(0).getPort();\n                    success = true;\n                    try {\n                        String url = String.format(\"http://%s:%d/actuator/env\", ip, port);\n                        restTemplate.exchange(url,\n                                HttpMethod.GET, HttpEntity.EMPTY,\n                                new ParameterizedTypeReference<LinkedHashMap<String, ArrayList<LinkedHashMap>>>() {\n                                    @Override\n                                    public Type getType() {\n                                        return Map.class;\n                                    }\n                                });\n                    } catch (ResourceAccessException rae) {\n                        success = false;\n                        try {\n                            Thread.sleep(5000L);\n                        } catch (InterruptedException e) {\n                        }\n                    }\n                } else {\n                    try {\n                        Thread.sleep(5000L);\n                    } catch (InterruptedException e) {\n                    }\n                    svc = kubernetesClient.services().withName(appId).get();\n                }\n            }\n            log.debug(String.format(\"LoadBalancer Ingress: %s\",\n                    svc.getStatus().getLoadBalancer().getIngress().toString()));\n        }\n\n        assertThat(success).as(\"cannot get service information for \" + appId).isFalse();\n\n        String url = String.format(\"http://%s:%d/actuator/env\", ip, port);\n        log.debug(\"getting app environment from \" + url);\n        restTemplate = new RestTemplate();\n\n        ResponseEntity<LinkedHashMap<String, ArrayList<LinkedHashMap>>> response = restTemplate.exchange(url,\n                HttpMethod.GET, HttpEntity.EMPTY,\n                new ParameterizedTypeReference<LinkedHashMap<String, ArrayList<LinkedHashMap>>>() {\n                    @Override\n                    public Type getType() {\n                        return Map.class;\n                    }\n                });\n\n        LinkedHashMap<String, ArrayList<LinkedHashMap>> env = response.getBody();\n        ArrayList<LinkedHashMap> propertySources = env.get(\"propertySources\");\n\n        String hostName = null;\n        String instanceIndex = null;\n\n        for (LinkedHashMap propertySource : propertySources) {\n            if (propertySource.get(\"name\").equals(\"systemEnvironment\")) {\n                LinkedHashMap s = (LinkedHashMap) propertySource.get(\"properties\");\n                hostName = (String) ((LinkedHashMap) s.get(\"HOSTNAME\")).get(\"value\");\n            }\n\n            if (propertySource.get(\"name\").equals(\"applicationConfig: [file:./config/application.properties]\")) {\n                LinkedHashMap s = (LinkedHashMap) propertySource.get(\"properties\");\n                instanceIndex = (String) ((LinkedHashMap) s.get(\"INSTANCE_INDEX\")).get(\"value\");\n            }\n        }\n\n        assertThat(hostName).as(\"Hostname is null\").isNotNull();\n        assertThat(instanceIndex).as(\"Instance index is null\").isNotNull();\n\n        String expectedIndex = hostName.substring(hostName.lastIndexOf(\"-\") + 1);\n        assertThat(instanceIndex).isEqualTo(expectedIndex);\n    }\n\n    @Test\n    @Disabled(\"Disabled until we can test lbs\")\n    public void testDeploymentWithGroupAndIndex() throws IOException {\n        log.info(\"Testing {}...\", \"DeploymentWithWithGroupAndIndex\");\n        KubernetesDeployerProperties deployProperties = new KubernetesDeployerProperties();\n        deployProperties.setCreateLoadBalancer(true);\n        deployProperties.setMinutesToWaitForLoadBalancer(1);\n        KubernetesAppDeployer testAppDeployer = kubernetesAppDeployer(deployProperties);\n\n        Map<String, String> appProperties = new HashMap<>();\n        appProperties.put(\"security.basic.enabled\", \"false\");\n        appProperties.put(\"management.security.enabled\", \"false\");\n        AppDefinition definition = new AppDefinition(randomName(), appProperties);\n        Resource resource = testApplication();\n        Map<String, String> props = new HashMap<>();\n        props.put(AppDeployer.GROUP_PROPERTY_KEY, \"foo\");\n        props.put(AppDeployer.INDEXED_PROPERTY_KEY, \"true\");\n\n        AppDeploymentRequest request = new AppDeploymentRequest(definition, resource, props);\n\n        log.info(\"Deploying {}...\", request.getDefinition().getName());\n        String deploymentId = testAppDeployer.deploy(request);\n        Timeout timeout = deploymentTimeout();\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.deployed));\n\n        Map<String, String> selector = Collections.singletonMap(AbstractKubernetesDeployer.SPRING_APP_KEY, deploymentId);\n        PodSpec spec = kubernetesClient.pods().withLabels(selector).list().getItems().get(0).getSpec();\n\n        Map<String, String> envVars = new HashMap<>();\n        for (EnvVar e : spec.getContainers().get(0).getEnv()) {\n            envVars.put(e.getName(), e.getValue());\n        }\n        assertThat(envVars).contains(entry(\"SPRING_CLOUD_APPLICATION_GROUP\", \"foo\"));\n        verifyAppEnv(deploymentId);\n\n        log.info(\"Undeploying {}...\", deploymentId);\n        timeout = undeploymentTimeout();\n        testAppDeployer.undeploy(deploymentId);\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.unknown));\n    }\n\n    @Test\n    public void testDeploymentServiceAccountName() {\n        log.info(\"Testing {}...\", \"DeploymentServiceAccountName\");\n\n        ServiceAccount deploymentServiceAccount = new ServiceAccountBuilder().withNewMetadata().withName(\"appsa\")\n                .endMetadata().build();\n\n        this.kubernetesClient.serviceAccounts().inNamespace(\"default\").resource(deploymentServiceAccount).create();\n\n        String serviceAccountName = deploymentServiceAccount.getMetadata().getName();\n\n        KubernetesDeployerProperties deployProperties = new KubernetesDeployerProperties();\n        deployProperties.setDeploymentServiceAccountName(serviceAccountName);\n\n        ContainerFactory containerFactory = new DefaultContainerFactory(deployProperties);\n        KubernetesAppDeployer appDeployer = kubernetesAppDeployer(deployProperties);\n\n        AppDefinition definition = new AppDefinition(randomName(), null);\n        AppDeploymentRequest request = new AppDeploymentRequest(definition, testApplication());\n\n        log.info(\"Deploying {}...\", request.getDefinition().getName());\n        String deploymentId = appDeployer.deploy(request);\n        Timeout timeout = deploymentTimeout();\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.deployed));\n\n        log.info(\"Undeploying {}...\", deploymentId);\n        timeout = undeploymentTimeout();\n        appDeployer.undeploy(deploymentId);\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.unknown));\n\n        kubernetesClient.serviceAccounts().inNamespace(\"default\").resource(deploymentServiceAccount).delete();\n    }\n\n    @Test\n    public void testCreateStatefulSet() throws Exception {\n        Map<String, String> props = new HashMap<>();\n        props.put(AppDeployer.COUNT_PROPERTY_KEY, \"3\");\n        props.put(AppDeployer.INDEXED_PROPERTY_KEY, \"true\");\n\n        AppDefinition definition = new AppDefinition(randomName(), null);\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, testApplication(), props);\n\n        KubernetesAppDeployer deployer = kubernetesAppDeployer();\n\n        log.info(\"Deploying {}...\", appDeploymentRequest.getDefinition().getName());\n        String deploymentId = deployer.deploy(appDeploymentRequest);\n        Map<String, String> idMap = deployer.createIdMap(deploymentId, appDeploymentRequest);\n\n        Timeout timeout = deploymentTimeout();\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.deployed));\n\n        Map<String, String> selector = Collections.singletonMap(AbstractKubernetesDeployer.SPRING_APP_KEY, deploymentId);\n\n        List<StatefulSet> statefulSets = kubernetesClient.apps().statefulSets().withLabels(selector).list().getItems();\n\n        assertThat(statefulSets).hasSize(1);\n\n        StatefulSet statefulSet = statefulSets.get(0);\n\n        StatefulSetSpec statefulSetSpec = statefulSet.getSpec();\n\n        List<Container> statefulSetInitContainers = statefulSetSpec.getTemplate().getSpec().getInitContainers();\n        assertThat(statefulSetInitContainers).hasSize(1);\n        Container statefulSetInitContainer = statefulSetInitContainers.get(0);\n        assertThat(statefulSetInitContainer.getImage()).isEqualTo(DeploymentPropertiesResolver.STATEFUL_SET_IMAGE_NAME);\n\t\tassertThat(statefulSetInitContainer.getSecurityContext()).isNull();\n\n        assertThat(statefulSetSpec.getPodManagementPolicy()).isEqualTo(\"Parallel\");\n        assertThat(statefulSetSpec.getReplicas()).isEqualTo(3);\n        assertThat(statefulSetSpec.getServiceName()).isEqualTo(deploymentId);\n\n        assertThat(statefulSet.getMetadata().getName()).isEqualTo(deploymentId);\n\n        assertThat(statefulSetSpec.getSelector().getMatchLabels())\n                .containsAllEntriesOf(deployer.createIdMap(deploymentId, appDeploymentRequest));\n        assertThat(statefulSetSpec.getSelector().getMatchLabels())\n                .contains(entry(KubernetesAppDeployer.SPRING_MARKER_KEY, KubernetesAppDeployer.SPRING_MARKER_VALUE));\n\n        assertThat(statefulSetSpec.getTemplate().getMetadata().getLabels()).containsAllEntriesOf(idMap);\n        assertThat(statefulSetSpec.getTemplate().getMetadata().getLabels())\n                .contains(entry(KubernetesAppDeployer.SPRING_MARKER_KEY, KubernetesAppDeployer.SPRING_MARKER_VALUE));\n\n        Container container = statefulSetSpec.getTemplate().getSpec().getContainers().get(0);\n\n        assertThat(container.getName()).isEqualTo(deploymentId);\n        assertThat(container.getPorts().get(0).getContainerPort()).isEqualTo(8080);\n        assertThat(container.getImage()).isEqualTo(testApplication().getURI().getSchemeSpecificPart());\n\n        PersistentVolumeClaim pvc = statefulSetSpec.getVolumeClaimTemplates().get(0);\n        assertThat(pvc.getMetadata().getName()).isEqualTo(deploymentId);\n\n        PersistentVolumeClaimSpec pvcSpec = pvc.getSpec();\n        assertThat(pvcSpec.getAccessModes()).containsOnly(\"ReadWriteOnce\");\n        assertThat(pvcSpec.getStorageClassName()).isNull();\n\n        assertThat(pvcSpec.getResources().getLimits().get(\"storage\").getAmount()).isEqualTo(\"10\");\n        assertThat(pvcSpec.getResources().getRequests().get(\"storage\").getAmount()).isEqualTo(\"10\");\n\n        assertThat(pvcSpec.getResources().getLimits().get(\"storage\").getFormat()).isEqualTo(\"Mi\");\n        assertThat(pvcSpec.getResources().getRequests().get(\"storage\").getFormat()).isEqualTo(\"Mi\");\n\n        log.info(\"Undeploying {}...\", deploymentId);\n        timeout = undeploymentTimeout();\n        appDeployer.undeploy(deploymentId);\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.unknown));\n    }\n\n    @Test\n    public void testCreateStatefulSetInitContainerImageNamePropOverride() throws Exception {\n        Map<String, String> props = new HashMap<>();\n        props.put(AppDeployer.COUNT_PROPERTY_KEY, \"3\");\n        props.put(AppDeployer.INDEXED_PROPERTY_KEY, \"true\");\n\n        String imageName = testApplication().getURI().getSchemeSpecificPart();\n\n        props.put(\"spring.cloud.deployer.kubernetes.statefulSetInitContainerImageName\", imageName);\n\n        AppDefinition definition = new AppDefinition(randomName(), null);\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, testApplication(), props);\n\n        KubernetesAppDeployer deployer = kubernetesAppDeployer();\n\n        log.info(\"Deploying {}...\", appDeploymentRequest.getDefinition().getName());\n        String deploymentId = deployer.deploy(appDeploymentRequest);\n\n        Timeout timeout = deploymentTimeout();\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.deployed));\n\n        Map<String, String> selector = Collections.singletonMap(AbstractKubernetesDeployer.SPRING_APP_KEY, deploymentId);\n\n        List<StatefulSet> statefulSets = kubernetesClient.apps().statefulSets().withLabels(selector).list().getItems();\n\n        assertThat(statefulSets).hasSize(1);\n\n        StatefulSet statefulSet = statefulSets.get(0);\n\n        StatefulSetSpec statefulSetSpec = statefulSet.getSpec();\n\n        List<Container> statefulSetInitContainers = statefulSetSpec.getTemplate().getSpec().getInitContainers();\n        assertThat(statefulSetInitContainers).hasSize(1);\n        Container statefulSetInitContainer = statefulSetInitContainers.get(0);\n        assertThat(statefulSetInitContainer.getImage()).isEqualTo(imageName);\n\n        log.info(\"Undeploying {}...\", deploymentId);\n        timeout = undeploymentTimeout();\n        appDeployer.undeploy(deploymentId);\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.unknown));\n    }\n\n    @Test\n    public void createStatefulSetInitContainerImageNameGlobalOverride() throws Exception {\n        Map<String, String> props = new HashMap<>();\n        props.put(AppDeployer.COUNT_PROPERTY_KEY, \"3\");\n        props.put(AppDeployer.INDEXED_PROPERTY_KEY, \"true\");\n\n        AppDefinition definition = new AppDefinition(randomName(), null);\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, testApplication(), props);\n\n        String imageName = testApplication().getURI().getSchemeSpecificPart();\n\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n        kubernetesDeployerProperties.setStatefulSetInitContainerImageName(imageName);\n\n        KubernetesAppDeployer deployer = kubernetesAppDeployer(kubernetesDeployerProperties);\n\n        log.info(\"Deploying {}...\", appDeploymentRequest.getDefinition().getName());\n        String deploymentId = deployer.deploy(appDeploymentRequest);\n\n        Timeout timeout = deploymentTimeout();\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.deployed));\n\n        Map<String, String> selector = Collections.singletonMap(AbstractKubernetesDeployer.SPRING_APP_KEY, deploymentId);\n\n        List<StatefulSet> statefulSets = kubernetesClient.apps().statefulSets().withLabels(selector).list().getItems();\n\n        assertThat(statefulSets).hasSize(1);\n\n        StatefulSet statefulSet = statefulSets.get(0);\n\n        StatefulSetSpec statefulSetSpec = statefulSet.getSpec();\n\n        List<Container> statefulSetInitContainers = statefulSetSpec.getTemplate().getSpec().getInitContainers();\n        assertThat(statefulSetInitContainers).hasSize(1);\n        Container statefulSetInitContainer = statefulSetInitContainers.get(0);\n        assertThat(statefulSetInitContainer.getImage()).isEqualTo(imageName);\n\n        log.info(\"Undeploying {}...\", deploymentId);\n        timeout = undeploymentTimeout();\n        appDeployer.undeploy(deploymentId);\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.unknown));\n    }\n\n    @Test\n    public void createStatefulSetWithOverridingRequest() throws Exception {\n        Map<String, String> props = new HashMap<>();\n        props.put(AppDeployer.COUNT_PROPERTY_KEY, \"3\");\n        props.put(AppDeployer.INDEXED_PROPERTY_KEY, \"true\");\n        props.put(\"spring.cloud.deployer.kubernetes.statefulSet.volumeClaimTemplate.name\", \"mystorage\");\n        props.put(\"spring.cloud.deployer.kubernetes.statefulSet.volumeClaimTemplate.storage\", \"1g\");\n\n        AppDefinition definition = new AppDefinition(randomName(), null);\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, testApplication(), props);\n\n        KubernetesAppDeployer deployer = kubernetesAppDeployer();\n\n        log.info(\"Deploying {}...\", appDeploymentRequest.getDefinition().getName());\n        String deploymentId = deployer.deploy(appDeploymentRequest);\n        Map<String, String> idMap = deployer.createIdMap(deploymentId, appDeploymentRequest);\n\n        Timeout timeout = deploymentTimeout();\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.deployed));\n\n        Map<String, String> selector = Collections.singletonMap(AbstractKubernetesDeployer.SPRING_APP_KEY, deploymentId);\n\n        StatefulSet statefulSet = kubernetesClient.apps().statefulSets().withLabels(selector).list().getItems().get(0);\n        StatefulSetSpec statefulSetSpec = statefulSet.getSpec();\n\n        assertThat(statefulSetSpec.getPodManagementPolicy()).isEqualTo(\"Parallel\");\n        assertThat(statefulSetSpec.getReplicas()).isEqualTo(3);\n        assertThat(statefulSetSpec.getServiceName()).isEqualTo(deploymentId);\n        assertThat(statefulSet.getMetadata().getName()).isEqualTo(deploymentId);\n\n        assertThat(statefulSetSpec.getSelector().getMatchLabels())\n                .containsAllEntriesOf(deployer.createIdMap(deploymentId, appDeploymentRequest));\n        assertThat(statefulSetSpec.getSelector().getMatchLabels())\n                .contains(entry(KubernetesAppDeployer.SPRING_MARKER_KEY, KubernetesAppDeployer.SPRING_MARKER_VALUE));\n\n        assertThat(statefulSetSpec.getTemplate().getMetadata().getLabels()).containsAllEntriesOf(idMap);\n        assertThat(statefulSetSpec.getTemplate().getMetadata().getLabels())\n                .contains(entry(KubernetesAppDeployer.SPRING_MARKER_KEY, KubernetesAppDeployer.SPRING_MARKER_VALUE));\n\n        Container container = statefulSetSpec.getTemplate().getSpec().getContainers().get(0);\n\n        assertThat(container.getName()).isEqualTo(deploymentId);\n        assertThat(container.getPorts().get(0).getContainerPort()).isEqualTo(8080);\n        assertThat(container.getImage()).isEqualTo(testApplication().getURI().getSchemeSpecificPart());\n\n        PersistentVolumeClaim pvc = statefulSetSpec.getVolumeClaimTemplates().get(0);\n        assertThat(pvc.getMetadata().getName()).isEqualTo(\"mystorage\");\n\n        PersistentVolumeClaimSpec pvcSpec = pvc.getSpec();\n        assertThat(pvcSpec.getAccessModes()).containsOnly(\"ReadWriteOnce\");\n\n        assertThat(pvcSpec.getResources().getLimits().get(\"storage\").getAmount()).isEqualTo(\"1\");\n        assertThat(pvcSpec.getResources().getRequests().get(\"storage\").getAmount()).isEqualTo(\"1\");\n\n        assertThat(pvcSpec.getResources().getLimits().get(\"storage\").getFormat()).isEqualTo(\"Gi\");\n        assertThat(pvcSpec.getResources().getRequests().get(\"storage\").getFormat()).isEqualTo(\"Gi\");\n\n        log.info(\"Undeploying {}...\", deploymentId);\n        timeout = undeploymentTimeout();\n        appDeployer.undeploy(deploymentId);\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.unknown));\n    }\n\n    @Test\n    public void createStatefulSetWithPVCDefaultName() throws Exception {\n        Map<String, String> props = new HashMap<>();\n        props.put(AppDeployer.COUNT_PROPERTY_KEY, \"3\");\n        props.put(AppDeployer.INDEXED_PROPERTY_KEY, \"true\");\n        AppDefinition definition = new AppDefinition(randomName(), null);\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, testApplication(), props);\n\n        KubernetesAppDeployer deployer = kubernetesAppDeployer();\n\n        log.info(\"Deploying {}...\", appDeploymentRequest.getDefinition().getName());\n        String deploymentId = deployer.deploy(appDeploymentRequest);\n        Timeout timeout = deploymentTimeout();\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.deployed));\n\n        Map<String, String> selector = Collections.singletonMap(AbstractKubernetesDeployer.SPRING_APP_KEY, deploymentId);\n\n        StatefulSet statefulSet = kubernetesClient.apps().statefulSets().withLabels(selector).list().getItems().get(0);\n        StatefulSetSpec statefulSetSpec = statefulSet.getSpec();\n        Container container = statefulSetSpec.getTemplate().getSpec().getContainers().get(0);\n\n        assertThat(container.getName()).isEqualTo(deploymentId);\n\n        PersistentVolumeClaim pvc = statefulSetSpec.getVolumeClaimTemplates().get(0);\n        assertThat(pvc.getMetadata().getName()).isEqualTo(deploymentId);\n\n        log.info(\"Undeploying {}...\", deploymentId);\n        timeout = undeploymentTimeout();\n        appDeployer.undeploy(deploymentId);\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.unknown));\n    }\n\n    @Test\n    public void testStatefulSetPodAnnotations() {\n        log.info(\"Testing {}...\", \"StatefulSetPodAnnotations\");\n\n        KubernetesAppDeployer appDeployer = kubernetesAppDeployer();\n\n        AppDefinition definition = new AppDefinition(randomName(), null);\n        Resource resource = testApplication();\n\n        Map<String, String> props = new HashMap<>();\n        props.put(AppDeployer.COUNT_PROPERTY_KEY, \"3\");\n        props.put(AppDeployer.INDEXED_PROPERTY_KEY, \"true\");\n        props.put(\"spring.cloud.deployer.kubernetes.podAnnotations\",\n                \"iam.amazonaws.com/role:role-arn,foo:bar\");\n        AppDeploymentRequest request = new AppDeploymentRequest(definition, resource, props);\n\n        log.info(\"Deploying {}...\", request.getDefinition().getName());\n        Timeout timeout = deploymentTimeout();\n        String deploymentId = appDeployer.deploy(request);\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.deployed));\n\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> assertThat(appDeployer().status(deploymentId).getInstances()).hasSize(3));\n\n        // Ensure that a StatefulSet is deployed\n        Map<String, String> selector = Collections.singletonMap(AbstractKubernetesDeployer.SPRING_APP_KEY, deploymentId);\n        List<StatefulSet> statefulSets = kubernetesClient.apps().statefulSets().withLabels(selector).list().getItems();\n        assertThat(statefulSets).isNotNull();\n        assertThat(statefulSets).hasSize(1);\n        StatefulSet statefulSet = statefulSets.get(0);\n        StatefulSetSpec statefulSetSpec = statefulSet.getSpec();\n\n        Map<String, String> annotations = statefulSetSpec.getTemplate().getMetadata().getAnnotations();\n        assertThat(annotations.get(\"iam.amazonaws.com/role\")).isEqualTo(\"role-arn\");\n        assertThat(annotations.get(\"foo\")).isEqualTo(\"bar\");\n        appDeployer.undeploy(deploymentId);\n    }\n\n    @Test\n    public void testDeploymentLabels() {\n        Map<String, String> props = Collections.singletonMap(\"spring.cloud.deployer.kubernetes.deploymentLabels\",\n                \"label1:value1,label2:value2\");\n\n        AppDefinition definition = new AppDefinition(randomName(), null);\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, testApplication(), props);\n\n        KubernetesAppDeployer deployer = kubernetesAppDeployer();\n\n        log.info(\"Deploying {}...\", appDeploymentRequest.getDefinition().getName());\n\n        String deploymentId = deployer.deploy(appDeploymentRequest);\n\n        Timeout timeout = deploymentTimeout();\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.deployed));\n\n        Map<String, String> selector = Collections.singletonMap(AbstractKubernetesDeployer.SPRING_APP_KEY, deploymentId);\n\n        List<Deployment> deployments = kubernetesClient.apps().deployments().withLabels(selector).list().getItems();\n\n        Map<String, String> specLabels = deployments.get(0).getSpec().getTemplate().getMetadata().getLabels();\n\n        assertThat(specLabels.containsKey(\"label1\")).as(\"Label 'label1' not found in deployment spec\").isTrue();\n        assertThat(specLabels.get(\"label1\")).as(\"Unexpected value for label1\").isEqualTo(\"value1\");\n        assertThat(specLabels).as(\"Label 'label2' not found in deployment spec\").containsKey(\"label2\");\n        assertThat(specLabels.get(\"label2\")).as(\"Unexpected value for label1\").isEqualTo(\"value2\");\n\n        log.info(\"Undeploying {}...\", deploymentId);\n        timeout = undeploymentTimeout();\n        appDeployer.undeploy(deploymentId);\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.unknown));\n    }\n\n    @Test\n    public void testDeploymentLabelsStatefulSet() {\n        log.info(\"Testing {}...\", \"DeploymentLabelsForStatefulSet\");\n        Map<String, String> props = new HashMap<>();\n        props.put(AppDeployer.COUNT_PROPERTY_KEY, \"2\");\n        props.put(AppDeployer.INDEXED_PROPERTY_KEY, \"true\");\n        props.put(\"spring.cloud.deployer.kubernetes.deploymentLabels\",\n                \"stateful-label1:stateful-value1,stateful-label2:stateful-value2\");\n        AppDefinition definition = new AppDefinition(randomName(), null);\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, testApplication(), props);\n\n        KubernetesAppDeployer deployer = kubernetesAppDeployer();\n\n        log.info(\"Deploying {}...\", appDeploymentRequest.getDefinition().getName());\n\n        String deploymentId = deployer.deploy(appDeploymentRequest);\n\n        Timeout timeout = deploymentTimeout();\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.deployed));\n        Map<String, String> idMap = deployer.createIdMap(deploymentId, appDeploymentRequest);\n        Map<String, String> selector = Collections.singletonMap(AbstractKubernetesDeployer.SPRING_APP_KEY, deploymentId);\n\n        StatefulSet statefulSet = kubernetesClient.apps().statefulSets().withLabels(selector).list().getItems().get(0);\n        StatefulSetSpec statefulSetSpec = statefulSet.getSpec();\n        assertThat(statefulSetSpec.getReplicas()).isEqualTo(2);\n        assertThat(statefulSetSpec.getTemplate().getMetadata().getLabels()).containsAllEntriesOf(idMap);\n\n        //verify stateful set match labels\n        Map<String, String> setLabels = statefulSet.getMetadata().getLabels();\n        assertThat(setLabels).contains(entry(\"stateful-label1\", \"stateful-value1\"), entry(\"stateful-label2\", \"stateful-value2\"));\n\n        //verify pod template labels\n        Map<String, String> specLabels = statefulSetSpec.getTemplate().getMetadata().getLabels();\n        assertThat(specLabels).contains(entry(\"stateful-label1\", \"stateful-value1\"), entry(\"stateful-label2\", \"stateful-value2\"));\n\n        //verify that labels got replicated to one of the deployments\n        List<Pod> pods = kubernetesClient.pods().withLabels(selector).list().getItems();\n        Map<String, String> podLabels = pods.get(0).getMetadata().getLabels();\n\n        assertThat(podLabels).contains(entry(\"stateful-label1\", \"stateful-value1\"), entry(\"stateful-label2\", \"stateful-value2\"));\n\n        log.info(\"Undeploying {}...\", deploymentId);\n        timeout = undeploymentTimeout();\n        appDeployer.undeploy(deploymentId);\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.unknown));\n    }\n\n    @Test\n    public void testCleanupOnDeployFailure() throws InterruptedException {\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, testApplication(), null);\n\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n\n        // simulate a pod going into an un-schedulable state\n        KubernetesDeployerProperties.LimitsResources resources = new KubernetesDeployerProperties.LimitsResources();\n        resources.setCpu(\"9000000\");\n\n        kubernetesDeployerProperties.setLimits(resources);\n\n        KubernetesAppDeployer deployer = kubernetesAppDeployer(kubernetesDeployerProperties);\n\n        log.info(\"Deploying {}...\", appDeploymentRequest.getDefinition().getName());\n\n        String deploymentId = deployer.deploy(appDeploymentRequest);\n\n        Timeout timeout = deploymentTimeout();\n\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.unknown));\n\n        // attempt to undeploy the failed deployment\n        log.info(\"Undeploying {}...\", deploymentId);\n\n        try {\n            appDeployer.undeploy(deploymentId);\n        } catch (Exception e) {\n            log.info(\"Got expected not not deployed exception on undeployment: \" + e.getMessage());\n        }\n\n        deployer = kubernetesAppDeployer();\n\n        log.info(\"Deploying {}... again\", deploymentId);\n\n        // ensure a previous failed deployment with the same name was cleaned up and can be deployed again\n        deployer.deploy(appDeploymentRequest);\n\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.deployed));\n\n        appDeployer.undeploy(deploymentId);\n\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.unknown));\n    }\n\n    @Test\n    public void testMultipleContainersInPod() {\n        log.info(\"Testing {}...\", \"MultipleContainersInPod\");\n\n        KubernetesAppDeployer kubernetesAppDeployer = Mockito.spy(kubernetesAppDeployer());\n\t\tAppDefinition definition = new AppDefinition(randomName(), Collections.singletonMap(\"server.port\", \"9090\"));\n        Resource resource = testApplication();\n\n        AppDeploymentRequest request = new AppDeploymentRequest(definition, resource);\n\n        doAnswer((Answer<PodSpec>) invocationOnMock -> {\n            PodSpec podSpec = (PodSpec) invocationOnMock.callRealMethod();\n\n            Container container = new ContainerBuilder().withName(\"asecondcontainer\")\n                    .withImage(resource.getURI().getSchemeSpecificPart()).build();\n\n            podSpec.getContainers().add(container);\n\n            return podSpec;\n        }).when(kubernetesAppDeployer).createPodSpec(Mockito.any(AppDeploymentRequest.class));\n\n        log.info(\"Deploying {}...\", request.getDefinition().getName());\n\n        String deploymentId = kubernetesAppDeployer.deploy(request);\n        Timeout timeout = deploymentTimeout();\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.deployed));\n\n        log.info(\"Undeploying {}...\", deploymentId);\n        timeout = undeploymentTimeout();\n        kubernetesAppDeployer.undeploy(deploymentId);\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.unknown));\n    }\n\n    @Test\n    public void testDefaultServicePort() {\n        log.info(\"Testing {}...\", \"DefaultServicePort\");\n        KubernetesAppDeployer kubernetesAppDeployer = kubernetesAppDeployer();\n\n        AppDefinition definition = new AppDefinition(randomName(), null);\n        Resource resource = testApplication();\n        AppDeploymentRequest request = new AppDeploymentRequest(definition, resource);\n\n        log.info(\"Deploying {}...\", request.getDefinition().getName());\n        String deploymentId = kubernetesAppDeployer.deploy(request);\n        Timeout timeout = deploymentTimeout();\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.deployed));\n\n        List<ServicePort> servicePorts = kubernetesClient.services().withName(request.getDefinition().getName()).get()\n                .getSpec().getPorts();\n\n        assertThat(servicePorts).hasSize(1);\n        assertThat(servicePorts.get(0).getPort()).isEqualTo(8080);\n        assertThat(servicePorts.get(0).getName()).isEqualTo(\"port-8080\");\n\n        log.info(\"Undeploying {}...\", deploymentId);\n        timeout = undeploymentTimeout();\n        kubernetesAppDeployer.undeploy(deploymentId);\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.unknown));\n    }\n\n    @Test\n    public void testDefaultServicePortOverride() {\n        log.info(\"Testing {}...\", \"DefaultServicePortOverride\");\n        KubernetesAppDeployer kubernetesAppDeployer = kubernetesAppDeployer();\n\n        AppDefinition definition = new AppDefinition(randomName(), Collections.singletonMap(\"server.port\", \"9090\"));\n        Resource resource = testApplication();\n        AppDeploymentRequest request = new AppDeploymentRequest(definition, resource);\n\n        log.info(\"Deploying {}...\", request.getDefinition().getName());\n        String deploymentId = kubernetesAppDeployer.deploy(request);\n        Timeout timeout = deploymentTimeout();\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.deployed));\n\n        List<ServicePort> servicePorts = kubernetesClient.services().withName(request.getDefinition().getName()).get()\n                .getSpec().getPorts();\n\n        assertThat(servicePorts).hasSize(1);\n        assertThat(servicePorts.get(0).getPort()).isEqualTo(9090);\n        assertThat(servicePorts.get(0).getName()).isEqualTo(\"port-9090\");\n\n        log.info(\"Undeploying {}...\", deploymentId);\n        timeout = undeploymentTimeout();\n        kubernetesAppDeployer.undeploy(deploymentId);\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.unknown));\n    }\n\n    @Test\n    public void testServiceWithMultiplePorts() {\n        log.info(\"Testing {}...\", \"ServiceWithMultiplePorts\");\n        KubernetesAppDeployer kubernetesAppDeployer = kubernetesAppDeployer();\n\n        AppDefinition definition = new AppDefinition(randomName(), null);\n        Resource resource = testApplication();\n        AppDeploymentRequest request = new AppDeploymentRequest(definition, resource,\n                Collections.singletonMap(\"spring.cloud.deployer.kubernetes.servicePorts\", \"8080,9090\"));\n\n        log.info(\"Deploying {}...\", request.getDefinition().getName());\n        String deploymentId = kubernetesAppDeployer.deploy(request);\n        Timeout timeout = deploymentTimeout();\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.deployed));\n\n        List<ServicePort> servicePorts = kubernetesClient.services().withName(request.getDefinition().getName()).get()\n                .getSpec().getPorts();\n\n        assertThat(servicePorts).hasSize(2);\n        assertThat(servicePorts.stream().anyMatch(o -> o.getPort().equals(8080))).isTrue();\n        assertThat(servicePorts.stream().anyMatch(o -> o.getName().equals(\"port-8080\"))).isTrue();\n        assertThat(servicePorts.stream().anyMatch(o -> o.getPort().equals(9090))).isTrue();\n        assertThat(servicePorts.stream().anyMatch(o -> o.getName().equals(\"port-9090\"))).isTrue();\n\n        log.info(\"Undeploying {}...\", deploymentId);\n        timeout = undeploymentTimeout();\n        kubernetesAppDeployer.undeploy(deploymentId);\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.unknown));\n    }\n\n    @Test\n    public void testCreateInitContainer() {\n        log.info(\"Testing {}...\", \"CreateInitContainer\");\n        KubernetesAppDeployer kubernetesAppDeployer = kubernetesAppDeployer();\n\n        Map<String, String> props = Collections.singletonMap(\"spring.cloud.deployer.kubernetes.initContainer\",\n                \"{containerName: 'test', imageName: 'busybox:latest', commands: ['sh', '-c', 'echo hello']}\");\n\n        AppDefinition definition = new AppDefinition(randomName(), null);\n        AppDeploymentRequest request = new AppDeploymentRequest(definition, testApplication(), props);\n\n        log.info(\"Deploying {}...\", request.getDefinition().getName());\n        String deploymentId = kubernetesAppDeployer.deploy(request);\n        Timeout timeout = deploymentTimeout();\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.deployed));\n\n        Deployment deployment = kubernetesClient.apps().deployments().withName(request.getDefinition().getName()).get();\n        List<Container> initContainers = deployment.getSpec().getTemplate().getSpec().getInitContainers();\n\n        Optional<Container> initContainer = initContainers.stream().filter(i -> i.getName().equals(\"test\")).findFirst();\n        assertThat(initContainer.isPresent()).as(\"Init container not found\").isTrue();\n\n        Container testInitContainer = initContainer.get();\n\n        assertThat(testInitContainer.getName()).as(\"Unexpected init container name\").isEqualTo(\"test\");\n        assertThat(testInitContainer.getImage()).as(\"Unexpected init container image\").isEqualTo(\"busybox:latest\");\n\n        List<String> commands = testInitContainer.getCommand();\n\n        assertThat(commands).contains(\"sh\", \"-c\", \"echo hello\");\n\n        log.info(\"Undeploying {}...\", deploymentId);\n        timeout = undeploymentTimeout();\n        kubernetesAppDeployer.undeploy(deploymentId);\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.unknown));\n    }\n\n    @Test\n    public void testCreateInitContainerWithEnvVariables() {\n        log.info(\"Testing {}...\", \"CreateInitContainer\");\n        KubernetesAppDeployer kubernetesAppDeployer = kubernetesAppDeployer();\n\n        Map<String, String> props = Collections.singletonMap(\"spring.cloud.deployer.kubernetes.initContainer\",\n                \"{containerName: 'test', imageName: 'busybox:latest', commands: ['sh', '-c', 'echo hello'], environmentVariables: ['KEY1=VAL1', 'KEY2=VAL2']}\");\n\n        AppDefinition definition = new AppDefinition(randomName(), null);\n        AppDeploymentRequest request = new AppDeploymentRequest(definition, testApplication(), props);\n\n        log.info(\"Deploying {}...\", request.getDefinition().getName());\n        String deploymentId = kubernetesAppDeployer.deploy(request);\n        Timeout timeout = deploymentTimeout();\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.deployed));\n\n        Deployment deployment = kubernetesClient.apps().deployments().withName(request.getDefinition().getName()).get();\n        List<Container> initContainers = deployment.getSpec().getTemplate().getSpec().getInitContainers();\n\n        Optional<Container> initContainer = initContainers.stream().filter(i -> i.getName().equals(\"test\")).findFirst();\n        assertThat(initContainer.isPresent()).as(\"Init container not found\").isTrue();\n\n        Container testInitContainer = initContainer.get();\n\n        List<EnvVar> containerEnvs = testInitContainer.getEnv();\n\n        assertThat(containerEnvs).hasSize(2);\n        assertThat(containerEnvs.stream().map(EnvVar::getName).collect(Collectors.toList())).contains(\"KEY1\", \"KEY2\");\n        assertThat(containerEnvs.stream().map(EnvVar::getValue).collect(Collectors.toList())).contains(\"VAL1\", \"VAL2\");\n\n        log.info(\"Undeploying {}...\", deploymentId);\n        timeout = undeploymentTimeout();\n        kubernetesAppDeployer.undeploy(deploymentId);\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.unknown));\n    }\n\n    @Test\n    public void initContainerFromGlobalProps() {\n        // Set up a global initContainer (it should be chosen)\n        KubernetesDeployerProperties.InitContainer globalInitContainerProps = new KubernetesDeployerProperties.InitContainer();\n        globalInitContainerProps.setName(\"test-global\");\n        globalInitContainerProps.setImage(\"busybox:latest\");\n        globalInitContainerProps.setCommand(Arrays.asList(\"sh\", \"-c\", \"echo hello-global\"));\n\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n        kubernetesDeployerProperties.setInitContainer(globalInitContainerProps);\n        KubernetesAppDeployer kubernetesAppDeployer = kubernetesAppDeployer(kubernetesDeployerProperties);\n        AppDefinition definition = new AppDefinition(randomName(), null);\n        AppDeploymentRequest request = new AppDeploymentRequest(definition, testApplication(), Collections.emptyMap());\n\n        String deploymentId = deploy(kubernetesAppDeployer, request);\n\n        Deployment deployment = kubernetesClient.apps().deployments().withName(request.getDefinition().getName()).get();\n        List<Container> initContainers = deployment.getSpec().getTemplate().getSpec().getInitContainers();\n        Optional<Container> initContainer = initContainers.stream().filter(i -> i.getName().equals(\"test-global\")).findFirst();\n        assertThat(initContainer).isPresent();\n        Container testInitContainer = initContainer.get();\n        assertThat(testInitContainer.getName()).isEqualTo(\"test-global\");\n        assertThat(testInitContainer.getImage()).isEqualTo(\"busybox:latest\");\n        assertThat(testInitContainer.getCommand()).containsExactly(\"sh\", \"-c\", \"echo hello-global\");\n\n        undeploy(kubernetesAppDeployer, deploymentId);\n    }\n\n    @Test\n    public void initContainerFromDeployerProps() {\n        // Set up a global mock initContainer (it should not be chosen)\n        KubernetesDeployerProperties.InitContainer globalInitContainerProps = mock(KubernetesDeployerProperties.InitContainer.class);\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n        kubernetesDeployerProperties.setInitContainer(globalInitContainerProps);\n        KubernetesAppDeployer kubernetesAppDeployer = kubernetesAppDeployer(kubernetesDeployerProperties);\n\n        // Set up a deployer props initContainer (it should be chosen)\n        Map<String, String> deployerProps = Collections.singletonMap(\"spring.cloud.deployer.kubernetes.initContainer\",\n                \"{containerName: 'test', imageName: 'busybox:latest', imagePullPolicy: 'IfNotPresent', commands: ['sh', '-c', 'echo hello']\" +\n                        \", environmentVariables: ['KEY1=VAL1', 'KEY2=VAL2']}\");\n        AppDefinition definition = new AppDefinition(randomName(), null);\n        AppDeploymentRequest request = new AppDeploymentRequest(definition, testApplication(), deployerProps);\n\n        String deploymentId = deploy(kubernetesAppDeployer, request);\n\n        Deployment deployment = kubernetesClient.apps().deployments().withName(request.getDefinition().getName()).get();\n        List<Container> initContainers = deployment.getSpec().getTemplate().getSpec().getInitContainers();\n        Optional<Container> initContainer = initContainers.stream().filter(i -> i.getName().equals(\"test\")).findFirst();\n        assertThat(initContainer).isPresent();\n        Container testInitContainer = initContainer.get();\n        assertThat(testInitContainer.getName()).isEqualTo(\"test\");\n        assertThat(testInitContainer.getImage()).isEqualTo(\"busybox:latest\");\n        assertThat(testInitContainer.getEnv()).extracting(\"name\", \"value\")\n                .containsExactlyInAnyOrder(tuple(\"KEY1\", \"VAL1\"), tuple(\"KEY2\", \"VAL2\"));\n        assertThat(testInitContainer.getCommand()).containsExactly(\"sh\", \"-c\", \"echo hello\");\n\n        undeploy(kubernetesAppDeployer, deploymentId);\n    }\n\n    @Test\n    public void initContainerPartialFromDeployerProps() {\n        // Set up a global mock initContainer (it should not be chosen)\n        KubernetesDeployerProperties.InitContainer globalInitContainerProps = mock(KubernetesDeployerProperties.InitContainer.class);\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n        kubernetesDeployerProperties.setInitContainer(globalInitContainerProps);\n        KubernetesAppDeployer kubernetesAppDeployer = kubernetesAppDeployer(kubernetesDeployerProperties);\n\n        // Set up a deployer props initContainer partial (they should be used)\n        Map<String, String> deployerProps = new HashMap<>();\n        deployerProps.put(\"spring.cloud.deployer.kubernetes.initContainer.containerName\", \"test\");\n        deployerProps.put(\"spring.cloud.deployer.kubernetes.initContainer.imageName\", \"busybox:latest\");\n        deployerProps.put(\"spring.cloud.deployer.kubernetes.initContainer.commands\", \"sh,-c,echo hello\");\n        AppDefinition definition = new AppDefinition(randomName(), null);\n        AppDeploymentRequest request = new AppDeploymentRequest(definition, testApplication(), deployerProps);\n\n        String deploymentId = deploy(kubernetesAppDeployer, request);\n\n        Deployment deployment = kubernetesClient.apps().deployments().withName(request.getDefinition().getName()).get();\n        List<Container> initContainers = deployment.getSpec().getTemplate().getSpec().getInitContainers();\n        Optional<Container> initContainer = initContainers.stream().filter(i -> i.getName().equals(\"test\")).findFirst();\n        assertThat(initContainer).isPresent();\n        Container testInitContainer = initContainer.get();\n        assertThat(testInitContainer.getName()).isEqualTo(\"test\");\n        assertThat(testInitContainer.getImage()).isEqualTo(\"busybox:latest\");\n        assertThat(testInitContainer.getCommand()).containsExactly(\"sh\", \"-c\", \"echo hello\");\n\n        undeploy(kubernetesAppDeployer, deploymentId);\n    }\n\n    @Test\n    public void initContainerFromDeployerPropsWithVolumeMounts() {\n        log.info(\"Testing {}...\", \"CreateInitContainerWithVolumeMounts\");\n        KubernetesAppDeployer kubernetesAppDeployer = kubernetesAppDeployer();\n\n        Map<String, String> props = Stream.of(new String[][]{\n                {\n                        \"spring.cloud.deployer.kubernetes.volumes\",\n                        \"[{name: 'test-volume', emptyDir: {}}]\",\n                },\n                {\n                        \"spring.cloud.deployer.kubernetes.volumeMounts\",\n                        \"[{name: 'test-volume', mountPath: '/tmp'}]\",\n                },\n                {\n                        \"spring.cloud.deployer.kubernetes.initContainer\",\n                        \"{containerName: 'test', imageName: 'busybox:latest', commands: ['sh', '-c', 'echo hello'], \" +\n                                \"volumeMounts: [{name: 'test-volume', mountPath: '/tmp', readOnly: true}]}\",\n                }\n        }).collect(Collectors.toMap(data -> data[0], data -> data[1]));\n\n        AppDefinition definition = new AppDefinition(randomName(), null);\n        AppDeploymentRequest request = new AppDeploymentRequest(definition, testApplication(), props);\n\n        String deploymentId = deploy(kubernetesAppDeployer, request);\n\n        Deployment deployment = kubernetesClient.apps().deployments().withName(request.getDefinition().getName()).get();\n        List<Container> initContainers = deployment.getSpec().getTemplate().getSpec().getInitContainers();\n\n        Optional<Container> initContainer = initContainers.stream().filter(i -> i.getName().equals(\"test\")).findFirst();\n        assertThat(initContainer.isPresent()).as(\"Init container not found\").isTrue();\n\n        Container testInitContainer = initContainer.get();\n\n        assertThat(testInitContainer.getName()).as(\"Unexpected init container name\").isEqualTo(\"test\");\n        assertThat(testInitContainer.getImage()).as(\"Unexpected init container image\").isEqualTo(\"busybox:latest\");\n\n        List<String> commands = testInitContainer.getCommand();\n\n        assertThat(commands != null && !commands.isEmpty()).as(\"Init container commands missing\").isTrue();\n        assertThat(commands).hasSize(3);\n        assertThat(commands).contains(\"sh\", \"-c\", \"echo hello\");\n\n        List<VolumeMount> volumeMounts = testInitContainer.getVolumeMounts();\n        assertThat(volumeMounts != null && !volumeMounts.isEmpty()).as(\"Init container volumeMounts missing\").isTrue();\n        assertThat(volumeMounts).hasSize(1);\n\n        VolumeMount vm = volumeMounts.get(0);\n        assertThat(vm.getName()).as(\"Unexpected init container volume mount name\").isEqualTo(\"test-volume\");\n        assertThat(vm.getMountPath()).as(\"Unexpected init container volume mount path\").isEqualTo(\"/tmp\");\n        assertThat(vm.getReadOnly()).as(\"Expected read only volume mount\").isTrue();\n\n        undeploy(kubernetesAppDeployer, deploymentId);\n    }\n\n    private String deploy(KubernetesAppDeployer kubernetesAppDeployer, AppDeploymentRequest deployRequest) {\n        log.info(\"Deploying {}...\", deployRequest.getDefinition().getName());\n        String deploymentId = kubernetesAppDeployer.deploy(deployRequest);\n        Timeout timeout = deploymentTimeout();\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis((long) timeout.maxAttempts * (long)  timeout.pause))\n                .untilAsserted(() -> assertThat(appDeployer().status(deploymentId).getState())\n                        .isEqualTo(DeploymentState.deployed));\n        return deploymentId;\n    }\n\n    private void undeploy(KubernetesAppDeployer kubernetesAppDeployer, String deploymentId) {\n        log.info(\"Undeploying {}...\", deploymentId);\n        Timeout timeout = undeploymentTimeout();\n        kubernetesAppDeployer.undeploy(deploymentId);\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis((long) timeout.maxAttempts * (long) timeout.pause))\n                .untilAsserted(() -> assertThat(appDeployer().status(deploymentId).getState())\n                        .isEqualTo(DeploymentState.unknown));\n    }\n\n\n    @Test\n    public void testCreateAdditionalContainers() {\n        log.info(\"Testing {}...\", \"CreateAdditionalContainers\");\n        KubernetesAppDeployer kubernetesAppDeployer = kubernetesAppDeployer();\n\n        Map<String, String> props = Stream.of(new String[][]{\n                {\n                        \"spring.cloud.deployer.kubernetes.volumes\",\n                        \"[{name: 'test-volume', emptyDir: {}}]\",\n                },\n                {\n                        \"spring.cloud.deployer.kubernetes.volumeMounts\",\n                        \"[{name: 'test-volume', mountPath: '/tmp'}]\",\n                },\n                {\n                        \"spring.cloud.deployer.kubernetes.additional-containers\",\n                        \"[{name: 'c1', image: 'busybox:latest', command: ['sh', '-c', 'echo hello1'], volumeMounts: [{name: 'test-volume', mountPath: '/tmp', readOnly: true}]},\"\n                                + \"{name: 'c2', image: 'busybox:1.26.1', command: ['sh', '-c', 'echo hello2']}]\"\n                }}).collect(Collectors.toMap(data -> data[0], data -> data[1]));\n\n        AppDefinition definition = new AppDefinition(randomName(), null);\n        AppDeploymentRequest request = new AppDeploymentRequest(definition, testApplication(), props);\n\n        log.info(\"Deploying {}...\", request.getDefinition().getName());\n        String deploymentId = kubernetesAppDeployer.deploy(request);\n        Timeout timeout = deploymentTimeout();\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.deployed));\n\n        Deployment deployment = kubernetesClient.apps().deployments().withName(request.getDefinition().getName()).get();\n        List<Container> containers = deployment.getSpec().getTemplate().getSpec().getContainers();\n\n        assertThat(containers).hasSize(3);\n\n        Optional<Container> additionalContainer1 = containers.stream().filter(i -> i.getName().equals(\"c1\")).findFirst();\n        assertThat(additionalContainer1.isPresent()).isTrue();\n\n        Container testAdditionalContainer1 = additionalContainer1.get();\n\n        assertThat(testAdditionalContainer1.getName()).as(\"Unexpected additional container name\").isEqualTo(\"c1\");\n        assertThat(testAdditionalContainer1.getImage()).as(\"Unexpected additional container image\").isEqualTo(\"busybox:latest\");\n\n        List<String> commands = testAdditionalContainer1.getCommand();\n\n        assertThat(commands).contains(\"sh\", \"-c\", \"echo hello1\");\n\n        List<VolumeMount> volumeMounts = testAdditionalContainer1.getVolumeMounts();\n\n        assertThat(volumeMounts).hasSize(1);\n        assertThat(volumeMounts.get(0).getName()).isEqualTo(\"test-volume\");\n        assertThat(volumeMounts.get(0).getMountPath()).isEqualTo(\"/tmp\");\n        assertThat(volumeMounts.get(0).getReadOnly()).isTrue();\n\n        Optional<Container> additionalContainer2 = containers.stream().filter(i -> i.getName().equals(\"c2\")).findFirst();\n        assertThat(additionalContainer2.isPresent()).as(\"Additional container c2 not found\").isTrue();\n\n        Container testAdditionalContainer2 = additionalContainer2.get();\n\n        assertThat(testAdditionalContainer2.getName()).as(\"Unexpected additional container name\").isEqualTo(\"c2\");\n        assertThat(testAdditionalContainer2.getImage()).as(\"Unexpected additional container image\").isEqualTo(\"busybox:1.26.1\");\n\n        List<String> container2Commands = testAdditionalContainer2.getCommand();\n\n        assertThat(container2Commands).contains(\"sh\", \"-c\", \"echo hello2\");\n\n        log.info(\"Undeploying {}...\", deploymentId);\n        timeout = undeploymentTimeout();\n        kubernetesAppDeployer.undeploy(deploymentId);\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.unknown));\n    }\n\n    @Test\n    public void testCreateAdditionalContainersOverride() {\n        log.info(\"Testing {}...\", \"CreateAdditionalContainersOverride\");\n        KubernetesDeployerProperties.Container container1 = new KubernetesDeployerProperties.Container();\n        container1.setName(\"c1\");\n        container1.setImage(\"busybox:1.31.0\");\n        container1.setCommand(Arrays.asList(\"sh\", \"-c\", \"echo hello-from-original-properties\"));\n        KubernetesDeployerProperties.Container container2 = new KubernetesDeployerProperties.Container();\n        container2.setName(\"container2\");\n        container2.setImage(\"busybox:1.31.0\");\n        container2.setCommand(Arrays.asList(\"sh\", \"-c\", \"echo hello-from-original-properties\"));\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n        kubernetesDeployerProperties.setAdditionalContainers(Arrays.asList(container1, container2));\n        KubernetesAppDeployer kubernetesAppDeployer = kubernetesAppDeployer(kubernetesDeployerProperties);\n\n\n        Map<String, String> props = Stream.of(new String[][]{\n                {\n                        \"spring.cloud.deployer.kubernetes.volumes\",\n                        \"[{name: 'test-volume', emptyDir: {}}]\",\n                },\n                {\n                        \"spring.cloud.deployer.kubernetes.volumeMounts\",\n                        \"[{name: 'test-volume', mountPath: '/tmp'}]\",\n                },\n                {\n                        \"spring.cloud.deployer.kubernetes.additional-containers\",\n                        \"[{name: 'c1', image: 'busybox:latest', command: ['sh', '-c', 'echo hello1'], volumeMounts: [{name: 'test-volume', mountPath: '/tmp', readOnly: true}]},\"\n                                + \"{name: 'c2', image: 'busybox:1.26.1', command: ['sh', '-c', 'echo hello2']}]\"\n                }}).collect(Collectors.toMap(data -> data[0], data -> data[1]));\n\n        AppDefinition definition = new AppDefinition(randomName(), null);\n        AppDeploymentRequest request = new AppDeploymentRequest(definition, testApplication(), props);\n\n        log.info(\"Deploying {}...\", request.getDefinition().getName());\n        String deploymentId = kubernetesAppDeployer.deploy(request);\n        Timeout timeout = deploymentTimeout();\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.deployed));\n\n        Deployment deployment = kubernetesClient.apps().deployments().withName(request.getDefinition().getName()).get();\n        List<Container> containers = deployment.getSpec().getTemplate().getSpec().getContainers();\n\n        assertThat(containers).hasSize(4);\n\n        // c1 from the deployment properties should have overridden the c1 from the original deployer properties\n        Optional<Container> additionalContainer1 = containers.stream().filter(i -> i.getName().equals(\"c1\")).findFirst();\n        assertThat(additionalContainer1.isPresent()).as(\"Additional container c1 not found\").isTrue();\n\n        Container testAdditionalContainer1 = additionalContainer1.get();\n\n        assertThat(testAdditionalContainer1.getName()).as(\"Unexpected additional container name\").isEqualTo(\"c1\");\n        assertThat(testAdditionalContainer1.getImage()).as(\"Unexpected additional container image\").isEqualTo(\"busybox:latest\");\n\n        List<String> commands = testAdditionalContainer1.getCommand();\n\n        assertThat(commands).contains(\"sh\", \"-c\", \"echo hello1\");\n\n        List<VolumeMount> volumeMounts = testAdditionalContainer1.getVolumeMounts();\n\n        assertThat(volumeMounts).hasSize(1);\n        assertThat(volumeMounts.get(0).getName()).isEqualTo(\"test-volume\");\n        assertThat(volumeMounts.get(0).getMountPath()).isEqualTo(\"/tmp\");\n        assertThat(volumeMounts.get(0).getReadOnly()).isTrue();\n\n        Optional<Container> additionalContainer2 = containers.stream().filter(i -> i.getName().equals(\"c2\")).findFirst();\n        assertThat(additionalContainer2.isPresent()).as(\"Additional container c2 not found\").isTrue();\n\n        Container testAdditionalContainer2 = additionalContainer2.get();\n\n        assertThat(testAdditionalContainer2.getName()).as(\"Unexpected additional container name\").isEqualTo(\"c2\");\n        assertThat(testAdditionalContainer2.getImage()).as(\"Unexpected additional container image\").isEqualTo(\"busybox:1.26.1\");\n\n        List<String> container2Commands = testAdditionalContainer2.getCommand();\n\n        assertThat(container2Commands).contains(\"sh\", \"-c\", \"echo hello2\");\n\n        // Verifying the additional container passed from the root deployer properties\n        Optional<Container> additionalContainer3 = containers.stream().filter(i -> i.getName().equals(\"container2\")).findFirst();\n        assertThat(additionalContainer3.isPresent()).as(\"Additional container c2 not found\").isTrue();\n\n        Container testAdditionalContainer3 = additionalContainer3.get();\n\n        assertThat(testAdditionalContainer3.getName()).as(\"Unexpected additional container name\").isEqualTo(\"container2\");\n        assertThat(testAdditionalContainer3.getImage()).as(\"Unexpected additional container image\").isEqualTo(\"busybox:1.31.0\");\n\n        List<String> container3Commands = testAdditionalContainer3.getCommand();\n\n        assertThat(container3Commands).contains(\"sh\", \"-c\", \"echo hello-from-original-properties\");\n\n        log.info(\"Undeploying {}...\", deploymentId);\n        timeout = undeploymentTimeout();\n        kubernetesAppDeployer.undeploy(deploymentId);\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.unknown));\n    }\n\n\t@Nested\n\tclass SecurityContextITs {\n\n\t\t@Test\n\t\tvoid podWithInitContainerAndAdditionalContainers() {\n\n\t\t\tMap<String, String> deploymentProps = new HashMap<>();\n\t\t\tdeploymentProps.put(\"spring.cloud.deployer.kubernetes.initContainer\",\n\t\t\t\t\t\"{ containerName: 'init-container-5150', imageName: 'busybox:latest', commands: ['sh', '-c', 'echo hello']}\");\n\t\t\tdeploymentProps.put(\"spring.cloud.deployer.kubernetes.additional-containers\",\n\t\t\t\t\t\"[\" +\n\t\t\t\t\t\t\"{ name: 'extra-container-5150', image: 'busybox:latest', command: ['sh', '-c'], args: [\\\"while true; do echo ‘hello 5150’ & sleep 2; done\\\"]},\" +\n\t\t\t\t\t\t\"{ name: 'extra-container-6160', image: 'busybox:latest', command: ['sh', '-c'], args: [\\\"while true; do echo ‘hello 6160’ & sleep 2; done\\\"]}\" +\n\t\t\t\t\t\"]\");\n\t\t\tdeploymentProps.put(\"spring.cloud.deployer.kubernetes.podSecurityContext\",\n\t\t\t\t\t\"{ fsGroup: 65534\" +\n\t\t\t\t\t\t\", fsGroupChangePolicy: Always\" +\n\t\t\t\t\t\t\", runAsUser: 65534\" +\n\t\t\t\t\t\t\", runAsGroup: 65534\" +\n\t\t\t\t\t\t\", seLinuxOptions: { level: \\\"s0:c123,c456\\\" }\" +\n\t\t\t\t\t\"}\");\n\t\t\tdeploymentProps.put(\"spring.cloud.deployer.kubernetes.containerSecurityContext\",\n\t\t\t\t\t\"{ allowPrivilegeEscalation: true\" +\n\t\t\t\t\t\t\", runAsUser: 65534\" +\n\t\t\t\t\t\t\", runAsGroup: 65534\" +\n\t\t\t\t\t\t\", seLinuxOptions: { level: \\\"s0:c123,c777\\\" }\" +\n\t\t\t\t\t\"}\");\n\n\t\t\tAppDefinition definition = new AppDefinition(randomName(), null);\n\t\t\tAppDeploymentRequest request = new AppDeploymentRequest(definition, testApplication(), deploymentProps);\n\n\t\t\tKubernetesDeployerProperties deployProperties = new KubernetesDeployerProperties();\n\t\t\tdeployProperties.setCreateLoadBalancer(true);\n\t\t\tdeployProperties.setMinutesToWaitForLoadBalancer(1);\n\t\t\tKubernetesAppDeployer kubernetesAppDeployer = kubernetesAppDeployer(deployProperties);\n\n\t\t\tlog.info(\"Deploying {}...\", request.getDefinition().getName());\n\t\t\tString deploymentId = kubernetesAppDeployer.deploy(request);\n\n\t\t\tTimeout timeout = deploymentTimeout();\n\t\t\tawait().pollInterval(Duration.ofMillis(timeout.pause))\n\t\t\t\t\t.atMost(Duration.ofMillis(timeout.totalTime))\n\t\t\t\t\t.untilAsserted(() -> assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.deployed));\n\n\t\t\tPodSecurityContext expectedPodSecurityContext = new PodSecurityContextBuilder()\n\t\t\t\t\t.withFsGroup(65534L)\n\t\t\t\t\t.withFsGroupChangePolicy(\"Always\")\n\t\t\t\t\t.withRunAsUser(65534L)\n\t\t\t\t\t.withRunAsGroup(65534L)\n\t\t\t\t\t.withSeLinuxOptions(new SELinuxOptions(\"s0:c123,c456\", null, null, null))\n\t\t\t\t\t.build();\n\n\t\t\tSecurityContext expectedContainerSecurityContext = new SecurityContextBuilder()\n\t\t\t\t\t.withAllowPrivilegeEscalation(true)\n\t\t\t\t\t.withRunAsUser(65534L)\n\t\t\t\t\t.withRunAsGroup(65534L)\n\t\t\t\t\t.withSeLinuxOptions(new SELinuxOptions(\"s0:c123,c777\", null, null, null))\n\t\t\t\t\t.build();\n\n\t\t\tDeployment deployment = kubernetesClient.apps().deployments().withName(request.getDefinition().getName()).get();\n\n\t\t\tassertThat(deployment.getSpec().getTemplate().getSpec().getSecurityContext())\n\t\t\t\t\t.isEqualTo(expectedPodSecurityContext);\n\n\t\t\tassertThatContainerExistsWithSecurityContext(deployment.getSpec().getTemplate().getSpec().getInitContainers(),\n\t\t\t\t\t\"init-container-5150\", expectedContainerSecurityContext);\n\n\t\t\tassertThatContainerExistsWithSecurityContext(deployment.getSpec().getTemplate().getSpec().getContainers(),\n\t\t\t\t\t\"extra-container-5150\", expectedContainerSecurityContext);\n\n\t\t\tassertThatContainerExistsWithSecurityContext(deployment.getSpec().getTemplate().getSpec().getContainers(),\n\t\t\t\t\t\"extra-container-6160\", expectedContainerSecurityContext);\n\n\t\t\tlog.info(\"Undeploying {}...\", deploymentId);\n\t\t\ttimeout = undeploymentTimeout();\n\t\t\tkubernetesAppDeployer.undeploy(deploymentId);\n\t\t\tawait().pollInterval(Duration.ofMillis(timeout.pause))\n\t\t\t\t\t.atMost(Duration.ofMillis(timeout.totalTime))\n\t\t\t\t\t.untilAsserted(() -> assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.unknown));\n\t\t}\n\n\t\t@Test\n\t\tvoid statefulSetWithInitContainerAndAdditionalContainers() throws IOException {\n\n\t\t\tMap<String, String> deploymentProps = new HashMap<>();\n\t\t\tdeploymentProps.put(AppDeployer.COUNT_PROPERTY_KEY, \"2\");\n\t\t\tdeploymentProps.put(AppDeployer.INDEXED_PROPERTY_KEY, \"true\");\n\t\t\tdeploymentProps.put(\"spring.cloud.deployer.kubernetes.podSecurityContext\",\n\t\t\t\t\t\"{ fsGroup: 65534\" +\n\t\t\t\t\t\t\t\", fsGroupChangePolicy: Always\" +\n\t\t\t\t\t\t\t\", runAsUser: 65534\" +\n\t\t\t\t\t\t\t\", runAsGroup: 65534\" +\n\t\t\t\t\t\t\t\", seLinuxOptions: { level: \\\"s0:c123,c456\\\" }\" +\n\t\t\t\t\t\t\t\"}\");\n\t\t\tdeploymentProps.put(\"spring.cloud.deployer.kubernetes.containerSecurityContext\",\n\t\t\t\t\t\"{ allowPrivilegeEscalation: true\" +\n\t\t\t\t\t\t\t\", runAsUser: 65534\" +\n\t\t\t\t\t\t\t\", runAsGroup: 65534\" +\n\t\t\t\t\t\t\t\", seLinuxOptions: { level: \\\"s0:c123,c777\\\" }\" +\n\t\t\t\t\t\t\t\"}\");\n\n\t\t\tAppDefinition definition = new AppDefinition(randomName(), null);\n\t\t\tAppDeploymentRequest request = new AppDeploymentRequest(definition, testApplication(), deploymentProps);\n\n\t\t\tString imageName = testApplication().getURI().getSchemeSpecificPart();\n\t\t\tKubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n\t\t\tkubernetesDeployerProperties.setStatefulSetInitContainerImageName(imageName);\n\t\t\tKubernetesAppDeployer kubernetesAppDeployer = kubernetesAppDeployer(kubernetesDeployerProperties);\n\n\t\t\tlog.info(\"Deploying {}...\", request.getDefinition().getName());\n\t\t\tString deploymentId = kubernetesAppDeployer.deploy(request);\n\n\t\t\tTimeout timeout = deploymentTimeout();\n\t\t\tawait().pollInterval(Duration.ofMillis(timeout.pause))\n\t\t\t\t\t.atMost(Duration.ofMillis(timeout.totalTime))\n\t\t\t\t\t.untilAsserted(() -> assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.deployed));\n\n\t\t\tPodSecurityContext expectedPodSecurityContext = new PodSecurityContextBuilder()\n\t\t\t\t\t.withFsGroup(65534L)\n\t\t\t\t\t.withFsGroupChangePolicy(\"Always\")\n\t\t\t\t\t.withRunAsUser(65534L)\n\t\t\t\t\t.withRunAsGroup(65534L)\n\t\t\t\t\t.withSeLinuxOptions(new SELinuxOptions(\"s0:c123,c456\", null, null, null))\n\t\t\t\t\t.build();\n\n\t\t\tSecurityContext expectedContainerSecurityContext = new SecurityContextBuilder()\n\t\t\t\t\t.withAllowPrivilegeEscalation(true)\n\t\t\t\t\t.withRunAsUser(65534L)\n\t\t\t\t\t.withRunAsGroup(65534L)\n\t\t\t\t\t.withSeLinuxOptions(new SELinuxOptions(\"s0:c123,c777\", null, null, null))\n\t\t\t\t\t.build();\n\n\t\t\tMap<String, String> selector = Collections.singletonMap(AbstractKubernetesDeployer.SPRING_APP_KEY, deploymentId);\n\t\t\tList<StatefulSet> statefulSets = kubernetesClient.apps().statefulSets().withLabels(selector).list().getItems();\n\t\t\tassertThat(statefulSets).hasSize(1)\n\t\t\t\t\t.element(0, InstanceOfAssertFactories.type(StatefulSet.class))\n\t\t\t\t\t.extracting(\"spec.template.spec\", InstanceOfAssertFactories.type(PodSpec.class))\n\t\t\t\t\t.satisfies((podSpec) -> {\n\t\t\t\t\t\tassertThat(podSpec.getSecurityContext()).isEqualTo(expectedPodSecurityContext);\n\t\t\t\t\t\tassertThat(podSpec.getInitContainers())\n\t\t\t\t\t\t\t\t.hasSize(1)\n\t\t\t\t\t\t\t\t.element(0, InstanceOfAssertFactories.type(Container.class))\n\t\t\t\t\t\t\t\t.extracting(Container::getSecurityContext).isEqualTo(expectedContainerSecurityContext);\n\t\t\t\t\t});\n\n\t\t\tlog.info(\"Undeploying {}...\", deploymentId);\n\t\t\ttimeout = undeploymentTimeout();\n\t\t\tkubernetesAppDeployer.undeploy(deploymentId);\n\t\t\tawait().pollInterval(Duration.ofMillis(timeout.pause))\n\t\t\t\t\t.atMost(Duration.ofMillis(timeout.totalTime))\n\t\t\t\t\t.untilAsserted(() -> assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.unknown));\n\t\t}\n\n\t}\n\n\tprivate void assertThatContainerExistsWithSecurityContext(List<Container> containers, String expectedName, SecurityContext expectedSecurityContext) {\n\t\tassertThat(containers\n\t\t\t\t.stream().filter(c -> c.getName().equals(expectedName)).findFirst())\n\t\t\t\t.hasValueSatisfying((initContainer) -> assertThat(initContainer.getSecurityContext()).isEqualTo(expectedSecurityContext));\n\t}\n\n    @Test\n    public void testUnknownStatusOnPendingResources() throws InterruptedException {\n        log.info(\"Testing {}...\", \"UnknownStatusOnPendingResources\");\n        KubernetesAppDeployer kubernetesAppDeployer = kubernetesAppDeployer();\n\n        AppDefinition definition = new AppDefinition(randomName(), null);\n        Resource resource = testApplication();\n\n        Map<String, String> props = new HashMap<>();\n        // requests.cpu mirrors limits.cpu when only limits is set avoiding need to set both here\n        props.put(\"spring.cloud.deployer.kubernetes.limits.cpu\", \"5000\");\n\n        AppDeploymentRequest request = new AppDeploymentRequest(definition, resource, props);\n\n        log.info(\"Deploying {}...\", request.getDefinition().getName());\n        String deploymentId = kubernetesAppDeployer.deploy(request);\n\n        while (kubernetesClient.pods().withLabel(\"spring-deployment-id\", deploymentId).list().getItems().isEmpty()) {\n            log.info(\"Waiting for deployed pod\");\n            Thread.sleep(500);\n        }\n\n        Pod pod = kubernetesClient.pods().withLabel(\"spring-deployment-id\", deploymentId).list().getItems().get(0);\n\n        while (pod.getStatus().getConditions().isEmpty()) {\n            log.info(\"Waiting for pod conditions to be set\");\n            Thread.sleep(500);\n        }\n\n        while (!\"Unschedulable\".equals(pod.getStatus().getConditions().get(0).getReason())) {\n            log.info(\"Waiting for deployed pod to become Unschedulable\");\n        }\n\n        Timeout timeout = deploymentTimeout();\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.unknown));\n\n        log.info(\"Undeploying {}...\", deploymentId);\n        timeout = undeploymentTimeout();\n\n        assertThatThrownBy(() -> kubernetesAppDeployer.undeploy(deploymentId)).isInstanceOf(IllegalStateException.class)\n                .hasMessage(\"App '%s' is not deployed\", deploymentId);\n\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.unknown));\n    }\n\n    @Test\n    public void testSecretRef() throws InterruptedException {\n        log.info(\"Testing {}...\", \"SecretRef\");\n\n        Secret secret = randomSecret();\n\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n        kubernetesDeployerProperties.setSecretRefs(Collections.singletonList(secret.getMetadata().getName()));\n\n        KubernetesAppDeployer kubernetesAppDeployer = kubernetesAppDeployer(kubernetesDeployerProperties);\n\n        AppDefinition definition = new AppDefinition(randomName(), null);\n        Resource resource = testApplication();\n        AppDeploymentRequest request = new AppDeploymentRequest(definition, resource);\n\n        log.info(\"Deploying {}...\", request.getDefinition().getName());\n        String deploymentId = kubernetesAppDeployer.deploy(request);\n        Timeout timeout = deploymentTimeout();\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.deployed));\n\n        Container container = kubernetesClient.apps().deployments().withName(deploymentId).get().getSpec()\n                .getTemplate().getSpec().getContainers().get(0);\n\n        List<EnvFromSource> envFromSources = container.getEnvFrom();\n\n        assertThat(envFromSources).hasSize(1);\n\n        EnvFromSource envFromSource = envFromSources.get(0);\n        assertThat(envFromSource.getSecretRef().getName()).isEqualTo(secret.getMetadata().getName());\n\n        String podEnvironment = getPodEnvironment(deploymentId);\n\n        assertThat(secret.getData()).hasSize(2);\n\n        for (Map.Entry<String, String> secretData : secret.getData().entrySet()) {\n            String decodedValue = new String(Base64.getDecoder().decode(secretData.getValue()));\n            assertThat(podEnvironment).contains(secretData.getKey() + \"=\" + decodedValue);\n        }\n\n        log.info(\"Undeploying {}...\", deploymentId);\n        timeout = undeploymentTimeout();\n        kubernetesAppDeployer.undeploy(deploymentId);\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.unknown));\n\n        kubernetesClient.secrets().inNamespace(\"default\").resource(secret).delete();\n    }\n\n    @Test\n    public void testSecretRefFromDeployerProperty() throws InterruptedException {\n        log.info(\"Testing {}...\", \"SecretRefFromDeployerProperty\");\n\n        Secret secret = randomSecret();\n\n        KubernetesAppDeployer kubernetesAppDeployer = kubernetesAppDeployer();\n\n        Map<String, String> props = new HashMap<>();\n        props.put(\"spring.cloud.deployer.kubernetes.secretRefs\", secret.getMetadata().getName());\n\n        AppDefinition definition = new AppDefinition(randomName(), null);\n        Resource resource = testApplication();\n        AppDeploymentRequest request = new AppDeploymentRequest(definition, resource, props);\n\n        log.info(\"Deploying {}...\", request.getDefinition().getName());\n        String deploymentId = kubernetesAppDeployer.deploy(request);\n        Timeout timeout = deploymentTimeout();\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.deployed));\n\n        Container container = kubernetesClient.apps().deployments().withName(deploymentId).get().getSpec()\n                .getTemplate().getSpec().getContainers().get(0);\n\n        List<EnvFromSource> envFromSources = container.getEnvFrom();\n\n        assertThat(envFromSources).hasSize(1);\n\n        EnvFromSource envFromSource = envFromSources.get(0);\n        assertThat(secret.getMetadata().getName()).isEqualTo(envFromSource.getSecretRef().getName());\n\n        String podEnvironment = getPodEnvironment(deploymentId);\n\n        assertThat(secret.getData()).hasSize(2);\n\n        for (Map.Entry<String, String> secretData : secret.getData().entrySet()) {\n            String decodedValue = new String(Base64.getDecoder().decode(secretData.getValue()));\n            assertThat(podEnvironment).contains(secretData.getKey() + \"=\" + decodedValue);\n        }\n\n        log.info(\"Undeploying {}...\", deploymentId);\n        timeout = undeploymentTimeout();\n        kubernetesAppDeployer.undeploy(deploymentId);\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.unknown));\n\n        kubernetesClient.secrets().inNamespace(\"default\").resource(secret).delete();\n    }\n\n    @Test\n    public void testSecretRefFromDeployerPropertyOverride() throws IOException, InterruptedException {\n        log.info(\"Testing {}...\", \"SecretRefFromDeployerPropertyOverride\");\n\n        Secret propertySecret = randomSecret();\n        Secret deployerPropertySecret = randomSecret();\n\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n        kubernetesDeployerProperties.setSecretRefs(Collections.singletonList(propertySecret.getMetadata().getName()));\n\n        KubernetesAppDeployer kubernetesAppDeployer = kubernetesAppDeployer(kubernetesDeployerProperties);\n\n        Map<String, String> props = new HashMap<>();\n        props.put(\"spring.cloud.deployer.kubernetes.secretRefs\", deployerPropertySecret.getMetadata().getName());\n\n        AppDefinition definition = new AppDefinition(randomName(), null);\n        Resource resource = testApplication();\n        AppDeploymentRequest request = new AppDeploymentRequest(definition, resource, props);\n\n        log.info(\"Deploying {}...\", request.getDefinition().getName());\n        String deploymentId = kubernetesAppDeployer.deploy(request);\n        Timeout timeout = deploymentTimeout();\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.deployed));\n\n        Container container = kubernetesClient.apps().deployments().withName(deploymentId).get().getSpec()\n                .getTemplate().getSpec().getContainers().get(0);\n\n        List<EnvFromSource> envFromSources = container.getEnvFrom();\n\n        assertThat(envFromSources).hasSize(1);\n\n        EnvFromSource envFromSource = envFromSources.get(0);\n        assertThat(envFromSource.getSecretRef().getName()).isEqualTo(deployerPropertySecret.getMetadata().getName());\n\n        String podEnvironment = getPodEnvironment(deploymentId);\n\n        for (Map.Entry<String, String> deployerPropertySecretData : deployerPropertySecret.getData().entrySet()) {\n            String decodedValue = new String(Base64.getDecoder().decode(deployerPropertySecretData.getValue()));\n            assertThat(podEnvironment).contains(deployerPropertySecretData.getKey() + \"=\" + decodedValue);\n        }\n\n        for (Map.Entry<String, String> propertySecretData : propertySecret.getData().entrySet()) {\n            String decodedValue = new String(Base64.getDecoder().decode(propertySecretData.getValue()));\n            assertThat(podEnvironment).doesNotContain(propertySecretData.getKey() + \"=\" + decodedValue);\n        }\n\n        log.info(\"Undeploying {}...\", deploymentId);\n        timeout = undeploymentTimeout();\n        kubernetesAppDeployer.undeploy(deploymentId);\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.unknown));\n\n        kubernetesClient.secrets().inNamespace(\"default\").resource(propertySecret).delete();\n        kubernetesClient.secrets().inNamespace(\"default\").resource(deployerPropertySecret).delete();\n    }\n\n    @Test\n    public void testSecretRefFromPropertyMultiple() throws InterruptedException {\n        log.info(\"Testing {}...\", \"SecretRefFromPropertyMultiple\");\n\n        Secret secret1 = randomSecret();\n        Secret secret2 = randomSecret();\n\n        List<String> secrets = new ArrayList<>();\n        secrets.add(secret1.getMetadata().getName());\n        secrets.add(secret2.getMetadata().getName());\n\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n        kubernetesDeployerProperties.setSecretRefs(secrets);\n\n        KubernetesAppDeployer kubernetesAppDeployer = kubernetesAppDeployer(kubernetesDeployerProperties);\n\n        AppDefinition definition = new AppDefinition(randomName(), null);\n        Resource resource = testApplication();\n        AppDeploymentRequest request = new AppDeploymentRequest(definition, resource, null);\n\n        log.info(\"Deploying {}...\", request.getDefinition().getName());\n        String deploymentId = kubernetesAppDeployer.deploy(request);\n        Timeout timeout = deploymentTimeout();\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.deployed));\n\n        Container container = kubernetesClient.apps().deployments().withName(deploymentId).get().getSpec()\n                .getTemplate().getSpec().getContainers().get(0);\n\n        List<EnvFromSource> envFromSources = container.getEnvFrom();\n\n        assertThat(envFromSources).hasSize(2);\n\n        String podEnvironment = getPodEnvironment(deploymentId);\n\n        EnvFromSource envFromSource1 = envFromSources.get(0);\n        assertThat(envFromSource1.getSecretRef().getName()).isEqualTo(secret1.getMetadata().getName());\n\n        EnvFromSource envFromSource2 = envFromSources.get(1);\n        assertThat(envFromSource2.getSecretRef().getName()).isEqualTo(secret2.getMetadata().getName());\n\n        Map<String, String> mergedSecretData = new HashMap<>();\n        mergedSecretData.putAll(secret1.getData());\n        mergedSecretData.putAll(secret2.getData());\n\n        assertThat(mergedSecretData).hasSize(4);\n\n        for (Map.Entry<String, String> secretData : secret1.getData().entrySet()) {\n            String decodedValue = new String(Base64.getDecoder().decode(secretData.getValue()));\n            assertThat(podEnvironment).contains(secretData.getKey() + \"=\" + decodedValue);\n        }\n\n        log.info(\"Undeploying {}...\", deploymentId);\n        timeout = undeploymentTimeout();\n        kubernetesAppDeployer.undeploy(deploymentId);\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.unknown));\n\n        kubernetesClient.secrets().inNamespace(\"default\").resource(secret1).delete();\n        kubernetesClient.secrets().inNamespace(\"default\").resource(secret2).delete();\n    }\n\n    @Test\n    public void testSecretRefFromDeploymentPropertyMultiple() throws InterruptedException {\n        log.info(\"Testing {}...\", \"SecretRefFromDeploymentPropertyMultiple\");\n\n        Secret secret1 = randomSecret();\n        Secret secret2 = randomSecret();\n\n        Map<String, String> props = new HashMap<>();\n        props.put(\"spring.cloud.deployer.kubernetes.secretRefs\", \"[\" + secret1.getMetadata().getName() + \",\" +\n                secret2.getMetadata().getName() + \"]\");\n\n        KubernetesAppDeployer kubernetesAppDeployer = kubernetesAppDeployer();\n\n        AppDefinition definition = new AppDefinition(randomName(), null);\n        Resource resource = testApplication();\n        AppDeploymentRequest request = new AppDeploymentRequest(definition, resource, props);\n\n        log.info(\"Deploying {}...\", request.getDefinition().getName());\n        String deploymentId = kubernetesAppDeployer.deploy(request);\n        Timeout timeout = deploymentTimeout();\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.deployed));\n\n        Container container = kubernetesClient.apps().deployments().withName(deploymentId).get().getSpec()\n                .getTemplate().getSpec().getContainers().get(0);\n\n        List<EnvFromSource> envFromSources = container.getEnvFrom();\n\n        assertThat(envFromSources).hasSize(2);\n\n        String podEnvironment = getPodEnvironment(deploymentId);\n\n        EnvFromSource envFromSource1 = envFromSources.get(0);\n        assertThat(secret1.getMetadata().getName()).isEqualTo(envFromSource1.getSecretRef().getName());\n\n        EnvFromSource envFromSource2 = envFromSources.get(1);\n        assertThat(secret2.getMetadata().getName()).isEqualTo(envFromSource2.getSecretRef().getName());\n\n        Map<String, String> mergedSecretData = new HashMap<>();\n        mergedSecretData.putAll(secret1.getData());\n        mergedSecretData.putAll(secret2.getData());\n\n        assertThat(mergedSecretData).hasSize(4);\n\n        for (Map.Entry<String, String> secretData : secret1.getData().entrySet()) {\n            String decodedValue = new String(Base64.getDecoder().decode(secretData.getValue()));\n            assertThat(podEnvironment).contains(secretData.getKey() + \"=\" + decodedValue);\n        }\n\n        log.info(\"Undeploying {}...\", deploymentId);\n        timeout = undeploymentTimeout();\n        kubernetesAppDeployer.undeploy(deploymentId);\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.unknown));\n\n        kubernetesClient.secrets().inNamespace(\"default\").resource(secret1).delete();\n        kubernetesClient.secrets().inNamespace(\"default\").resource(secret2).delete();\n    }\n\n    @Test\n    public void testConfigMapRef() throws InterruptedException {\n        log.info(\"Testing {}...\", \"ConfigMapRef\");\n\n        ConfigMap configMap = randomConfigMap();\n\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n        kubernetesDeployerProperties.setConfigMapRefs(Collections.singletonList(configMap.getMetadata().getName()));\n\n        KubernetesAppDeployer kubernetesAppDeployer = kubernetesAppDeployer(kubernetesDeployerProperties);\n\n        AppDefinition definition = new AppDefinition(randomName(), null);\n        Resource resource = testApplication();\n        AppDeploymentRequest request = new AppDeploymentRequest(definition, resource);\n\n        log.info(\"Deploying {}...\", request.getDefinition().getName());\n        String deploymentId = kubernetesAppDeployer.deploy(request);\n        Timeout timeout = deploymentTimeout();\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.deployed));\n\n        Container container = kubernetesClient.apps().deployments().withName(deploymentId).get().getSpec()\n                .getTemplate().getSpec().getContainers().get(0);\n\n        List<EnvFromSource> envFromSources = container.getEnvFrom();\n\n        assertThat(envFromSources).hasSize(1);\n\n        EnvFromSource envFromSource = envFromSources.get(0);\n        assertThat(envFromSource.getConfigMapRef().getName()).isEqualTo(configMap.getMetadata().getName());\n\n        String podEnvironment = getPodEnvironment(deploymentId);\n\n        assertThat(configMap.getData()).hasSize(2);\n\n        for (Map.Entry<String, String> configMapData : configMap.getData().entrySet()) {\n            assertThat(podEnvironment).contains(configMapData.getKey() + \"=\" + configMapData.getValue());\n        }\n\n        log.info(\"Undeploying {}...\", deploymentId);\n        timeout = undeploymentTimeout();\n        kubernetesAppDeployer.undeploy(deploymentId);\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.unknown));\n\n        kubernetesClient.configMaps().inNamespace(\"default\").resource(configMap).delete();\n    }\n\n    @Test\n    public void testConfigMapRefFromDeployerProperty() throws InterruptedException {\n        log.info(\"Testing {}...\", \"ConfigMapRefFromDeployerProperty\");\n\n        ConfigMap configMap = randomConfigMap();\n\n        KubernetesAppDeployer kubernetesAppDeployer = kubernetesAppDeployer();\n\n        Map<String, String> props = new HashMap<>();\n        props.put(\"spring.cloud.deployer.kubernetes.config-map-refs\", configMap.getMetadata().getName());\n\n        AppDefinition definition = new AppDefinition(randomName(), null);\n        Resource resource = testApplication();\n        AppDeploymentRequest request = new AppDeploymentRequest(definition, resource, props);\n\n        log.info(\"Deploying {}...\", request.getDefinition().getName());\n        String deploymentId = kubernetesAppDeployer.deploy(request);\n        Timeout timeout = deploymentTimeout();\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.deployed));\n\n        Container container = kubernetesClient.apps().deployments().withName(deploymentId).get().getSpec()\n                .getTemplate().getSpec().getContainers().get(0);\n\n        List<EnvFromSource> envFromSources = container.getEnvFrom();\n\n        assertThat(envFromSources).hasSize(1);\n\n        EnvFromSource envFromSource = envFromSources.get(0);\n        assertThat(envFromSource.getConfigMapRef().getName()).isEqualTo(configMap.getMetadata().getName());\n\n        String podEnvironment = getPodEnvironment(deploymentId);\n\n        assertThat(configMap.getData()).hasSize(2);\n\n        for (Map.Entry<String, String> configMapData : configMap.getData().entrySet()) {\n            assertThat(podEnvironment).contains(configMapData.getKey() + \"=\" + configMapData.getValue());\n        }\n\n        log.info(\"Undeploying {}...\", deploymentId);\n        timeout = undeploymentTimeout();\n        kubernetesAppDeployer.undeploy(deploymentId);\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.unknown));\n\n        kubernetesClient.configMaps().inNamespace(\"default\").resource(configMap).delete();\n    }\n\n    @Test\n    public void testConfigMapRefFromDeployerPropertyOverride() throws IOException, InterruptedException {\n        log.info(\"Testing {}...\", \"ConfigMapRefFromDeployerPropertyOverride\");\n\n        ConfigMap propertyConfigMap = randomConfigMap();\n        ConfigMap deployerPropertyConfigMap = randomConfigMap();\n\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n        kubernetesDeployerProperties.setConfigMapRefs(Collections.singletonList(propertyConfigMap.getMetadata().getName()));\n\n        KubernetesAppDeployer kubernetesAppDeployer = kubernetesAppDeployer(kubernetesDeployerProperties);\n\n        Map<String, String> props = new HashMap<>();\n        props.put(\"spring.cloud.deployer.kubernetes.configMapRefs\", deployerPropertyConfigMap.getMetadata().getName());\n\n        AppDefinition definition = new AppDefinition(randomName(), null);\n        Resource resource = testApplication();\n        AppDeploymentRequest request = new AppDeploymentRequest(definition, resource, props);\n\n        log.info(\"Deploying {}...\", request.getDefinition().getName());\n        String deploymentId = kubernetesAppDeployer.deploy(request);\n        Timeout timeout = deploymentTimeout();\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.deployed));\n\n        Container container = kubernetesClient.apps().deployments().withName(deploymentId).get().getSpec()\n                .getTemplate().getSpec().getContainers().get(0);\n\n        List<EnvFromSource> envFromSources = container.getEnvFrom();\n\n        assertThat(envFromSources).hasSize(1);\n\n        EnvFromSource envFromSource = envFromSources.get(0);\n        assertThat(deployerPropertyConfigMap.getMetadata().getName()).isEqualTo(envFromSource.getConfigMapRef().getName());\n\n        String podEnvironment = getPodEnvironment(deploymentId);\n\n        for (Map.Entry<String, String> deployerPropertyConfigMapData : deployerPropertyConfigMap.getData().entrySet()) {\n            assertThat(podEnvironment)\n                    .contains(deployerPropertyConfigMapData.getKey() + \"=\" + deployerPropertyConfigMapData.getValue());\n        }\n\n        for (Map.Entry<String, String> propertyConfigMapData : propertyConfigMap.getData().entrySet()) {\n            assertThat(podEnvironment).doesNotContain(propertyConfigMapData.getKey() + \"=\" + propertyConfigMapData.getValue());\n        }\n\n        log.info(\"Undeploying {}...\", deploymentId);\n        timeout = undeploymentTimeout();\n        kubernetesAppDeployer.undeploy(deploymentId);\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.unknown));\n\n        kubernetesClient.configMaps().inNamespace(\"default\").resource(propertyConfigMap).delete();\n        kubernetesClient.configMaps().inNamespace(\"default\").resource(deployerPropertyConfigMap).delete();\n    }\n\n    @Test\n    public void testConfigMapRefFromPropertyMultiple() throws InterruptedException {\n        log.info(\"Testing {}...\", \"ConfigMapRefFromPropertyMultiple\");\n\n        ConfigMap configMap1 = randomConfigMap();\n        ConfigMap configMap2 = randomConfigMap();\n\n        List<String> configMaps = new ArrayList<>();\n        configMaps.add(configMap1.getMetadata().getName());\n        configMaps.add(configMap2.getMetadata().getName());\n\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n        kubernetesDeployerProperties.setConfigMapRefs(configMaps);\n\n        KubernetesAppDeployer kubernetesAppDeployer = kubernetesAppDeployer(kubernetesDeployerProperties);\n\n        AppDefinition definition = new AppDefinition(randomName(), null);\n        Resource resource = testApplication();\n        AppDeploymentRequest request = new AppDeploymentRequest(definition, resource, null);\n\n        log.info(\"Deploying {}...\", request.getDefinition().getName());\n        String deploymentId = kubernetesAppDeployer.deploy(request);\n        Timeout timeout = deploymentTimeout();\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.deployed));\n\n        Container container = kubernetesClient.apps().deployments().withName(deploymentId).get().getSpec()\n                .getTemplate().getSpec().getContainers().get(0);\n\n        List<EnvFromSource> envFromSources = container.getEnvFrom();\n\n        assertThat(envFromSources).hasSize(2);\n\n        String podEnvironment = getPodEnvironment(deploymentId);\n\n        EnvFromSource envFromSource1 = envFromSources.get(0);\n        assertThat(configMap1.getMetadata().getName()).isEqualTo(envFromSource1.getConfigMapRef().getName());\n\n        EnvFromSource envFromSource2 = envFromSources.get(1);\n        assertThat(configMap2.getMetadata().getName()).isEqualTo(envFromSource2.getConfigMapRef().getName());\n\n        Map<String, String> mergedConfigMapData = new HashMap<>();\n        mergedConfigMapData.putAll(configMap1.getData());\n        mergedConfigMapData.putAll(configMap2.getData());\n\n        assertThat(mergedConfigMapData).hasSize(4);\n\n        for (Map.Entry<String, String> configMapData : configMap1.getData().entrySet()) {\n            assertThat(podEnvironment).contains(configMapData.getKey() + \"=\" + configMapData.getValue());\n        }\n\n        log.info(\"Undeploying {}...\", deploymentId);\n        timeout = undeploymentTimeout();\n        kubernetesAppDeployer.undeploy(deploymentId);\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.unknown));\n\n        kubernetesClient.configMaps().inNamespace(\"default\").resource(configMap1).delete();\n        kubernetesClient.configMaps().inNamespace(\"default\").resource(configMap2).delete();\n    }\n\n    @Test\n    public void testConfigMapRefFromDeploymentPropertyMultiple() throws InterruptedException {\n        log.info(\"Testing {}...\", \"ConfigMapRefFromDeploymentPropertyMultiple\");\n\n        ConfigMap configMap1 = randomConfigMap();\n        ConfigMap configMap2 = randomConfigMap();\n\n        Map<String, String> props = new HashMap<>();\n        props.put(\"spring.cloud.deployer.kubernetes.configMapRefs\", \"[\" + configMap1.getMetadata().getName() + \",\" +\n                configMap2.getMetadata().getName() + \"]\");\n\n        KubernetesAppDeployer kubernetesAppDeployer = kubernetesAppDeployer();\n\n        AppDefinition definition = new AppDefinition(randomName(), null);\n        Resource resource = testApplication();\n        AppDeploymentRequest request = new AppDeploymentRequest(definition, resource, props);\n\n        log.info(\"Deploying {}...\", request.getDefinition().getName());\n        String deploymentId = kubernetesAppDeployer.deploy(request);\n        Timeout timeout = deploymentTimeout();\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.deployed));\n\n        Container container = kubernetesClient.apps().deployments().withName(deploymentId).get().getSpec()\n                .getTemplate().getSpec().getContainers().get(0);\n\n        List<EnvFromSource> envFromSources = container.getEnvFrom();\n\n        assertThat(envFromSources).isNotNull();\n        assertThat(envFromSources).hasSize(2);\n\n        String podEnvironment = getPodEnvironment(deploymentId);\n\n        EnvFromSource envFromSource1 = envFromSources.get(0);\n        assertThat(envFromSource1.getConfigMapRef().getName()).isEqualTo(configMap1.getMetadata().getName());\n\n        EnvFromSource envFromSource2 = envFromSources.get(1);\n        assertThat(envFromSource2.getConfigMapRef().getName()).isEqualTo(configMap2.getMetadata().getName());\n\n        Map<String, String> mergedConfigMapData = new HashMap<>();\n        mergedConfigMapData.putAll(configMap1.getData());\n        mergedConfigMapData.putAll(configMap2.getData());\n\n        assertThat(mergedConfigMapData).hasSize(4);\n\n        for (Map.Entry<String, String> configMapData : configMap1.getData().entrySet()) {\n            assertThat(podEnvironment).contains(configMapData.getKey() + \"=\" + configMapData.getValue());\n        }\n\n        log.info(\"Undeploying {}...\", deploymentId);\n        timeout = undeploymentTimeout();\n        kubernetesAppDeployer.undeploy(deploymentId);\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.unknown));\n\n        kubernetesClient.configMaps().inNamespace(\"default\").resource(configMap1).delete();\n        kubernetesClient.configMaps().inNamespace(\"default\").resource(configMap2).delete();\n    }\n\n    @Override\n    protected String randomName() {\n        // Kubernetes service names must start with a letter and can only be 24 characters long\n        return \"app-\" + UUID.randomUUID().toString().substring(0, 18);\n    }\n\n    @Override\n    protected Timeout deploymentTimeout() {\n        return new Timeout(300, 2000);\n    }\n\n    @Override\n    protected Resource testApplication() {\n        return new DockerResource(\"springcloud/spring-cloud-deployer-spi-test-app:latest\");\n    }\n\n    private String getPodEnvironment(String deploymentId) throws InterruptedException {\n        String podName = kubernetesClient.pods().withLabel(\"spring-deployment-id\", deploymentId).list().getItems()\n                .get(0).getMetadata().getName();\n\n        final CountDownLatch countDownLatch = new CountDownLatch(1);\n        ByteArrayOutputStream execOutputStream = new ByteArrayOutputStream();\n\n        ExecWatch watch = kubernetesClient.pods().withName(podName).inContainer(deploymentId)\n                .writingOutput(execOutputStream)\n                .usingListener(new ExecListener() {\n\n                    @Override\n                    public void onFailure(Throwable throwable, Response response) {\n                        countDownLatch.countDown();\n                    }\n\n                    @Override\n                    public void onClose(int code, String reason) {\n                        countDownLatch.countDown();\n                    }\n                }).exec(\"printenv\");\n\n        countDownLatch.await();\n\n        watch.close();\n\n\t\treturn execOutputStream.toString();\n    }\n\n    // Creates a Secret with a name will be generated prefixed by \"secret-\" followed by random numbers.\n    //\n    // Two data keys are present, both prefixed by \"d-\" followed by random numbers. This allows for cases where\n    // multiple Secrets may be read into environment variables avoiding variable name clashes.\n    //\n    // Data values are Base64 encoded strings of value1 and value2\n    private Secret randomSecret() {\n        Map<String, String> secretData = new HashMap<>();\n        secretData.put(\"d-\" + UUID.randomUUID().toString().substring(0, 5), \"dmFsdWUx\");\n        secretData.put(\"d-\" + UUID.randomUUID().toString().substring(0, 5), \"dmFsdWUy\");\n\n        Secret secret = new Secret();\n        secret.setData(secretData);\n\n        ObjectMeta objectMeta = new ObjectMeta();\n        objectMeta.setName(\"secret-\" + UUID.randomUUID().toString().substring(0, 5));\n\n        secret.setMetadata(objectMeta);\n\n\t\treturn kubernetesClient.secrets().inNamespace(\"default\").resource(secret).create();\n    }\n\n    // Creates a ConfigMap with a name will be generated prefixed by \"cm-\" followed by random numbers.\n    //\n    // Two data keys are present, both prefixed by \"d-\" followed by random numbers. This allows for cases where\n    // multiple ConfigMaps may be read into environment variables avoiding variable name clashes.\n    //\n    // Data values are strings of value1 and value\n    private ConfigMap randomConfigMap() {\n        Map<String, String> configMapData = new HashMap<>();\n        configMapData.put(\"d-\" + UUID.randomUUID().toString().substring(0, 5), \"value1\");\n        configMapData.put(\"d-\" + UUID.randomUUID().toString().substring(0, 5), \"value2\");\n\n        ConfigMap configMap = new ConfigMap();\n        configMap.setData(configMapData);\n\n        ObjectMeta objectMeta = new ObjectMeta();\n        objectMeta.setName(\"cm-\" + UUID.randomUUID().toString().substring(0, 5));\n\n        configMap.setMetadata(objectMeta);\n\n        return kubernetesClient.configMaps().inNamespace(\"default\").resource(configMap).create();\n    }\n\n    private KubernetesAppDeployer kubernetesAppDeployer() {\n        return kubernetesAppDeployer(new KubernetesDeployerProperties());\n    }\n\n    private KubernetesAppDeployer kubernetesAppDeployer(KubernetesDeployerProperties kubernetesDeployerProperties) {\n        return new KubernetesAppDeployer(kubernetesDeployerProperties, this.kubernetesClient,\n                new DefaultContainerFactory(kubernetesDeployerProperties));\n    }\n}\n"
  },
  {
    "path": "spring-cloud-deployer-kubernetes/src/test/java/org/springframework/cloud/deployer/spi/kubernetes/KubernetesAppDeployerTests.java",
    "content": "/*\n * Copyright 2015-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.kubernetes;\n\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Properties;\nimport java.util.Set;\nimport java.util.stream.Collectors;\n\nimport io.fabric8.kubernetes.api.model.AffinityBuilder;\nimport io.fabric8.kubernetes.api.model.Capabilities;\nimport io.fabric8.kubernetes.api.model.ConfigMapEnvSourceBuilder;\nimport io.fabric8.kubernetes.api.model.ConfigMapKeySelector;\nimport io.fabric8.kubernetes.api.model.ConfigMapVolumeSource;\nimport io.fabric8.kubernetes.api.model.ConfigMapVolumeSourceBuilder;\nimport io.fabric8.kubernetes.api.model.Container;\nimport io.fabric8.kubernetes.api.model.EnvFromSource;\nimport io.fabric8.kubernetes.api.model.EnvVar;\nimport io.fabric8.kubernetes.api.model.EnvVarSourceBuilder;\nimport io.fabric8.kubernetes.api.model.HostPathVolumeSource;\nimport io.fabric8.kubernetes.api.model.HostPathVolumeSourceBuilder;\nimport io.fabric8.kubernetes.api.model.KeyToPath;\nimport io.fabric8.kubernetes.api.model.LabelSelector;\nimport io.fabric8.kubernetes.api.model.LabelSelectorRequirementBuilder;\nimport io.fabric8.kubernetes.api.model.NodeAffinity;\nimport io.fabric8.kubernetes.api.model.NodeSelectorRequirementBuilder;\nimport io.fabric8.kubernetes.api.model.NodeSelectorTerm;\nimport io.fabric8.kubernetes.api.model.ObjectFieldSelectorBuilder;\nimport io.fabric8.kubernetes.api.model.PodAffinity;\nimport io.fabric8.kubernetes.api.model.PodAffinityTerm;\nimport io.fabric8.kubernetes.api.model.PodAntiAffinity;\nimport io.fabric8.kubernetes.api.model.PodSecurityContext;\nimport io.fabric8.kubernetes.api.model.PodSecurityContextBuilder;\nimport io.fabric8.kubernetes.api.model.PodSpec;\nimport io.fabric8.kubernetes.api.model.PreferredSchedulingTerm;\nimport io.fabric8.kubernetes.api.model.SELinuxOptions;\nimport io.fabric8.kubernetes.api.model.SeccompProfile;\nimport io.fabric8.kubernetes.api.model.SecretEnvSourceBuilder;\nimport io.fabric8.kubernetes.api.model.SecretKeySelector;\nimport io.fabric8.kubernetes.api.model.SecurityContext;\nimport io.fabric8.kubernetes.api.model.SecurityContextBuilder;\nimport io.fabric8.kubernetes.api.model.Sysctl;\nimport io.fabric8.kubernetes.api.model.Toleration;\nimport io.fabric8.kubernetes.api.model.Volume;\nimport io.fabric8.kubernetes.api.model.VolumeBuilder;\nimport io.fabric8.kubernetes.api.model.WeightedPodAffinityTerm;\nimport io.fabric8.kubernetes.api.model.WindowsSecurityContextOptions;\nimport org.junit.jupiter.api.DisplayName;\nimport org.junit.jupiter.api.Nested;\nimport org.junit.jupiter.api.Test;\n\nimport org.springframework.beans.factory.config.YamlPropertiesFactoryBean;\nimport org.springframework.boot.context.properties.bind.Bindable;\nimport org.springframework.boot.context.properties.bind.Binder;\nimport org.springframework.boot.context.properties.source.MapConfigurationPropertySource;\nimport org.springframework.cloud.deployer.resource.docker.DockerResource;\nimport org.springframework.cloud.deployer.spi.core.AppDefinition;\nimport org.springframework.cloud.deployer.spi.core.AppDeploymentRequest;\nimport org.springframework.core.io.ClassPathResource;\nimport org.springframework.core.io.Resource;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.assertThatThrownBy;\nimport static org.assertj.core.api.Assertions.entry;\n\n/**\n * Unit tests for {@link KubernetesAppDeployer}\n *\n * @author Donovan Muller\n * @author David Turanski\n * @author Ilayaperumal Gopinathan\n * @author Chris Schaefer\n * @author Enrique Medina Montenegro\n * @author Chris Bono\n * @author Corneil du Plessis\n */\n@DisplayName(\"KubernetesAppDeployer\")\npublic class KubernetesAppDeployerTests {\n\n    private KubernetesAppDeployer deployer;\n\n    private DeploymentPropertiesResolver deploymentPropertiesResolver = new DeploymentPropertiesResolver(\n            KubernetesDeployerProperties.KUBERNETES_DEPLOYER_PROPERTIES_PREFIX, new KubernetesDeployerProperties());\n\n    @Test\n    public void deployWithVolumesOnly() throws Exception {\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(),\n                new HashMap<>());\n\n        deployer = k8sAppDeployer(bindDeployerProperties());\n        PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);\n\n        assertThat(podSpec.getVolumes()).isEmpty();\n    }\n\n    @Test\n    public void deployWithVolumesAndVolumeMounts() throws Exception {\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        Map<String, String> props = new HashMap<>();\n        props.put(\"spring.cloud.deployer.kubernetes.volumeMounts\", \"[\" + \"{name: 'testpvc', mountPath: '/test/pvc'}, \"\n                + \"{name: 'testnfs', mountPath: '/test/nfs', readOnly: 'true'}\" + \"]\");\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), props);\n\n        deployer = k8sAppDeployer();\n        PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);\n\n        assertThat(podSpec.getVolumes()).containsOnly(\n                // volume 'testhostpath' defined in dataflow-server.yml should not be added\n                // as there is no corresponding volume mount\n                new VolumeBuilder().withName(\"testpvc\").withNewPersistentVolumeClaim(\"testClaim\", true).build(),\n                new VolumeBuilder().withName(\"testnfs\").withNewNfs(\"/test/nfs\", null, \"10.0.0.1:111\").build());\n\n        props.clear();\n        props.put(\"spring.cloud.deployer.kubernetes.volumes\",\n                \"[\" + \"{name: testhostpath, hostPath: { path: '/test/override/hostPath' }},\"\n                        + \"{name: 'testnfs', nfs: { server: '192.168.1.1:111', path: '/test/override/nfs' }} \" + \"]\");\n        props.put(\"spring.cloud.deployer.kubernetes.volumeMounts\",\n                \"[\" + \"{name: 'testhostpath', mountPath: '/test/hostPath'}, \"\n                        + \"{name: 'testpvc', mountPath: '/test/pvc'}, \"\n                        + \"{name: 'testnfs', mountPath: '/test/nfs', readOnly: 'true'}\" + \"]\");\n        appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), props);\n\n        deployer = k8sAppDeployer();\n        podSpec = deployer.createPodSpec(appDeploymentRequest);\n\n        HostPathVolumeSource hostPathVolumeSource = new HostPathVolumeSourceBuilder()\n                .withPath(\"/test/override/hostPath\").build();\n\n        assertThat(podSpec.getVolumes()).containsOnly(\n                new VolumeBuilder().withName(\"testhostpath\").withHostPath(hostPathVolumeSource).build(),\n                new VolumeBuilder().withName(\"testpvc\").withNewPersistentVolumeClaim(\"testClaim\", true).build(),\n                new VolumeBuilder().withName(\"testnfs\").withNewNfs(\"/test/override/nfs\", null, \"192.168.1.1:111\").build());\n    }\n    @Test\n    public void deployWithVolumesAndVolumeMountsOnAdditionalContainer() throws Exception {\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        Map<String, String> props = new HashMap<>();\n        props.put(\"spring.cloud.deployer.kubernetes.volumes\", \"[{name: 'config', configMap: {name: promtail-config, items: [{key: promtail.yaml, path: promtail.yaml}]}},{name: 'config2', configMap: {name: promtail-config, items: [{key: promtail.yaml, path: promtail.yaml}]}}]\");\n        props.put(\"spring.cloud.deployer.kubernetes.additional-containers\", \"[{name: 'promtail',image: image-path-of-promtail, ports:[{protocol: TCP,containerPort: 8080}],args: [\\\"-config.file=/home/conf/promtail.yaml\\\"],volumeMounts: [{name: 'config', mountPath: '/home/conf'}]},{name: 'promtail2',image: image-path-of-promtail, ports:[{protocol: TCP,containerPort: 8080}],args: [\\\"-config.file=/home/conf/promtail.yaml\\\"],volumeMounts: [{name: 'config2', mountPath: '/home/conf'}]}]\");\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), props);\n\n        deployer = k8sAppDeployer();\n        PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);\n\n        ConfigMapVolumeSource configMapVolumeSource = new ConfigMapVolumeSourceBuilder()\n                .withName(\"promtail-config\")\n                .withItems(new KeyToPath(\"promtail.yaml\", null, \"promtail.yaml\"))\n                .build();\n        Volume volume = new VolumeBuilder().withName(\"config\").withNewConfigMapLike(configMapVolumeSource).endConfigMap().build();\n        Volume volume2 = new VolumeBuilder().withName(\"config2\").withNewConfigMapLike(configMapVolumeSource).endConfigMap().build();\n        assertThat(podSpec.getVolumes()).containsExactly(volume, volume2);\n    }\n    @Test\n    public void deployWithVolumesAndVolumeMountsOnAdditionalContainerAbsentVolumeMount() throws Exception {\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        Map<String, String> props = new HashMap<>();\n        props.put(\"spring.cloud.deployer.kubernetes.volumes\", \"[{name: 'config', configMap: {name: promtail-config, items: [{key: promtail.yaml, path: promtail.yaml}]}},{name: 'config2', configMap: {name: promtail-config, items: [{key: promtail.yaml, path: promtail.yaml}]}}]\");\n        // both containers reference config.\n        props.put(\"spring.cloud.deployer.kubernetes.additional-containers\", \"[{name: 'promtail',image: image-path-of-promtail, ports:[{protocol: TCP,containerPort: 8080}],args: [\\\"-config.file=/home/conf/promtail.yaml\\\"],volumeMounts: [{name: 'config', mountPath: '/home/conf'}]},{name: 'promtail2',image: image-path-of-promtail, ports:[{protocol: TCP,containerPort: 8080}],args: [\\\"-config.file=/home/conf/promtail.yaml\\\"],volumeMounts: [{name: 'config', mountPath: '/home/conf'}]}]\");\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), props);\n\n        deployer = k8sAppDeployer();\n        PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);\n\n        ConfigMapVolumeSource configMapVolumeSource = new ConfigMapVolumeSourceBuilder()\n                .withName(\"promtail-config\")\n                .withItems(new KeyToPath(\"promtail.yaml\", null, \"promtail.yaml\"))\n                .build();\n        Set<String> volumeNames = podSpec.getVolumes().stream().map(v -> v.getName()).collect(Collectors.toSet());\n        assertThat(volumeNames).doesNotContain(\"config2\");\n        Volume volume = new VolumeBuilder().withName(\"config\").withNewConfigMapLike(configMapVolumeSource).endConfigMap().build();\n        assertThat(podSpec.getVolumes()).containsExactly(volume);\n\n    }\n    @Test\n    public void deployWithNodeSelectorGlobalProperty() throws Exception {\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), null);\n\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n        kubernetesDeployerProperties.setNodeSelector(\"disktype:ssd, os:qnx\");\n\n        deployer = k8sAppDeployer(kubernetesDeployerProperties);\n        PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);\n\n        assertThat(podSpec.getNodeSelector()).containsOnly(entry(\"disktype\", \"ssd\"), entry(\"os\", \"qnx\"));\n    }\n\n    @Test\n    public void deployWithNodeSelectorDeploymentProperty() throws Exception {\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        Map<String, String> props = new HashMap<>();\n        props.put(KubernetesDeployerProperties.KUBERNETES_DEPLOYMENT_NODE_SELECTOR, \"disktype:ssd, os: linux\");\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), props);\n\n        deployer = k8sAppDeployer();\n        PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);\n\n        assertThat(podSpec.getNodeSelector()).containsOnly(entry(\"disktype\", \"ssd\"), entry(\"os\", \"linux\"));\n    }\n    @Test\n    public void deployWithNodeSelectorTrainTruckCaseProperty() throws Exception {\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        Map<String, String> props = new HashMap<>();\n        props.put(KubernetesDeployerProperties.KUBERNETES_DEPLOYER_PROPERTIES_PREFIX + \".node-selector\", \"os: linux\");\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), props);\n\n        deployer = k8sAppDeployer();\n        PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);\n\n        assertThat(podSpec.getNodeSelector()).containsOnly(entry(\"os\", \"linux\"));\n    }\n\n    @Test\n    public void deployWithNodeSelectorDeploymentPropertyGlobalOverride() throws Exception {\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        Map<String, String> props = new HashMap<>();\n        props.put(KubernetesDeployerProperties.KUBERNETES_DEPLOYMENT_NODE_SELECTOR, \"disktype:ssd, os: openbsd\");\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), props);\n\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n        kubernetesDeployerProperties.setNodeSelector(\"disktype:ssd, os:qnx\");\n\n        deployer = k8sAppDeployer(kubernetesDeployerProperties);\n        PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);\n\n        assertThat(podSpec.getNodeSelector()).containsOnly(entry(\"disktype\", \"ssd\"), entry(\"os\", \"openbsd\"));\n    }\n\n    @Test\n    public void deployWithEnvironmentWithCommaDelimitedValue() throws Exception {\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        Map<String, String> props = new HashMap<>();\n        props.put(\"spring.cloud.deployer.kubernetes.environmentVariables\",\n                \"JAVA_TOOL_OPTIONS='thing1,thing2',foo='bar,baz',car=caz,boo='zoo,gnu',doo=dar,OPTS='thing1'\");\n\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), props);\n\n        deployer = k8sAppDeployer();\n        PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);\n\n        assertThat(podSpec.getContainers().get(0).getEnv())\n                .contains(\n                        new EnvVar(\"foo\", \"bar,baz\", null),\n                        new EnvVar(\"car\", \"caz\", null),\n                        new EnvVar(\"boo\", \"zoo,gnu\", null),\n                        new EnvVar(\"doo\", \"dar\", null),\n                        new EnvVar(\"JAVA_TOOL_OPTIONS\", \"thing1,thing2\", null),\n                        new EnvVar(\"OPTS\", \"thing1\", null));\n    }\n\n    @Test\n    public void deployWithEnvironmentWithSingleCommaDelimitedValue() throws Exception {\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        Map<String, String> props = new HashMap<>();\n        props.put(\"spring.cloud.deployer.kubernetes.environmentVariables\",\n                \"JAVA_TOOL_OPTIONS='thing1,thing2'\");\n\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), props);\n\n        deployer = k8sAppDeployer();\n        PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);\n\n        assertThat(podSpec.getContainers().get(0).getEnv())\n                .contains(new EnvVar(\"JAVA_TOOL_OPTIONS\", \"thing1,thing2\", null));\n    }\n\n    @Test\n    public void deployWithEnvironmentWithMultipleCommaDelimitedValue() throws Exception {\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        Map<String, String> props = new HashMap<>();\n        props.put(\"spring.cloud.deployer.kubernetes.environmentVariables\",\n                \"JAVA_TOOL_OPTIONS='thing1,thing2',OPTS='thing3, thing4'\");\n\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), props);\n\n        deployer = k8sAppDeployer();\n        PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);\n\n        assertThat(podSpec.getContainers().get(0).getEnv())\n                .contains(\n                        new EnvVar(\"JAVA_TOOL_OPTIONS\", \"thing1,thing2\", null),\n                        new EnvVar(\"OPTS\", \"thing3, thing4\", null));\n    }\n\n    @Test\n    public void deployWithImagePullSecretDeploymentProperty() {\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n\n        Map<String, String> props = new HashMap<>();\n        props.put(\"spring.cloud.deployer.kubernetes.imagePullSecret\", \"regcred\");\n\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), props);\n\n        deployer = k8sAppDeployer(new KubernetesDeployerProperties());\n        PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);\n\n        assertThat(podSpec.getImagePullSecrets().size()).isEqualTo(1);\n        assertThat(podSpec.getImagePullSecrets().get(0).getName()).isEqualTo(\"regcred\");\n    }\n\n    @Test\n    public void deployWithImagePullSecretDeployerProperty() {\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), null);\n\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n        kubernetesDeployerProperties.setImagePullSecret(\"regcred\");\n\n        deployer = k8sAppDeployer(kubernetesDeployerProperties);\n        PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);\n\n        assertThat(podSpec.getImagePullSecrets().size()).isEqualTo(1);\n        assertThat(podSpec.getImagePullSecrets().get(0).getName()).isEqualTo(\"regcred\");\n    }\n\n    @Test\n    public void deployWithImagePullSecretsDeploymentProperty() {\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n\n        Map<String, String> props = new HashMap<>();\n        props.put(\"spring.cloud.deployer.kubernetes.imagePullSecrets\", \"['regcredone','regcredtwo']\");\n\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), props);\n\n        deployer = k8sAppDeployer(new KubernetesDeployerProperties());\n        PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);\n\n        assertThat(podSpec.getImagePullSecrets().size()).isEqualTo(2);\n        assertThat(podSpec.getImagePullSecrets().get(0).getName()).isEqualTo(\"regcredone\");\n        assertThat(podSpec.getImagePullSecrets().get(1).getName()).isEqualTo(\"regcredtwo\");\n    }\n\n    @Test\n    public void deployWithImagePullSecretsDeployerProperty() {\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), null);\n\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n        kubernetesDeployerProperties.setImagePullSecrets(Arrays.asList(\"regcredone\", \"regcredtwo\"));\n\n        deployer = k8sAppDeployer(kubernetesDeployerProperties);\n        PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);\n\n        assertThat(podSpec.getImagePullSecrets().size()).isEqualTo(2);\n        assertThat(podSpec.getImagePullSecrets().get(0).getName()).isEqualTo(\"regcredone\");\n        assertThat(podSpec.getImagePullSecrets().get(1).getName()).isEqualTo(\"regcredtwo\");\n    }\n\n    @Test\n    public void deployWithDeploymentServiceAccountNameDeploymentProperties() {\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n\n        Map<String, String> props = new HashMap<>();\n        props.put(\"spring.cloud.deployer.kubernetes.deploymentServiceAccountName\", \"myserviceaccount\");\n\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), props);\n\n        deployer = k8sAppDeployer(new KubernetesDeployerProperties());\n        PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);\n\n        assertThat(podSpec.getServiceAccountName()).isNotNull();\n        assertThat(podSpec.getServiceAccountName().equals(\"myserviceaccount\"));\n    }\n\n    @Test\n    public void deployWithDeploymentServiceAccountNameDeployerProperty() {\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), null);\n\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n        kubernetesDeployerProperties.setDeploymentServiceAccountName(\"myserviceaccount\");\n\n        deployer = k8sAppDeployer(kubernetesDeployerProperties);\n        PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);\n\n        assertThat(podSpec.getServiceAccountName()).isNotNull();\n        assertThat(podSpec.getServiceAccountName().equals(\"myserviceaccount\"));\n    }\n\n    @Test\n    public void deployWithDeploymentServiceAccountNameDeploymentPropertyOverride() {\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n\n        Map<String, String> props = new HashMap<>();\n        props.put(\"spring.cloud.deployer.kubernetes.deploymentServiceAccountName\", \"overridesan\");\n\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), props);\n\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n        kubernetesDeployerProperties.setDeploymentServiceAccountName(\"defaultsan\");\n\n        deployer = k8sAppDeployer(kubernetesDeployerProperties);\n        PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);\n\n        assertThat(podSpec.getServiceAccountName()).isNotNull();\n        assertThat(podSpec.getServiceAccountName().equals(\"overridesan\"));\n    }\n\n    @Test\n    public void deployWithTolerations() throws Exception {\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(),\n                new HashMap<>());\n\n        deployer = k8sAppDeployer();\n        PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);\n\n        assertThat(podSpec.getTolerations()).isNotEmpty();\n    }\n\n    @Test\n    public void deployWithGlobalTolerations() {\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n\n        Map<String, String> props = new HashMap<>();\n        props.put(\"spring.cloud.deployer.kubernetes.tolerations\",\n                \"[{key: 'test', value: 'true', operator: 'Equal', effect: 'NoSchedule', tolerationSeconds: 5}, \"\n                        + \"{key: 'test2', value: 'false', operator: 'Equal', effect: 'NoSchedule', tolerationSeconds: 5}]\");\n\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), props);\n\n        deployer = k8sAppDeployer(new KubernetesDeployerProperties());\n        PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);\n\n        assertThat(podSpec.getTolerations()).isNotNull();\n        assertThat(podSpec.getTolerations().size() == 2);\n        assertThat(podSpec.getTolerations().contains(new Toleration(\"NoSchedule\", \"test\", \"Equal\", 5L, \"true\")));\n        assertThat(podSpec.getTolerations().contains(new Toleration(\"NoSchedule\", \"test2\", \"Equal\", 5L, \"false\")));\n    }\n\n    @Test\n    public void deployWithTolerationPropertyOverride() {\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n\n        Map<String, String> props = new HashMap<>();\n        props.put(\"spring.cloud.deployer.kubernetes.tolerations\",\n                \"[{key: 'test', value: 'true', operator: 'Equal', effect: 'NoSchedule', tolerationSeconds: 5}, \"\n                        + \"{key: 'test2', value: 'false', operator: 'Equal', effect: 'NoSchedule', tolerationSeconds: 5}]\");\n\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), props);\n\n        KubernetesDeployerProperties.Toleration toleration = new KubernetesDeployerProperties.Toleration();\n        toleration.setEffect(\"NoSchedule\");\n        toleration.setKey(\"test\");\n        toleration.setOperator(\"Equal\");\n        toleration.setTolerationSeconds(5L);\n        toleration.setValue(\"false\");\n\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n        kubernetesDeployerProperties.getTolerations().add(toleration);\n\n        deployer = k8sAppDeployer(kubernetesDeployerProperties);\n        PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);\n\n        assertThat(podSpec.getTolerations()).isNotNull();\n        assertThat(podSpec.getTolerations().size() == 2);\n        assertThat(podSpec.getTolerations().contains(new Toleration(\"NoSchedule\", \"test\", \"Equal\", 5L, \"true\")));\n        assertThat(podSpec.getTolerations().contains(new Toleration(\"NoSchedule\", \"test2\", \"Equal\", 5L, \"false\")));\n    }\n\n    @Test\n    public void deployWithDuplicateTolerationKeyPropertyOverride() {\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n\n        Map<String, String> props = new HashMap<>();\n        props.put(\"spring.cloud.deployer.kubernetes.tolerations\",\n                \"[{key: 'test', value: 'true', operator: 'Equal', effect: 'NoSchedule', tolerationSeconds: 5}]\");\n\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), props);\n\n        KubernetesDeployerProperties.Toleration toleration = new KubernetesDeployerProperties.Toleration();\n        toleration.setEffect(\"NoSchedule\");\n        toleration.setKey(\"test\");\n        toleration.setOperator(\"Equal\");\n        toleration.setTolerationSeconds(5L);\n        toleration.setValue(\"false\");\n\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n        kubernetesDeployerProperties.getTolerations().add(toleration);\n        kubernetesDeployerProperties.setStartupHttpProbePort(kubernetesDeployerProperties.getLivenessHttpProbePort());\n        deployer = k8sAppDeployer(kubernetesDeployerProperties);\n        PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);\n\n        assertThat(podSpec.getTolerations()).isNotNull();\n        assertThat(podSpec.getTolerations().size() == 1);\n        assertThat(podSpec.getTolerations().contains(new Toleration(\"NoSchedule\", \"test2\", \"Equal\", 5L, \"false\")));\n    }\n\n    @Test\n    public void deployWithDuplicateGlobalToleration() {\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), null);\n\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n\n        KubernetesDeployerProperties.Toleration toleration1 = new KubernetesDeployerProperties.Toleration();\n        toleration1.setEffect(\"NoSchedule\");\n        toleration1.setKey(\"test\");\n        toleration1.setOperator(\"Equal\");\n        toleration1.setTolerationSeconds(5L);\n        toleration1.setValue(\"false\");\n\n        kubernetesDeployerProperties.getTolerations().add(toleration1);\n\n        KubernetesDeployerProperties.Toleration toleration2 = new KubernetesDeployerProperties.Toleration();\n        toleration2.setEffect(\"NoSchedule\");\n        toleration2.setKey(\"test\");\n        toleration2.setOperator(\"Equal\");\n        toleration2.setTolerationSeconds(5L);\n        toleration2.setValue(\"true\");\n\n        kubernetesDeployerProperties.getTolerations().add(toleration2);\n\n        deployer = k8sAppDeployer(kubernetesDeployerProperties);\n        PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);\n\n        assertThat(podSpec.getTolerations()).isNotNull();\n        assertThat(podSpec.getTolerations().size() == 1);\n        assertThat(podSpec.getTolerations().contains(new Toleration(\"NoSchedule\", \"test2\", \"Equal\", 5L, \"true\")));\n    }\n\n    @Test\n    public void testInvalidDeploymentLabelDelimiter() {\n        Map<String, String> props = Collections.singletonMap(\"spring.cloud.deployer.kubernetes.deploymentLabels\",\n                \"label1|value1\");\n\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), props);\n\n        assertThatThrownBy(() -> {\n            this.deploymentPropertiesResolver.getDeploymentLabels(appDeploymentRequest.getDeploymentProperties());\n        }).isInstanceOf(IllegalArgumentException.class);\n    }\n\n    @Test\n    public void testInvalidMultipleDeploymentLabelDelimiter() {\n        Map<String, String> props = Collections.singletonMap(\"spring.cloud.deployer.kubernetes.deploymentLabels\",\n                \"label1:value1 label2:value2\");\n\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), props);\n\n        assertThatThrownBy(() -> {\n            this.deploymentPropertiesResolver.getDeploymentLabels(appDeploymentRequest.getDeploymentProperties());\n        }).isInstanceOf(IllegalArgumentException.class);\n    }\n\n    @Test\n    public void testDeploymentLabels() {\n        Map<String, String> props = Collections.singletonMap(\"spring.cloud.deployer.kubernetes.deploymentLabels\",\n                \"label1:value1,label2:value2\");\n\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), props);\n\n        Map<String, String> deploymentLabels = this.deploymentPropertiesResolver.getDeploymentLabels(appDeploymentRequest.getDeploymentProperties());\n\n        assertThat(deploymentLabels).isNotEmpty();\n        assertThat(deploymentLabels.size()).as(\"Invalid number of labels\").isEqualTo(2);\n        assertThat(deploymentLabels).containsKey(\"label1\");\n        assertThat(deploymentLabels.get(\"label1\")).as(\"Invalid value for 'label1'\").isEqualTo(\"value1\");\n        assertThat(deploymentLabels).containsKey(\"label2\");\n        assertThat(deploymentLabels.get(\"label2\")).as(\"Invalid value for 'label2'\").isEqualTo(\"value2\");\n    }\n\n    @Test\n    public void testSecretKeyRef() {\n        Map<String, String> props = new HashMap<>();\n        props.put(\"spring.cloud.deployer.kubernetes.secretKeyRefs\",\n                \"[{envVarName: 'SECRET_PASSWORD', secretName: 'mySecret', dataKey: 'password'}]\");\n\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), props);\n\n        deployer = k8sAppDeployer(new KubernetesDeployerProperties());\n        PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);\n\n        List<EnvVar> envVars = podSpec.getContainers().get(0).getEnv();\n\n        assertThat(envVars.size()).as(\"Invalid number of env vars\").isEqualTo(2);\n\n        EnvVar secretKeyRefEnvVar = envVars.get(0);\n        assertThat(secretKeyRefEnvVar.getName()).as(\"Unexpected env var name\").isEqualTo(\"SECRET_PASSWORD\");\n        SecretKeySelector secretKeySelector = secretKeyRefEnvVar.getValueFrom().getSecretKeyRef();\n        assertThat(secretKeySelector.getName()).as(\"Unexpected secret name\").isEqualTo(\"mySecret\");\n        assertThat(secretKeySelector.getKey()).as(\"Unexpected secret data key\").isEqualTo(\"password\");\n    }\n\n    @Test\n    public void testSecretKeyRefMultiple() {\n        Map<String, String> props = new HashMap<>();\n        props.put(\"spring.cloud.deployer.kubernetes.secretKeyRefs\",\n                \"[{envVarName: 'SECRET_PASSWORD', secretName: 'mySecret', dataKey: 'password'},\" +\n                        \"{envVarName: 'SECRET_USERNAME', secretName: 'mySecret2', dataKey: 'username'}]\");\n\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), props);\n\n        deployer = k8sAppDeployer(new KubernetesDeployerProperties());\n        PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);\n\n        List<EnvVar> envVars = podSpec.getContainers().get(0).getEnv();\n\n        assertThat(envVars.size()).as(\"Invalid number of env vars\").isEqualTo(3);\n\n        EnvVar secretKeyRefEnvVar = envVars.get(0);\n        assertThat(secretKeyRefEnvVar.getName()).as(\"Unexpected env var name\").isEqualTo(\"SECRET_PASSWORD\");\n        SecretKeySelector secretKeySelector = secretKeyRefEnvVar.getValueFrom().getSecretKeyRef();\n        assertThat(secretKeySelector.getName()).as(\"Unexpected secret name\").isEqualTo(\"mySecret\");\n        assertThat(secretKeySelector.getKey()).as(\"Unexpected secret data key\").isEqualTo(\"password\");\n\n        secretKeyRefEnvVar = envVars.get(1);\n        assertThat(secretKeyRefEnvVar.getName()).as(\"Unexpected env var name\").isEqualTo(\"SECRET_USERNAME\");\n        secretKeySelector = secretKeyRefEnvVar.getValueFrom().getSecretKeyRef();\n        assertThat(secretKeySelector.getName()).as(\"Unexpected secret name\").isEqualTo(\"mySecret2\");\n        assertThat(secretKeySelector.getKey()).as(\"Unexpected secret data key\").isEqualTo(\"username\");\n    }\n\n    @Test\n    public void testSecretKeyRefGlobal() {\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), null);\n\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n        KubernetesDeployerProperties.SecretKeyRef secretKeyRef = new KubernetesDeployerProperties.SecretKeyRef();\n        secretKeyRef.setEnvVarName(\"SECRET_PASSWORD_GLOBAL\");\n        secretKeyRef.setSecretName(\"mySecretGlobal\");\n        secretKeyRef.setDataKey(\"passwordGlobal\");\n        kubernetesDeployerProperties.setSecretKeyRefs(Collections.singletonList(secretKeyRef));\n\n        deployer = k8sAppDeployer(kubernetesDeployerProperties);\n        PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);\n\n        List<EnvVar> envVars = podSpec.getContainers().get(0).getEnv();\n\n        assertThat(envVars.size()).as(\"Invalid number of env vars\").isEqualTo(2);\n\n        EnvVar secretKeyRefEnvVar = envVars.get(0);\n        assertThat(secretKeyRefEnvVar.getName()).as(\"Unexpected env var name\").isEqualTo(\"SECRET_PASSWORD_GLOBAL\");\n        SecretKeySelector secretKeySelector = secretKeyRefEnvVar.getValueFrom().getSecretKeyRef();\n        assertThat(secretKeySelector.getName()).as(\"Unexpected secret name\").isEqualTo(\"mySecretGlobal\");\n        assertThat(secretKeySelector.getKey()).as(\"Unexpected secret data key\").isEqualTo(\"passwordGlobal\");\n    }\n\n    @Test\n    public void testSecretKeyRefPropertyOverride() {\n        Map<String, String> props = new HashMap<>();\n        props.put(\"spring.cloud.deployer.kubernetes.secretKeyRefs\",\n                \"[{envVarName: 'SECRET_PASSWORD_GLOBAL', secretName: 'mySecret', dataKey: 'password'},\" +\n                        \"{envVarName: 'SECRET_USERNAME', secretName: 'mySecret2', dataKey: 'username'}]\");\n\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), props);\n\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n\n        List<KubernetesDeployerProperties.SecretKeyRef> globalSecretKeyRefs = new ArrayList<>();\n        KubernetesDeployerProperties.SecretKeyRef globalSecretKeyRef1 = new KubernetesDeployerProperties.SecretKeyRef();\n        globalSecretKeyRef1.setEnvVarName(\"SECRET_PASSWORD_GLOBAL\");\n        globalSecretKeyRef1.setSecretName(\"mySecretGlobal\");\n        globalSecretKeyRef1.setDataKey(\"passwordGlobal\");\n\n        KubernetesDeployerProperties.SecretKeyRef globalSecretKeyRef2 = new KubernetesDeployerProperties.SecretKeyRef();\n        globalSecretKeyRef2.setEnvVarName(\"SECRET_USERNAME_GLOBAL\");\n        globalSecretKeyRef2.setSecretName(\"mySecretGlobal\");\n        globalSecretKeyRef2.setDataKey(\"usernameGlobal\");\n\n        globalSecretKeyRefs.add(globalSecretKeyRef1);\n        globalSecretKeyRefs.add(globalSecretKeyRef2);\n\n        kubernetesDeployerProperties.setSecretKeyRefs(globalSecretKeyRefs);\n\n        deployer = k8sAppDeployer(kubernetesDeployerProperties);\n        PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);\n\n        List<EnvVar> envVars = podSpec.getContainers().get(0).getEnv();\n\n        assertThat(envVars.size()).as(\"Invalid number of env vars\").isEqualTo(4);\n\n        // deploy prop overrides global\n        EnvVar secretKeyRefEnvVar = envVars.get(0);\n        assertThat(secretKeyRefEnvVar.getName()).as(\"Unexpected env var name\").isEqualTo(\"SECRET_PASSWORD_GLOBAL\");\n        SecretKeySelector secretKeySelector = secretKeyRefEnvVar.getValueFrom().getSecretKeyRef();\n        assertThat(secretKeySelector.getName()).as(\"Unexpected secret name\").isEqualTo(\"mySecret\");\n        assertThat(secretKeySelector.getKey()).as(\"Unexpected secret data key\").isEqualTo(\"password\");\n\n        // unique deploy prop\n        secretKeyRefEnvVar = envVars.get(1);\n        assertThat(secretKeyRefEnvVar.getName()).as(\"Unexpected env var name\").isEqualTo(\"SECRET_USERNAME\");\n        secretKeySelector = secretKeyRefEnvVar.getValueFrom().getSecretKeyRef();\n        assertThat(secretKeySelector.getName()).as(\"Unexpected secret name\").isEqualTo(\"mySecret2\");\n        assertThat(secretKeySelector.getKey()).as(\"Unexpected secret data key\").isEqualTo(\"username\");\n\n        // unique, non-overridden global prop\n        secretKeyRefEnvVar = envVars.get(2);\n        assertThat(secretKeyRefEnvVar.getName()).as(\"Unexpected env var name\").isEqualTo(\"SECRET_USERNAME_GLOBAL\");\n        secretKeySelector = secretKeyRefEnvVar.getValueFrom().getSecretKeyRef();\n        assertThat(secretKeySelector.getName()).as(\"Unexpected secret name\").isEqualTo(\"mySecretGlobal\");\n        assertThat(secretKeySelector.getKey()).as(\"Unexpected secret data key\").isEqualTo(\"usernameGlobal\");\n    }\n\n    @Test\n    public void testSecretKeyRefGlobalFromYaml() throws Exception {\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), null);\n\n        deployer = k8sAppDeployer();\n        PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);\n\n        List<EnvVar> envVars = podSpec.getContainers().get(0).getEnv();\n\n        assertThat(envVars.size()).as(\"Invalid number of env vars\").isEqualTo(3);\n\n        EnvVar secretKeyRefEnvVar = envVars.get(0);\n        assertThat(secretKeyRefEnvVar.getName()).as(\"Unexpected env var name\").isEqualTo(\"SECRET_PASSWORD\");\n        SecretKeySelector secretKeySelector = secretKeyRefEnvVar.getValueFrom().getSecretKeyRef();\n        assertThat(secretKeySelector.getName()).as(\"Unexpected secret name\").isEqualTo(\"mySecret\");\n        assertThat(secretKeySelector.getKey()).as(\"Unexpected secret data key\").isEqualTo(\"myPassword\");\n    }\n\n    @Test\n    public void testConfigMapKeyRef() {\n        Map<String, String> props = new HashMap<>();\n        props.put(\"spring.cloud.deployer.kubernetes.configMapKeyRefs\",\n                \"[{envVarName: 'MY_ENV', configMapName: 'myConfigMap', dataKey: 'envName'}]\");\n\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), props);\n\n        deployer = k8sAppDeployer(new KubernetesDeployerProperties());\n        PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);\n\n        List<EnvVar> envVars = podSpec.getContainers().get(0).getEnv();\n\n        assertThat(envVars.size()).as(\"Invalid number of env vars\").isEqualTo(2);\n\n        EnvVar configMapKeyRefEnvVar = envVars.get(0);\n        assertThat(configMapKeyRefEnvVar.getName()).as(\"Unexpected env var name\").isEqualTo(\"MY_ENV\");\n        ConfigMapKeySelector configMapKeySelector = configMapKeyRefEnvVar.getValueFrom().getConfigMapKeyRef();\n        assertThat(configMapKeySelector.getName()).as(\"Unexpected config map name\").isEqualTo(\"myConfigMap\");\n        assertThat(configMapKeySelector.getKey()).as(\"Unexpected config map data key\").isEqualTo(\"envName\");\n    }\n\n    @Test\n    public void testConfigMapKeyRefMultiple() {\n        Map<String, String> props = new HashMap<>();\n        props.put(\"spring.cloud.deployer.kubernetes.configMapKeyRefs\",\n                \"[{envVarName: 'MY_ENV', configMapName: 'myConfigMap', dataKey: 'envName'},\" +\n                        \"{envVarName: 'ENV_VALUES', configMapName: 'myOtherConfigMap', dataKey: 'diskType'}]\");\n\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), props);\n\n        deployer = k8sAppDeployer(new KubernetesDeployerProperties());\n        PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);\n\n        List<EnvVar> envVars = podSpec.getContainers().get(0).getEnv();\n\n        assertThat(envVars.size()).as(\"Invalid number of env vars\").isEqualTo(3);\n\n        EnvVar configMapKeyRefEnvVar = envVars.get(0);\n        assertThat(configMapKeyRefEnvVar.getName()).as(\"Unexpected env var name\").isEqualTo(\"MY_ENV\");\n        ConfigMapKeySelector configMapKeySelector = configMapKeyRefEnvVar.getValueFrom().getConfigMapKeyRef();\n        assertThat(configMapKeySelector.getName()).as(\"Unexpected config map name\").isEqualTo(\"myConfigMap\");\n        assertThat(configMapKeySelector.getKey()).as(\"Unexpected config map data key\").isEqualTo(\"envName\");\n\n        configMapKeyRefEnvVar = envVars.get(1);\n        assertThat(configMapKeyRefEnvVar.getName()).as(\"Unexpected env var name\").isEqualTo(\"ENV_VALUES\");\n        configMapKeySelector = configMapKeyRefEnvVar.getValueFrom().getConfigMapKeyRef();\n        assertThat(configMapKeySelector.getName()).as(\"Unexpected config map name\").isEqualTo(\"myOtherConfigMap\");\n        assertThat(configMapKeySelector.getKey()).as(\"Unexpected config map data key\").isEqualTo(\"diskType\");\n    }\n\n    @Test\n    public void testConfigMapKeyRefGlobal() {\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), null);\n\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n        KubernetesDeployerProperties.ConfigMapKeyRef configMapKeyRef = new KubernetesDeployerProperties.ConfigMapKeyRef();\n        configMapKeyRef.setEnvVarName(\"MY_ENV_GLOBAL\");\n        configMapKeyRef.setConfigMapName(\"myConfigMapGlobal\");\n        configMapKeyRef.setDataKey(\"envGlobal\");\n        kubernetesDeployerProperties.setConfigMapKeyRefs(Collections.singletonList(configMapKeyRef));\n\n        deployer = k8sAppDeployer(kubernetesDeployerProperties);\n        PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);\n\n        List<EnvVar> envVars = podSpec.getContainers().get(0).getEnv();\n\n        assertThat(envVars.size()).as(\"Invalid number of env vars\").isEqualTo(2);\n\n        EnvVar configMapKeyRefEnvVar = envVars.get(0);\n        assertThat(configMapKeyRefEnvVar.getName()).as(\"Unexpected env var name\").isEqualTo(\"MY_ENV_GLOBAL\");\n        ConfigMapKeySelector configMapKeySelector = configMapKeyRefEnvVar.getValueFrom().getConfigMapKeyRef();\n        assertThat(configMapKeySelector.getName()).as(\"Unexpected config map name\").isEqualTo(\"myConfigMapGlobal\");\n        assertThat(configMapKeySelector.getKey()).as(\"Unexpected config data key\").isEqualTo(\"envGlobal\");\n    }\n\n    @Test\n    public void testConfigMapKeyRefPropertyOverride() {\n        Map<String, String> props = new HashMap<>();\n        props.put(\"spring.cloud.deployer.kubernetes.configMapKeyRefs\",\n                \"[{envVarName: 'MY_ENV', configMapName: 'myConfigMap', dataKey: 'envName'},\" +\n                        \"{envVarName: 'ENV_VALUES', configMapName: 'myOtherConfigMap', dataKey: 'diskType'}]\");\n\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), props);\n\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n\n        List<KubernetesDeployerProperties.ConfigMapKeyRef> globalConfigMapKeyRefs = new ArrayList<>();\n        KubernetesDeployerProperties.ConfigMapKeyRef globalConfigMapKeyRef1 = new KubernetesDeployerProperties.ConfigMapKeyRef();\n        globalConfigMapKeyRef1.setEnvVarName(\"MY_ENV\");\n        globalConfigMapKeyRef1.setConfigMapName(\"myEnvGlobal\");\n        globalConfigMapKeyRef1.setDataKey(\"envGlobal\");\n\n        KubernetesDeployerProperties.ConfigMapKeyRef globalConfigMapKeyRef2 = new KubernetesDeployerProperties.ConfigMapKeyRef();\n        globalConfigMapKeyRef2.setEnvVarName(\"MY_VALS_GLOBAL\");\n        globalConfigMapKeyRef2.setConfigMapName(\"myValsGlobal\");\n        globalConfigMapKeyRef2.setDataKey(\"valsGlobal\");\n\n        globalConfigMapKeyRefs.add(globalConfigMapKeyRef1);\n        globalConfigMapKeyRefs.add(globalConfigMapKeyRef2);\n\n        kubernetesDeployerProperties.setConfigMapKeyRefs(globalConfigMapKeyRefs);\n\n        deployer = k8sAppDeployer(kubernetesDeployerProperties);\n        PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);\n\n        List<EnvVar> envVars = podSpec.getContainers().get(0).getEnv();\n\n        assertThat(envVars.size()).as(\"Invalid number of env vars\").isEqualTo(4);\n\n        // deploy prop overrides global\n        EnvVar configMapKeyRefEnvVar = envVars.get(0);\n        assertThat(configMapKeyRefEnvVar.getName()).as(\"Unexpected env var name\").isEqualTo(\"MY_ENV\");\n        ConfigMapKeySelector configMapKeySelector = configMapKeyRefEnvVar.getValueFrom().getConfigMapKeyRef();\n        assertThat(configMapKeySelector.getName()).as(\"Unexpected config map name\").isEqualTo(\"myConfigMap\");\n        assertThat(configMapKeySelector.getKey()).as(\"Unexpected config map data key\").isEqualTo(\"envName\");\n\n        // unique deploy prop\n        configMapKeyRefEnvVar = envVars.get(1);\n        assertThat(configMapKeyRefEnvVar.getName()).as(\"Unexpected env var name\").isEqualTo(\"ENV_VALUES\");\n        configMapKeySelector = configMapKeyRefEnvVar.getValueFrom().getConfigMapKeyRef();\n        assertThat(configMapKeySelector.getName()).as(\"Unexpected config map name\").isEqualTo(\"myOtherConfigMap\");\n        assertThat(configMapKeySelector.getKey()).as(\"Unexpected config map data key\").isEqualTo(\"diskType\");\n\n        // unique, non-overridden global prop\n        configMapKeyRefEnvVar = envVars.get(2);\n        assertThat(configMapKeyRefEnvVar.getName()).as(\"Unexpected env var name\").isEqualTo(\"MY_VALS_GLOBAL\");\n        configMapKeySelector = configMapKeyRefEnvVar.getValueFrom().getConfigMapKeyRef();\n        assertThat(configMapKeySelector.getName()).as(\"Unexpected config map name\").isEqualTo(\"myValsGlobal\");\n        assertThat(configMapKeySelector.getKey()).as(\"Unexpected config map data key\").isEqualTo(\"valsGlobal\");\n    }\n\n    @Test\n    public void testConfigMapKeyRefGlobalFromYaml() throws Exception {\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), null);\n\n        deployer = k8sAppDeployer();\n        PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);\n\n        List<EnvVar> envVars = podSpec.getContainers().get(0).getEnv();\n\n        assertThat(envVars.size()).as(\"Invalid number of env vars\").isEqualTo(3);\n\n        EnvVar configMapKeyRefEnvVar = envVars.get(1);\n        assertThat(configMapKeyRefEnvVar.getName()).as(\"Unexpected env var name\").isEqualTo(\"MY_ENV\");\n        ConfigMapKeySelector configMapKeySelector = configMapKeyRefEnvVar.getValueFrom().getConfigMapKeyRef();\n        assertThat(configMapKeySelector.getName()).as(\"Unexpected config map name\").isEqualTo(\"myConfigMap\");\n        assertThat(configMapKeySelector.getKey()).as(\"Unexpected config map data key\").isEqualTo(\"envName\");\n    }\n    @Test\n    public void testInitContainerProperties() {\n        Map<String, String> props = new HashMap<>();\n        props.put(\"spring.cloud.deployer.kubernetes.initContainer\", \"{ \\\"imageName\\\": \\\"busybox:1\\\", \\\"containerName\\\": \\\"bb_s1\\\", \\\"commands\\\": [\\\"sh\\\", \\\"-c\\\", \\\"script1.sh\\\"] }\");\n\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), props);\n\n        deployer = k8sAppDeployer(new KubernetesDeployerProperties());\n        PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);\n        assertThat(podSpec.getInitContainers()).isNotEmpty();\n        Container container = podSpec.getInitContainers().get(0);\n        assertThat(container.getImage()).isEqualTo(\"busybox:1\");\n        assertThat(container.getName()).isEqualTo(\"bb_s1\");\n        assertThat(container.getCommand()).containsExactly(\"sh\", \"-c\", \"script1.sh\");\n    }\n    @Test\n    public void testInitContainerJsonArrayProperties() {\n        Map<String, String> props = new HashMap<>();\n        props.put(\"spring.cloud.deployer.kubernetes.init-containers\", \"[{ \\\"imageName\\\": \\\"busybox:1\\\", \\\"containerName\\\": \\\"bb_s1\\\", \\\"commands\\\": [\\\"sh\\\", \\\"-c\\\", \\\"script1.sh\\\"] }]\");\n\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), props);\n\n        deployer = k8sAppDeployer(new KubernetesDeployerProperties());\n        PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);\n        assertThat(podSpec.getInitContainers()).isNotEmpty();\n        Container container = podSpec.getInitContainers().get(0);\n        assertThat(container.getImage()).isEqualTo(\"busybox:1\");\n        assertThat(container.getName()).isEqualTo(\"bb_s1\");\n        assertThat(container.getCommand()).containsExactly(\"sh\", \"-c\", \"script1.sh\");\n    }\n\n    @Test\n    public void testInitContainerEnvironmentVariables() {\n        Map<String, String> props = new HashMap<>();\n        props.put(\"spring.cloud.deployer.kubernetes.initContainers[0]\", \"{ \\\"imageName\\\": \\\"busybox:1\\\", \\\"environmentVariablesFromFieldRefs\\\": [\\\"POD_UID=metadata.uid\\\"] }\");\n        props.put(\"spring.cloud.deployer.kubernetes.initContainers[1]\", \"{ \\\"imageName\\\": \\\"busybox:2\\\", \\\"configMapRefEnvVars\\\": [\\\"myConfigMap\\\",\\\"theirMap\\\"], \\\"secretRefEnvVars\\\": [\\\"mySecret\\\"] }\");\n\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), props);\n\n        deployer = k8sAppDeployer(new KubernetesDeployerProperties());\n        PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);\n        assertThat(podSpec.getInitContainers()).isNotEmpty();\n        assertThat(podSpec.getInitContainers().size()).isEqualTo(2);\n        Container container0 = podSpec.getInitContainers().get(0);\n        assertThat(container0.getImage()).isEqualTo(\"busybox:1\");\n        assertThat(container0.getEnv().get(0).getName()).isEqualTo(\"POD_UID\");\n        assertThat(container0.getEnv().get(0).getValueFrom().getFieldRef().getFieldPath()).isEqualTo(\"metadata.uid\");\n        Container container1 = podSpec.getInitContainers().get(1);\n        assertThat(container1.getImage()).isEqualTo(\"busybox:2\");\n        assertThat(container1.getEnvFrom().get(0).getConfigMapRef().getName()).isEqualTo(\"myConfigMap\");\n\t\tassertThat(container1.getEnvFrom().get(1).getConfigMapRef().getName()).isEqualTo(\"theirMap\");\n        assertThat(container1.getEnvFrom().get(2).getSecretRef().getName()).isEqualTo(\"mySecret\");\n    }\n\n    @Test\n    public void testMultipleInitContainerProperties() {\n        Map<String, String> props = new HashMap<>();\n        props.put(\"spring.cloud.deployer.kubernetes.initContainers[0]\", \"{ \\\"imageName\\\": \\\"busybox:1\\\", \\\"containerName\\\": \\\"bb_s1\\\", \\\"commands\\\": [\\\"sh\\\", \\\"-c\\\", \\\"script1.sh\\\"] }\");\n        props.put(\"spring.cloud.deployer.kubernetes.initContainers[1].imageName\", \"busybox:2\");\n        props.put(\"spring.cloud.deployer.kubernetes.initContainers[1].containerName\", \"bb_s2\");\n        props.put(\"spring.cloud.deployer.kubernetes.initContainers[1].commands\", \"sh,-c,script2.sh\");\n\t\tprops.put(\"spring.cloud.deployer.kubernetes.initContainers[1].environmentVariables\", \"foo=baz\");\n\n\t\tprops.put(\"spring.cloud.deployer.kubernetes.initContainers[2]\", \"{ \\\"image\\\": \\\"busybox:3\\\", \\\"name\\\": \\\"bb_s3\\\", \\\"args\\\": [\\\"-c\\\", \\\"script3.sh\\\"], \\\"volumeMounts\\\": [{\\\"mountPath\\\": \\\"/data\\\", \\\"name\\\": \\\"s3vol\\\", \\\"readOnly\\\": true}] }\");\n\t\tprops.put(\"spring.cloud.deployer.kubernetes.initContainers[3].image\", \"busybox:2\");\n\t\tprops.put(\"spring.cloud.deployer.kubernetes.initContainers[3].name\", \"bb_s4\");\n\t\tprops.put(\"spring.cloud.deployer.kubernetes.initContainers[3].command\", \"sh,-c,script4.sh\");\n\t\tprops.put(\"spring.cloud.deployer.kubernetes.initContainers[3].env\", \"foo=bar\");\n\n\t\tAppDefinition definition = new AppDefinition(\"app-test\", null);\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), props);\n\n        deployer = k8sAppDeployer(new KubernetesDeployerProperties());\n        PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);\n        assertThat(podSpec.getInitContainers()).isNotEmpty();\n        assertThat(podSpec.getInitContainers().size()).isEqualTo(4);\n        Container container0 = podSpec.getInitContainers().get(0);\n        assertThat(container0.getImage()).isEqualTo(\"busybox:1\");\n        assertThat(container0.getName()).isEqualTo(\"bb_s1\");\n        assertThat(container0.getCommand()).containsExactly(\"sh\", \"-c\", \"script1.sh\");\n        Container container1 = podSpec.getInitContainers().get(1);\n        assertThat(container1.getImage()).isEqualTo(\"busybox:2\");\n        assertThat(container1.getName()).isEqualTo(\"bb_s2\");\n        assertThat(container1.getCommand()).containsExactly(\"sh\", \"-c\", \"script2.sh\");\n\t\tassertThat(container1.getEnv()).isEqualTo(Collections.singletonList(new EnvVar(\"foo\",\"baz\", null)));\n\t\tContainer container2 = podSpec.getInitContainers().get(2);\n        assertThat(container2.getImage()).isEqualTo(\"busybox:3\");\n        assertThat(container2.getName()).isEqualTo(\"bb_s3\");\n        assertThat(container2.getArgs()).containsExactly(\"-c\", \"script3.sh\");\n        assertThat(container2.getVolumeMounts()).isNotEmpty();\n        assertThat(container2.getVolumeMounts().get(0).getName()).isEqualTo(\"s3vol\");\n        assertThat(container2.getVolumeMounts().get(0).getMountPath()).isEqualTo(\"/data\");\n        assertThat(container2.getVolumeMounts().get(0).getReadOnly()).isTrue();\n\t\tContainer container3 = podSpec.getInitContainers().get(3);\n\t\tassertThat(container3.getImage()).isEqualTo(\"busybox:2\");\n\t\tassertThat(container3.getName()).isEqualTo(\"bb_s4\");\n\t\tassertThat(container3.getCommand()).containsExactly(\"sh\", \"-c\", \"script4.sh\");\n\t\tassertThat(container3.getEnv()).isEqualTo(Collections.singletonList(new EnvVar(\"foo\",\"bar\", null)));\n\t}\n\n    @Test\n    public void testNodeAffinityProperty() {\n        Map<String, String> props = new HashMap<>();\n        props.put(\"spring.cloud.deployer.kubernetes.affinity.nodeAffinity\",\n                \"{ requiredDuringSchedulingIgnoredDuringExecution:\" +\n                        \"  { nodeSelectorTerms:\" +\n                        \"    [ { matchExpressions:\" +\n                        \"        [ { key: 'kubernetes.io/e2e-az-name', \" +\n                        \"            operator: 'In',\" +\n                        \"            values:\" +\n                        \"            [ 'e2e-az1', 'e2e-az2']}]}]}, \" +\n                        \"  preferredDuringSchedulingIgnoredDuringExecution:\" +\n                        \"  [ { weight: 1,\" +\n                        \"      preference:\" +\n                        \"      { matchExpressions:\" +\n                        \"        [ { key: 'another-node-label-key',\" +\n                        \"            operator: 'In',\" +\n                        \"            values:\" +\n                        \"            [ 'another-node-label-value' ]}]}}]}\");\n\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), props);\n\n        deployer = k8sAppDeployer(new KubernetesDeployerProperties());\n        PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);\n\n        NodeAffinity nodeAffinity = podSpec.getAffinity().getNodeAffinity();\n        assertThat(nodeAffinity).as(\"Node affinity should not be null\").isNotNull();\n        assertThat(nodeAffinity.getRequiredDuringSchedulingIgnoredDuringExecution()).as(\"RequiredDuringSchedulingIgnoredDuringExecution should not be null\").isNotNull();\n        assertThat(nodeAffinity.getPreferredDuringSchedulingIgnoredDuringExecution().size()).as(\"PreferredDuringSchedulingIgnoredDuringExecution should have one element\").isEqualTo(1);\n    }\n\n    @Test\n    public void testPodAffinityProperty() {\n        Map<String, String> props = new HashMap<>();\n        props.put(\"spring.cloud.deployer.kubernetes.affinity.podAffinity\",\n                \"{ requiredDuringSchedulingIgnoredDuringExecution:\" +\n                        \"  { labelSelector:\" +\n                        \"    [ { matchExpressions:\" +\n                        \"        [ { key: 'app', \" +\n                        \"            operator: 'In',\" +\n                        \"            values:\" +\n                        \"            [ 'store']}]}], \" +\n                        \"     topologyKey: 'kubernetes.io/hostname'}, \" +\n                        \"  preferredDuringSchedulingIgnoredDuringExecution:\" +\n                        \"  [ { weight: 1,\" +\n                        \"      podAffinityTerm:\" +\n                        \"      { labelSelector:\" +\n                        \"        { matchExpressions:\" +\n                        \"          [ { key: 'security',\" +\n                        \"              operator: 'In',\" +\n                        \"              values:\" +\n                        \"              [ 'S2' ]}]}, \" +\n                        \"        topologyKey: 'failure-domain.beta.kubernetes.io/zone'}}]}\");\n\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), props);\n\n        deployer = k8sAppDeployer(new KubernetesDeployerProperties());\n        PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);\n\n        PodAffinity podAffinity = podSpec.getAffinity().getPodAffinity();\n        assertThat(podAffinity).as(\"Pod affinity should not be null\").isNotNull();\n        assertThat(podAffinity.getRequiredDuringSchedulingIgnoredDuringExecution()).as(\"RequiredDuringSchedulingIgnoredDuringExecution should not be null\").isNotNull();\n        assertThat(podAffinity.getPreferredDuringSchedulingIgnoredDuringExecution().size()).as(\"PreferredDuringSchedulingIgnoredDuringExecution should have one element\").isEqualTo(1);\n    }\n\n    @Test\n    public void testPodAntiAffinityProperty() {\n        Map<String, String> props = new HashMap<>();\n        props.put(\"spring.cloud.deployer.kubernetes.affinity.podAntiAffinity\",\n                \"{ requiredDuringSchedulingIgnoredDuringExecution:\" +\n                        \"  { labelSelector:\" +\n                        \"    [ { matchExpressions:\" +\n                        \"        [ { key: 'app', \" +\n                        \"            operator: 'In',\" +\n                        \"            values:\" +\n                        \"            [ 'store']}]}], \" +\n                        \"     topologyKey: 'kubernetes.io/hostname'}, \" +\n                        \"  preferredDuringSchedulingIgnoredDuringExecution:\" +\n                        \"  [ { weight: 1,\" +\n                        \"      podAffinityTerm:\" +\n                        \"      { labelSelector:\" +\n                        \"        { matchExpressions:\" +\n                        \"          [ { key: 'security',\" +\n                        \"              operator: 'In',\" +\n                        \"              values:\" +\n                        \"              [ 'S2' ]}]}, \" +\n                        \"        topologyKey: 'failure-domain.beta.kubernetes.io/zone'}}]}\");\n\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), props);\n\n        deployer = k8sAppDeployer(new KubernetesDeployerProperties());\n        PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);\n\n        PodAntiAffinity podAntiAffinity = podSpec.getAffinity().getPodAntiAffinity();\n        assertThat(podAntiAffinity).as(\"Pod anti-affinity should not be null\").isNotNull();\n        assertThat(podAntiAffinity.getRequiredDuringSchedulingIgnoredDuringExecution()).as(\"RequiredDuringSchedulingIgnoredDuringExecution should not be null\").isNotNull();\n        assertThat(podAntiAffinity.getPreferredDuringSchedulingIgnoredDuringExecution().size()).as(\"PreferredDuringSchedulingIgnoredDuringExecution should have one element\").isEqualTo(1);\n    }\n\n    @Test\n    public void testNodeAffinityGlobalProperty() {\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), null);\n\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n\n        NodeSelectorTerm nodeSelectorTerm = new NodeSelectorTerm();\n        nodeSelectorTerm.setMatchExpressions(Arrays.asList(new NodeSelectorRequirementBuilder()\n                .withKey(\"kubernetes.io/e2e-az-name\")\n                .withOperator(\"In\")\n                .withValues(\"e2e-az1\", \"e2e-az2\")\n                .build()));\n        NodeSelectorTerm nodeSelectorTerm2 = new NodeSelectorTerm();\n        nodeSelectorTerm2.setMatchExpressions(Arrays.asList(new NodeSelectorRequirementBuilder()\n                .withKey(\"another-node-label-key\")\n                .withOperator(\"In\")\n                .withValues(\"another-node-label-value2\")\n                .build()));\n        PreferredSchedulingTerm preferredSchedulingTerm = new PreferredSchedulingTerm(nodeSelectorTerm2, 1);\n        NodeAffinity nodeAffinity = new AffinityBuilder()\n                .withNewNodeAffinity()\n                .withNewRequiredDuringSchedulingIgnoredDuringExecution()\n                .withNodeSelectorTerms(nodeSelectorTerm)\n                .endRequiredDuringSchedulingIgnoredDuringExecution()\n                .withPreferredDuringSchedulingIgnoredDuringExecution(preferredSchedulingTerm)\n                .endNodeAffinity()\n                .buildNodeAffinity();\n\n        kubernetesDeployerProperties.setNodeAffinity(nodeAffinity);\n\n        deployer = k8sAppDeployer(kubernetesDeployerProperties);\n        PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);\n\n        NodeAffinity nodeAffinityTest = podSpec.getAffinity().getNodeAffinity();\n        assertThat(nodeAffinityTest).as(\"Node affinity should not be null\").isNotNull();\n        assertThat(nodeAffinityTest.getRequiredDuringSchedulingIgnoredDuringExecution()).as(\"RequiredDuringSchedulingIgnoredDuringExecution should not be null\").isNotNull();\n        assertThat(nodeAffinityTest.getPreferredDuringSchedulingIgnoredDuringExecution().size()).as(\"PreferredDuringSchedulingIgnoredDuringExecution should have one element\").isEqualTo(1);\n    }\n\n    @Test\n    public void testPodAffinityGlobalProperty() {\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), null);\n\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n\n        LabelSelector labelSelector = new LabelSelector();\n        labelSelector.setMatchExpressions(Arrays.asList(new LabelSelectorRequirementBuilder()\n                .withKey(\"security\")\n                .withOperator(\"In\")\n                .withValues(\"S1\")\n                .build()));\n        PodAffinityTerm podAffinityTerm = new PodAffinityTerm(labelSelector, null, null, null, null, \"failure-domain.beta.kubernetes.io/zone\");\n        LabelSelector labelSelector2 = new LabelSelector();\n        labelSelector2.setMatchExpressions(Arrays.asList(new LabelSelectorRequirementBuilder()\n                .withKey(\"security\")\n                .withOperator(\"In\")\n                .withValues(\"s2\")\n                .build()));\n        PodAffinityTerm podAffinityTerm2 = new PodAffinityTerm(labelSelector2, null, null, null, null, \"failure-domain.beta.kubernetes.io/zone\");\n        WeightedPodAffinityTerm weightedPodAffinityTerm = new WeightedPodAffinityTerm(podAffinityTerm2, 100);\n        PodAffinity podAffinity = new AffinityBuilder()\n                .withNewPodAffinity()\n                .withRequiredDuringSchedulingIgnoredDuringExecution(podAffinityTerm)\n                .withPreferredDuringSchedulingIgnoredDuringExecution(weightedPodAffinityTerm)\n                .endPodAffinity()\n                .buildPodAffinity();\n\n        kubernetesDeployerProperties.setPodAffinity(podAffinity);\n\n        deployer = k8sAppDeployer(kubernetesDeployerProperties);\n        PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);\n\n        PodAffinity podAffinityTest = podSpec.getAffinity().getPodAffinity();\n        assertThat(podAffinityTest).as(\"Pod affinity should not be null\").isNotNull();\n        assertThat(podAffinityTest.getRequiredDuringSchedulingIgnoredDuringExecution()).as(\"RequiredDuringSchedulingIgnoredDuringExecution should not be null\").isNotNull();\n        assertThat(podAffinityTest.getPreferredDuringSchedulingIgnoredDuringExecution().size()).as(\"PreferredDuringSchedulingIgnoredDuringExecution should have one element\").isEqualTo(1);\n    }\n\n    @Test\n    public void testPodAntiAffinityGlobalProperty() {\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), null);\n\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n        kubernetesDeployerProperties.setStartupHttpProbePort(kubernetesDeployerProperties.getLivenessHttpProbePort());\n        LabelSelector labelSelector = new LabelSelector();\n        labelSelector.setMatchExpressions(Arrays.asList(new LabelSelectorRequirementBuilder()\n                .withKey(\"app\")\n                .withOperator(\"In\")\n                .withValues(\"store\")\n                .build()));\n        PodAffinityTerm podAffinityTerm = new PodAffinityTerm(labelSelector, null, null, null, null, \"kubernetes.io/hostname\");\n        LabelSelector labelSelector2 = new LabelSelector();\n        labelSelector2.setMatchExpressions(Arrays.asList(new LabelSelectorRequirementBuilder()\n                .withKey(\"security\")\n                .withOperator(\"In\")\n                .withValues(\"s2\")\n                .build()));\n        PodAffinityTerm podAffinityTerm2 = new PodAffinityTerm(labelSelector2, null, null, null, null, \"failure-domain.beta.kubernetes.io/zone\");\n        WeightedPodAffinityTerm weightedPodAffinityTerm = new WeightedPodAffinityTerm(podAffinityTerm2, 100);\n        PodAntiAffinity podAntiAffinity = new AffinityBuilder()\n                .withNewPodAntiAffinity()\n                .withRequiredDuringSchedulingIgnoredDuringExecution(podAffinityTerm)\n                .withPreferredDuringSchedulingIgnoredDuringExecution(weightedPodAffinityTerm)\n                .endPodAntiAffinity()\n                .buildPodAntiAffinity();\n\n        kubernetesDeployerProperties.setPodAntiAffinity(podAntiAffinity);\n\n        deployer = k8sAppDeployer(kubernetesDeployerProperties);\n        PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);\n\n        PodAntiAffinity podAntiAffinityTest = podSpec.getAffinity().getPodAntiAffinity();\n        assertThat(podAntiAffinityTest).as(\"Pod anti-affinity should not be null\").isNotNull();\n        assertThat(podAntiAffinityTest.getRequiredDuringSchedulingIgnoredDuringExecution()).as(\"RequiredDuringSchedulingIgnoredDuringExecution should not be null\").isNotNull();\n        assertThat(podAntiAffinityTest.getPreferredDuringSchedulingIgnoredDuringExecution().size()).as(\"PreferredDuringSchedulingIgnoredDuringExecution should have one element\").isEqualTo(1);\n    }\n\n    @Test\n    public void testNodeAffinityFromYaml() throws Exception {\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), null);\n\n        deployer = k8sAppDeployer();\n        PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);\n\n        NodeAffinity nodeAffinity = podSpec.getAffinity().getNodeAffinity();\n        assertThat(nodeAffinity).as(\"Node affinity should not be null\").isNotNull();\n        assertThat(nodeAffinity.getRequiredDuringSchedulingIgnoredDuringExecution()).as(\"RequiredDuringSchedulingIgnoredDuringExecution should not be null\").isNotNull();\n        assertThat(nodeAffinity.getPreferredDuringSchedulingIgnoredDuringExecution().size()).as(\"PreferredDuringSchedulingIgnoredDuringExecution should have one element\").isEqualTo(1);\n    }\n\n    @Test\n    public void testPodAffinityFromYaml() throws Exception {\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), null);\n\n        deployer = k8sAppDeployer();\n        PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);\n\n        PodAffinity podAffinity = podSpec.getAffinity().getPodAffinity();\n        assertThat(podAffinity).as(\"Pod affinity should not be null\").isNotNull();\n        assertThat(podAffinity.getRequiredDuringSchedulingIgnoredDuringExecution()).as(\"RequiredDuringSchedulingIgnoredDuringExecution should not be null\").isNotNull();\n        assertThat(podAffinity.getPreferredDuringSchedulingIgnoredDuringExecution().size()).as(\"PreferredDuringSchedulingIgnoredDuringExecution should have one element\").isEqualTo(1);\n    }\n\n    @Test\n    public void testPodAntiAffinityFromYaml() throws Exception {\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), null);\n\n        deployer = k8sAppDeployer();\n        PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);\n\n        PodAntiAffinity podAntiAffinity = podSpec.getAffinity().getPodAntiAffinity();\n        assertThat(podAntiAffinity).as(\"Pod anti-affinity should not be null\").isNotNull();\n        assertThat(podAntiAffinity.getRequiredDuringSchedulingIgnoredDuringExecution()).as(\"RequiredDuringSchedulingIgnoredDuringExecution should not be null\").isNotNull();\n        assertThat(podAntiAffinity.getPreferredDuringSchedulingIgnoredDuringExecution().size()).as(\"PreferredDuringSchedulingIgnoredDuringExecution should have one element\").isEqualTo(1);\n    }\n\n    @Test\n    public void testNodeAffinityPropertyOverrideGlobal() {\n        Map<String, String> props = new HashMap<>();\n        props.put(\"spring.cloud.deployer.kubernetes.affinity.nodeAffinity\",\n                \"{ requiredDuringSchedulingIgnoredDuringExecution:\" +\n                        \"  { nodeSelectorTerms:\" +\n                        \"    [ { matchExpressions:\" +\n                        \"        [ { key: 'kubernetes.io/e2e-az-name', \" +\n                        \"            operator: 'In',\" +\n                        \"            values:\" +\n                        \"            [ 'e2e-az1', 'e2e-az2']}]}]}, \" +\n                        \"  preferredDuringSchedulingIgnoredDuringExecution:\" +\n                        \"  [ { weight: 1,\" +\n                        \"      preference:\" +\n                        \"      { matchExpressions:\" +\n                        \"        [ { key: 'another-node-label-key',\" +\n                        \"            operator: 'In',\" +\n                        \"            values:\" +\n                        \"            [ 'another-node-label-value' ]}]}}]}\");\n\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), props);\n\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n\n        NodeSelectorTerm nodeSelectorTerm = new NodeSelectorTerm();\n        nodeSelectorTerm.setMatchExpressions(Arrays.asList(new NodeSelectorRequirementBuilder()\n                .withKey(\"kubernetes.io/e2e-az-name\")\n                .withOperator(\"In\")\n                .withValues(\"e2e-az1\", \"e2e-az2\")\n                .build()));\n        NodeAffinity nodeAffinity = new AffinityBuilder()\n                .withNewNodeAffinity()\n                .withNewRequiredDuringSchedulingIgnoredDuringExecution()\n                .withNodeSelectorTerms(nodeSelectorTerm)\n                .endRequiredDuringSchedulingIgnoredDuringExecution()\n                .endNodeAffinity()\n                .buildNodeAffinity();\n\n        kubernetesDeployerProperties.setNodeAffinity(nodeAffinity);\n\n        deployer = k8sAppDeployer(kubernetesDeployerProperties);\n        PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);\n\n        NodeAffinity nodeAffinityTest = podSpec.getAffinity().getNodeAffinity();\n        assertThat(nodeAffinityTest).as(\"Node affinity should not be null\").isNotNull();\n        assertThat(nodeAffinityTest.getRequiredDuringSchedulingIgnoredDuringExecution()).as(\"RequiredDuringSchedulingIgnoredDuringExecution should not be null\").isNotNull();\n        assertThat(nodeAffinityTest.getPreferredDuringSchedulingIgnoredDuringExecution().size()).as(\"PreferredDuringSchedulingIgnoredDuringExecution should have one element\").isEqualTo(1);\n    }\n\n    @Test\n    public void testPodAffinityPropertyOverrideGlobal() {\n        Map<String, String> props = new HashMap<>();\n        props.put(\"spring.cloud.deployer.kubernetes.affinity.podAffinity\",\n                \"{ requiredDuringSchedulingIgnoredDuringExecution:\" +\n                        \"  { labelSelector:\" +\n                        \"    [ { matchExpressions:\" +\n                        \"        [ { key: 'security', \" +\n                        \"            operator: 'In',\" +\n                        \"            values:\" +\n                        \"            [ 'S1']}]}], \" +\n                        \"     topologyKey: 'failure-domain.beta.kubernetes.io/zone'}, \" +\n                        \"  preferredDuringSchedulingIgnoredDuringExecution:\" +\n                        \"  [ { weight: 1,\" +\n                        \"      podAffinityTerm:\" +\n                        \"      { labelSelector:\" +\n                        \"        { matchExpressions:\" +\n                        \"          [ { key: 'security',\" +\n                        \"              operator: 'In',\" +\n                        \"              values:\" +\n                        \"              [ 'S2' ]}]}, \" +\n                        \"        topologyKey: 'failure-domain.beta.kubernetes.io/zone'}}]}\");\n\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), props);\n\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n\n        LabelSelector labelSelector = new LabelSelector();\n        labelSelector.setMatchExpressions(Arrays.asList(new LabelSelectorRequirementBuilder()\n                .withKey(\"tolerance\")\n                .withOperator(\"In\")\n                .withValues(\"Reliable\")\n                .build()));\n        PodAffinityTerm podAffinityTerm = new PodAffinityTerm(labelSelector, null, null, null, null, \"failure-domain.beta.kubernetes.io/zone\");\n        PodAffinity podAffinity = new AffinityBuilder()\n                .withNewPodAffinity()\n                .withRequiredDuringSchedulingIgnoredDuringExecution(podAffinityTerm)\n                .endPodAffinity()\n                .buildPodAffinity();\n\n        kubernetesDeployerProperties.setPodAffinity(podAffinity);\n\n        deployer = k8sAppDeployer(kubernetesDeployerProperties);\n        PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);\n\n        PodAffinity podAffinityTest = podSpec.getAffinity().getPodAffinity();\n        assertThat(podAffinityTest).as(\"Pod affinity should not be null\").isNotNull();\n        assertThat(podAffinityTest.getRequiredDuringSchedulingIgnoredDuringExecution()).as(\"RequiredDuringSchedulingIgnoredDuringExecution should not be null\").isNotNull();\n        assertThat(podAffinityTest.getPreferredDuringSchedulingIgnoredDuringExecution().size()).as(\"PreferredDuringSchedulingIgnoredDuringExecution should have one element\").isEqualTo(1);\n    }\n\n    @Test\n    public void testPodAntiAffinityPropertyOverrideGlobal() {\n        Map<String, String> props = new HashMap<>();\n        props.put(\"spring.cloud.deployer.kubernetes.affinity.podAntiAffinity\",\n                \"{ requiredDuringSchedulingIgnoredDuringExecution:\" +\n                        \"  { labelSelector:\" +\n                        \"    [ { matchExpressions:\" +\n                        \"        [ { key: 'app', \" +\n                        \"            operator: 'In',\" +\n                        \"            values:\" +\n                        \"            [ 'store']}]}], \" +\n                        \"     topologyKey: 'kubernetes.io/hostnam'}, \" +\n                        \"  preferredDuringSchedulingIgnoredDuringExecution:\" +\n                        \"  [ { weight: 1,\" +\n                        \"      podAffinityTerm:\" +\n                        \"      { labelSelector:\" +\n                        \"        { matchExpressions:\" +\n                        \"          [ { key: 'security',\" +\n                        \"              operator: 'In',\" +\n                        \"              values:\" +\n                        \"              [ 'S2' ]}]}, \" +\n                        \"        topologyKey: 'failure-domain.beta.kubernetes.io/zone'}}]}\");\n\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), props);\n\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n\n        LabelSelector labelSelector = new LabelSelector();\n        labelSelector.setMatchExpressions(Arrays.asList(new LabelSelectorRequirementBuilder()\n                .withKey(\"version\")\n                .withOperator(\"Equals\")\n                .withValues(\"v1\")\n                .build()));\n        PodAffinityTerm podAffinityTerm = new PodAffinityTerm(labelSelector, null, null, null, null, \"kubernetes.io/hostnam\");\n        PodAntiAffinity podAntiAffinity = new AffinityBuilder()\n                .withNewPodAntiAffinity()\n                .withRequiredDuringSchedulingIgnoredDuringExecution(podAffinityTerm)\n                .endPodAntiAffinity()\n                .buildPodAntiAffinity();\n\n        kubernetesDeployerProperties.setPodAntiAffinity(podAntiAffinity);\n\n        deployer = k8sAppDeployer(kubernetesDeployerProperties);\n        PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);\n\n        PodAntiAffinity podAntiAffinityTest = podSpec.getAffinity().getPodAntiAffinity();\n        assertThat(podAntiAffinityTest).as(\"Pod anti-affinity should not be null\").isNotNull();\n        assertThat(podAntiAffinityTest.getRequiredDuringSchedulingIgnoredDuringExecution()).as(\"RequiredDuringSchedulingIgnoredDuringExecution should not be null\").isNotNull();\n        assertThat(podAntiAffinityTest.getPreferredDuringSchedulingIgnoredDuringExecution().size()).as(\"PreferredDuringSchedulingIgnoredDuringExecution should have one element\").isEqualTo(1);\n    }\n\n\t@Nested\n\t@DisplayName(\"creates pod spec with pod security context\")\n    class CreatePodSpecWithPodSecurityContext {\n\n        @Test\n        @DisplayName(\"created from deployment property with all fields\")\n        void createdFromDeploymentPropertyWithAllFields() {\n            KubernetesDeployerProperties globalDeployerProps = new KubernetesDeployerProperties();\n            Map<String, String> deploymentProps = new HashMap<>();\n\t\t\tdeploymentProps.put(\"spring.cloud.deployer.kubernetes.podSecurityContext\", \"{\" +\n\t\t\t\t\t\"  fsGroup: 65534\" +\n\t\t\t\t\t\", fsGroupChangePolicy: Always\" +\n\t\t\t\t\t\", runAsUser: 65534\" +\n\t\t\t\t\t\", runAsGroup: 65534\" +\n\t\t\t\t\t\", runAsNonRoot: true\" +\n\t\t\t\t\t\", seLinuxOptions: { level: \\\"s0:c123,c456\\\" }\" +\n\t\t\t\t\t\", seccompProfile: { type: Localhost, localhostProfile: my-profiles/profile-allow.json }\" +\n\t\t\t\t\t\", supplementalGroups: [65534, 65535]\" +\n\t\t\t\t\t\", sysctls: [{name: \\\"kernel.shm_rmid_forced\\\", value: 0}, {name: \\\"net.core.somaxconn\\\", value: 1024}]\" +\n\t\t\t\t\t\", windowsOptions: { gmsaCredentialSpec: \\\"specA\\\", gmsaCredentialSpecName: \\\"specA-name\\\", hostProcess: true, runAsUserName: \\\"userA\\\" }\" +\n\t\t\t\t\t\"}\");\n            PodSecurityContext expectedPodSecurityContext = new PodSecurityContextBuilder()\n\t\t\t\t\t.withFsGroup(65534L)\n\t\t\t\t\t.withFsGroupChangePolicy(\"Always\")\n                    .withRunAsUser(65534L)\n\t\t\t\t\t.withRunAsGroup(65534L)\n\t\t\t\t\t.withRunAsNonRoot(true)\n\t\t\t\t\t.withSeLinuxOptions(new SELinuxOptions(\"s0:c123,c456\", null, null, null))\n\t\t\t\t\t.withSeccompProfile(new SeccompProfile(\"my-profiles/profile-allow.json\", \"Localhost\"))\n\t\t\t\t\t.withSupplementalGroups(65534L, 65535L)\n\t\t\t\t\t.withSysctls(new Sysctl(\"kernel.shm_rmid_forced\", \"0\"), new Sysctl(\"net.core.somaxconn\", \"1024\"))\n\t\t\t\t\t.withWindowsOptions(new WindowsSecurityContextOptions(\"specA\", \"specA-name\", true, \"userA\"))\n                    .build();\n            assertThatDeployerCreatesPodSpecWithPodSecurityContext(globalDeployerProps, deploymentProps, expectedPodSecurityContext);\n        }\n\n        @Test\n        @DisplayName(\"created from deployment property with runAsUser only\")\n        void createdFromDeploymentPropertyWithRunAsUserOnly() {\n            KubernetesDeployerProperties globalDeployerProps = new KubernetesDeployerProperties();\n            Map<String, String> deploymentProps = new HashMap<>();\n            deploymentProps.put(\"spring.cloud.deployer.kubernetes.podSecurityContext\", \"{runAsUser: 65534}\");\n            PodSecurityContext expectedPodSecurityContext = new PodSecurityContextBuilder()\n                    .withRunAsUser(65534L)\n                    .build();\n            assertThatDeployerCreatesPodSpecWithPodSecurityContext(globalDeployerProps, deploymentProps, expectedPodSecurityContext);\n        }\n\n        @Test\n        @DisplayName(\"created from deployment property with fsGroup only\")\n        void createdFromDeploymentPropertyWithFsGroupOnly() {\n            KubernetesDeployerProperties globalDeployerProps = new KubernetesDeployerProperties();\n            Map<String, String> deploymentProps = new HashMap<>();\n            deploymentProps.put(\"spring.cloud.deployer.kubernetes.podSecurityContext\", \"{fsGroup: 65534}\");\n            PodSecurityContext expectedPodSecurityContext = new PodSecurityContextBuilder()\n                    .withFsGroup(65534L)\n                    .build();\n            assertThatDeployerCreatesPodSpecWithPodSecurityContext(globalDeployerProps, deploymentProps, expectedPodSecurityContext);\n        }\n\n        @Test\n        @DisplayName(\"created from deployment property with supplementalGroups only\")\n        void createdFromDeploymentPropertyWithSupplementalGroupsOnly() {\n            KubernetesDeployerProperties globalDeployerProps = new KubernetesDeployerProperties();\n            Map<String, String> deploymentProps = new HashMap<>();\n            deploymentProps.put(\"spring.cloud.deployer.kubernetes.podSecurityContext\", \"{supplementalGroups: [65534,65535]}\");\n            PodSecurityContext expectedPodSecurityContext = new PodSecurityContextBuilder()\n                    .withSupplementalGroups(65534L, 65535L)\n                    .build();\n            assertThatDeployerCreatesPodSpecWithPodSecurityContext(globalDeployerProps, deploymentProps, expectedPodSecurityContext);\n        }\n\n        @Test\n        @DisplayName(\"created from deployment property with seccompProfile only\")\n        void createdFromDeploymentPropertyWithSeccompProfileOnly() {\n            KubernetesDeployerProperties globalDeployerProps = new KubernetesDeployerProperties();\n            Map<String, String> deploymentProps = new HashMap<>();\n            deploymentProps.put(\"spring.cloud.deployer.kubernetes.podSecurityContext\", \"{seccompProfile: { type: RuntimeDefault}}\");\n            PodSecurityContext expectedPodSecurityContext = new PodSecurityContextBuilder()\n                    .withSeccompProfile(new SeccompProfile(null, \"RuntimeDefault\"))\n                    .build();\n            assertThatDeployerCreatesPodSpecWithPodSecurityContext(globalDeployerProps, deploymentProps, expectedPodSecurityContext);\n        }\n\n        @Test\n        @DisplayName(\"created from global deployer property sourced from yaml\")\n        void createdFromGlobalDeployerPropertySourcedFromYaml() throws Exception {\n            KubernetesDeployerProperties globalDeployerProps = bindDeployerProperties();\n            Map<String, String> deploymentProps = new HashMap<>();\n\t\t\tPodSecurityContext expectedPodSecurityContext = new PodSecurityContextBuilder()\n\t\t\t\t\t.withFsGroup(65534L)\n\t\t\t\t\t.withFsGroupChangePolicy(\"Always\")\n\t\t\t\t\t.withRunAsUser(65534L)\n\t\t\t\t\t.withRunAsGroup(65534L)\n\t\t\t\t\t.withRunAsNonRoot(true)\n\t\t\t\t\t.withSeLinuxOptions(new SELinuxOptions(\"s0:c123,c456\", null, null, null))\n\t\t\t\t\t.withSeccompProfile(new SeccompProfile(\"my-profiles/profile-allow.json\", \"Localhost\"))\n\t\t\t\t\t.withSupplementalGroups(65534L, 65535L)\n\t\t\t\t\t.withSysctls(new Sysctl(\"kernel.shm_rmid_forced\", \"0\"), new Sysctl(\"net.core.somaxconn\", \"1024\"))\n\t\t\t\t\t.withWindowsOptions(new WindowsSecurityContextOptions(\"specA\", \"specA-name\", true, \"userA\"))\n\t\t\t\t\t.build();\n            assertThatDeployerCreatesPodSpecWithPodSecurityContext(globalDeployerProps, deploymentProps, expectedPodSecurityContext);\n        }\n\n        @Test\n        @DisplayName(\"created from global deployer property\")\n        void createdFromGlobalDeployerProperty() {\n            KubernetesDeployerProperties globalDeployerProps = new KubernetesDeployerProperties();\n            KubernetesDeployerProperties.PodSecurityContext securityContext = new KubernetesDeployerProperties.PodSecurityContext();\n            securityContext.setFsGroup(65534L);\n            securityContext.setRunAsUser(65534L);\n            securityContext.setSupplementalGroups(new Long[]{65534L});\n            KubernetesDeployerProperties.SeccompProfile seccompProfile = new KubernetesDeployerProperties.SeccompProfile();\n            seccompProfile.setType(\"Localhost\");\n            seccompProfile.setLocalhostProfile(\"profile.json\");\n            securityContext.setSeccompProfile(seccompProfile);\n            globalDeployerProps.setPodSecurityContext(securityContext);\n            Map<String, String> deploymentProps = Collections.emptyMap();\n            PodSecurityContext expectedPodSecurityContext = new PodSecurityContextBuilder()\n                    .withRunAsUser(65534L)\n                    .withFsGroup(65534L)\n                    .withSupplementalGroups(65534L)\n                    .withSeccompProfile(new SeccompProfile(\"profile.json\", \"Localhost\"))\n                    .build();\n            assertThatDeployerCreatesPodSpecWithPodSecurityContext(globalDeployerProps, deploymentProps, expectedPodSecurityContext);\n        }\n\n        @Test\n        @DisplayName(\"created from deployment property overrriding global deployer property\")\n        void createdFromDeploymentPropertyOverridingGlobalDeployerProperty() {\n            KubernetesDeployerProperties globalDeployerProps = new KubernetesDeployerProperties();\n            KubernetesDeployerProperties.PodSecurityContext securityContext = new KubernetesDeployerProperties.PodSecurityContext();\n            securityContext.setFsGroup(1000L);\n            securityContext.setRunAsUser(1000L);\n            securityContext.setSupplementalGroups(new Long[]{1000L});\n            KubernetesDeployerProperties.SeccompProfile seccompProfile = new KubernetesDeployerProperties.SeccompProfile();\n            seccompProfile.setType(\"Localhost\");\n            seccompProfile.setLocalhostProfile(\"sec/default-allow.json\");\n            securityContext.setSeccompProfile(seccompProfile);\n            globalDeployerProps.setPodSecurityContext(securityContext);\n            Map<String, String> deploymentProps = new HashMap<>();\n            deploymentProps.put(\"spring.cloud.deployer.kubernetes.podSecurityContext\", \"{runAsUser: 65534, fsGroup: 65534, supplementalGroups: [65534,65535], seccompProfile: { type: Localhost, localhostProfile: sec/custom-allow.json } }\");\n            PodSecurityContext expectedPodSecurityContext = new PodSecurityContextBuilder()\n                    .withRunAsUser(65534L)\n                    .withFsGroup(65534L)\n                    .withSupplementalGroups(65534L, 65535L)\n                    .withSeccompProfile(new SeccompProfile(\"sec/custom-allow.json\", \"Localhost\"))\n                    .build();\n            assertThatDeployerCreatesPodSpecWithPodSecurityContext(globalDeployerProps, deploymentProps, expectedPodSecurityContext);\n        }\n\n        private void assertThatDeployerCreatesPodSpecWithPodSecurityContext(\n                KubernetesDeployerProperties globalDeployerProps,\n                Map<String, String> deploymentProps,\n                PodSecurityContext expectedPodSecurityContext\n        ) {\n            PodSpec podSpec = deployerCreatesPodSpec(globalDeployerProps, deploymentProps);\n            PodSecurityContext actualPodSecurityContext = podSpec.getSecurityContext();\n            assertThat(actualPodSecurityContext)\n                    .isNotNull()\n                    .isEqualTo(expectedPodSecurityContext);\n        }\n    }\n\n    @Nested\n    @DisplayName(\"creates pod spec with container security context\")\n    class CreatePodSpecWithContainerSecurityContext {\n\n        @Test\n        @DisplayName(\"created from deployment property with all fields\")\n        void createdFromDeploymentPropertyWithAllFields() {\n            KubernetesDeployerProperties globalDeployerProps = new KubernetesDeployerProperties();\n            Map<String, String> deploymentProps = new HashMap<>();\n\t\t\tdeploymentProps.put(\"spring.cloud.deployer.kubernetes.containerSecurityContext\", \"{\" +\n\t\t\t\t\t\"  allowPrivilegeEscalation: true\" +\n\t\t\t\t\t\", capabilities: { add: [ \\\"a\\\", \\\"b\\\" ], drop: [ \\\"c\\\" ] }\" +\n\t\t\t\t\t\", privileged: true\" +\n\t\t\t\t\t\", procMount: DefaultProcMount\" +\n\t\t\t\t\t\", readOnlyRootFilesystem: true\" +\n\t\t\t\t\t\", runAsUser: 65534\" +\n\t\t\t\t\t\", runAsGroup: 65534\" +\n\t\t\t\t\t\", runAsNonRoot: true\" +\n\t\t\t\t\t\", seLinuxOptions: { level: \\\"s0:c123,c456\\\" }\" +\n\t\t\t\t\t\", seccompProfile: { type: Localhost, localhostProfile: my-profiles/profile-allow.json }\" +\n\t\t\t\t\t\", windowsOptions: { gmsaCredentialSpec: \\\"specA\\\", gmsaCredentialSpecName: \\\"specA-name\\\", hostProcess: true, runAsUserName: \\\"userA\\\" }\" +\n\t\t\t\t\t\"}\");\n\t\t\tSecurityContext expectedContainerSecurityContext = new SecurityContextBuilder()\n                    .withAllowPrivilegeEscalation(true)\n\t\t\t\t\t.withCapabilities(new Capabilities(Arrays.asList(\"a\", \"b\"), Arrays.asList(\"c\")))\n\t\t\t\t\t.withPrivileged(true)\n\t\t\t\t\t.withProcMount(\"DefaultProcMount\")\n\t\t\t\t\t.withReadOnlyRootFilesystem(true)\n\t\t\t\t\t.withRunAsUser(65534L)\n\t\t\t\t\t.withRunAsGroup(65534L)\n\t\t\t\t\t.withRunAsNonRoot(true)\n\t\t\t\t\t.withSeLinuxOptions(new SELinuxOptions(\"s0:c123,c456\", null, null, null))\n\t\t\t\t\t.withSeccompProfile(new SeccompProfile(\"my-profiles/profile-allow.json\", \"Localhost\"))\n\t\t\t\t\t.withWindowsOptions(new WindowsSecurityContextOptions(\"specA\", \"specA-name\", true, \"userA\"))\n                    .build();\n            assertThatDeployerCreatesPodSpecWithContainerSecurityContext(globalDeployerProps, deploymentProps, expectedContainerSecurityContext);\n        }\n\n        @Test\n        @DisplayName(\"created from deployment property with allowPrivilegeEscalation only\")\n        void createdFromDeploymentPropertyWithAllowPrivilegeEscalationOnly() {\n            KubernetesDeployerProperties globalDeployerProps = new KubernetesDeployerProperties();\n            Map<String, String> deploymentProps = Collections.singletonMap(\"spring.cloud.deployer.kubernetes.containerSecurityContext\", \"{allowPrivilegeEscalation: true}\");\n            SecurityContext expectedContainerSecurityContext = new SecurityContextBuilder()\n                    .withAllowPrivilegeEscalation(true)\n                    .build();\n            assertThatDeployerCreatesPodSpecWithContainerSecurityContext(globalDeployerProps, deploymentProps, expectedContainerSecurityContext);\n        }\n\n        @Test\n        @DisplayName(\"created from deployment property with readOnlyRootFilesystem only\")\n        void createdFromDeploymentPropertyWithReadOnlyRootFilesystemOnly() {\n            KubernetesDeployerProperties globalDeployerProps = new KubernetesDeployerProperties();\n            Map<String, String> deploymentProps = Collections.singletonMap(\"spring.cloud.deployer.kubernetes.containerSecurityContext\", \"{readOnlyRootFilesystem: true}\");\n            SecurityContext expectedContainerSecurityContext = new SecurityContextBuilder()\n                    .withReadOnlyRootFilesystem(true)\n                    .build();\n            assertThatDeployerCreatesPodSpecWithContainerSecurityContext(globalDeployerProps, deploymentProps, expectedContainerSecurityContext);\n        }\n\n        @Test\n        @DisplayName(\"created from global deployer property sourced from yaml\")\n        void createdFromGlobalDeployerPropertySourcedFromYaml() throws Exception {\n            KubernetesDeployerProperties globalDeployerProps = bindDeployerProperties();\n            Map<String, String> deploymentProps = Collections.emptyMap();\n\t\t\tSecurityContext expectedContainerSecurityContext = new SecurityContextBuilder()\n\t\t\t\t\t.withAllowPrivilegeEscalation(true)\n\t\t\t\t\t.withCapabilities(new Capabilities(Arrays.asList(\"a\", \"b\"), Arrays.asList(\"c\")))\n\t\t\t\t\t.withPrivileged(true)\n\t\t\t\t\t.withProcMount(\"DefaultProcMount\")\n\t\t\t\t\t.withReadOnlyRootFilesystem(true)\n\t\t\t\t\t.withRunAsUser(65534L)\n\t\t\t\t\t.withRunAsGroup(65534L)\n\t\t\t\t\t.withRunAsNonRoot(true)\n\t\t\t\t\t.withSeLinuxOptions(new SELinuxOptions(\"s0:c123,c456\", null, null, null))\n\t\t\t\t\t.withSeccompProfile(new SeccompProfile(\"my-profiles/profile-allow.json\", \"Localhost\"))\n\t\t\t\t\t.withWindowsOptions(new WindowsSecurityContextOptions(\"specA\", \"specA-name\", true, \"userA\"))\n\t\t\t\t\t.build();\n            assertThatDeployerCreatesPodSpecWithContainerSecurityContext(globalDeployerProps, deploymentProps, expectedContainerSecurityContext);\n        }\n\n        @Test\n        @DisplayName(\"created from global deployer property\")\n        void createdFromGlobalDeployerProperty() {\n            KubernetesDeployerProperties globalDeployerProps = new KubernetesDeployerProperties();\n            KubernetesDeployerProperties.ContainerSecurityContext securityContext = new KubernetesDeployerProperties.ContainerSecurityContext();\n            securityContext.setAllowPrivilegeEscalation(false);\n            securityContext.setReadOnlyRootFilesystem(true);\n            globalDeployerProps.setContainerSecurityContext(securityContext);\n            Map<String, String> deploymentProps = Collections.emptyMap();\n            SecurityContext expectedContainerSecurityContext = new SecurityContextBuilder()\n                    .withAllowPrivilegeEscalation(false)\n                    .withReadOnlyRootFilesystem(true)\n                    .build();\n            assertThatDeployerCreatesPodSpecWithContainerSecurityContext(globalDeployerProps, deploymentProps, expectedContainerSecurityContext);\n        }\n\n        @Test\n        @DisplayName(\"created from deployment property overrriding global deployer property\")\n        void createdFromDeploymentPropertyOverridingGlobalDeployerProperty() {\n            KubernetesDeployerProperties globalDeployerProps = new KubernetesDeployerProperties();\n            KubernetesDeployerProperties.ContainerSecurityContext securityContext = new KubernetesDeployerProperties.ContainerSecurityContext();\n            securityContext.setAllowPrivilegeEscalation(true);\n            securityContext.setReadOnlyRootFilesystem(false);\n            globalDeployerProps.setContainerSecurityContext(securityContext);\n            Map<String, String> deploymentProps = Collections.singletonMap(\"spring.cloud.deployer.kubernetes.containerSecurityContext\", \"{allowPrivilegeEscalation: false, readOnlyRootFilesystem: true}\");\n            SecurityContext expectedContainerSecurityContext = new SecurityContextBuilder()\n                    .withAllowPrivilegeEscalation(false)\n                    .withReadOnlyRootFilesystem(true)\n                    .build();\n            assertThatDeployerCreatesPodSpecWithContainerSecurityContext(globalDeployerProps, deploymentProps, expectedContainerSecurityContext);\n        }\n\n        private void assertThatDeployerCreatesPodSpecWithContainerSecurityContext(\n                KubernetesDeployerProperties globalDeployerProps,\n                Map<String, String> deploymentProps,\n                SecurityContext expectedContainerSecurityContext\n        ) {\n            PodSpec podSpec = deployerCreatesPodSpec(globalDeployerProps, deploymentProps);\n            assertThat(podSpec.getContainers())\n                    .singleElement()\n                    .extracting(Container::getSecurityContext)\n                    .isEqualTo(expectedContainerSecurityContext);\n        }\n    }\n\n    private PodSpec deployerCreatesPodSpec(KubernetesDeployerProperties globalDeployerProperties, Map<String, String> deploymentProperties) {\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), deploymentProperties);\n        KubernetesAppDeployer deployer = k8sAppDeployer(globalDeployerProperties);\n        return deployer.createPodSpec(appDeploymentRequest);\n    }\n\n    @Test\n    public void testWithLifecyclePostStart() {\n        Map<String, String> props = new HashMap<>();\n        props.put(\"spring.cloud.deployer.kubernetes.lifecycle.postStart.exec.command\",\n                \"/bin/sh,-c,echo Hello from the postStart handler > /usr/share/message\");\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), props);\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n        deployer = k8sAppDeployer(kubernetesDeployerProperties);\n        PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);\n        assertThat(podSpec.getContainers().get(0).getLifecycle().getPostStart().getExec().getCommand())\n                .containsExactlyInAnyOrder(\"/bin/sh\", \"-c\", \"echo Hello from the postStart handler > /usr/share/message\");\n    }\n\n    @Test\n    public void testWithLifecyclePreStop() {\n        Map<String, String> props = new HashMap<>();\n        props.put(\"spring.cloud.deployer.kubernetes.lifecycle.preStop.exec.command\",\n                \"/bin/sh,-c,nginx -s quit; while killall -0 nginx; do sleep 1; done\");\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), props);\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n        deployer = k8sAppDeployer(kubernetesDeployerProperties);\n        PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);\n        assertThat(podSpec.getContainers().get(0).getLifecycle().getPreStop().getExec().getCommand())\n                .containsExactlyInAnyOrder(\n                        \"/bin/sh\", \"-c\", \"nginx -s quit; while killall -0 nginx; do sleep 1; done\");\n    }\n\n    @Test\n    public void testLifecyclePostStartOverridesGlobalPostStart() {\n        Map<String, String> props = new HashMap<>();\n        props.put(\"spring.cloud.deployer.kubernetes.lifecycle.postStart.exec.command\",\n                \"/bin/sh,-c,nginx -s quit; while killall -0 nginx; do sleep 1; done\");\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), props);\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n        KubernetesDeployerProperties.Lifecycle lifecycle = new KubernetesDeployerProperties.Lifecycle();\n        lifecycle.setPostStart(new KubernetesDeployerProperties.Lifecycle.Hook() {\n            @Override\n            KubernetesDeployerProperties.Lifecycle.Exec getExec() {\n                return new KubernetesDeployerProperties.Lifecycle.Exec() {\n                    @Override\n                    List<String> getCommand() {\n                        return Arrays.asList(\"echo\", \"postStart\");\n                    }\n                };\n            }\n        });\n        lifecycle.setPreStop(new KubernetesDeployerProperties.Lifecycle.Hook() {\n            @Override\n            KubernetesDeployerProperties.Lifecycle.Exec getExec() {\n                return new KubernetesDeployerProperties.Lifecycle.Exec() {\n                    @Override\n                    List<String> getCommand() {\n                        return Arrays.asList(\"echo\", \"preStop\");\n                    }\n                };\n            }\n        });\n        kubernetesDeployerProperties.setLifecycle(lifecycle);\n        deployer = k8sAppDeployer(kubernetesDeployerProperties);\n        PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);\n        assertThat(podSpec.getContainers().get(0).getLifecycle().getPostStart().getExec().getCommand())\n                .containsExactlyInAnyOrder(\n                        \"/bin/sh\", \"-c\", \"nginx -s quit; while killall -0 nginx; do sleep 1; done\");\n        assertThat(podSpec.getContainers().get(0).getLifecycle().getPreStop().getExec().getCommand())\n                .containsExactlyInAnyOrder(\"echo\", \"preStop\");\n    }\n\n    @Test\n    public void testLifecyclePrestopOverridesGlobalPrestop() {\n        Map<String, String> props = new HashMap<>();\n        props.put(\"spring.cloud.deployer.kubernetes.lifecycle.preStop.exec.command\",\n                \"/bin/sh,-c,nginx -s quit; while killall -0 nginx; do sleep 1; done\");\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), props);\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n        KubernetesDeployerProperties.Lifecycle lifecycle = new KubernetesDeployerProperties.Lifecycle();\n        lifecycle.setPostStart(new KubernetesDeployerProperties.Lifecycle.Hook() {\n            @Override\n            KubernetesDeployerProperties.Lifecycle.Exec getExec() {\n                return new KubernetesDeployerProperties.Lifecycle.Exec() {\n                    @Override\n                    List<String> getCommand() {\n                        return Arrays.asList(\"echo\", \"postStart\");\n                    }\n                };\n            }\n        });\n        lifecycle.setPreStop(new KubernetesDeployerProperties.Lifecycle.Hook() {\n            @Override\n            KubernetesDeployerProperties.Lifecycle.Exec getExec() {\n                return new KubernetesDeployerProperties.Lifecycle.Exec() {\n                    @Override\n                    List<String> getCommand() {\n                        return Arrays.asList(\"echo\", \"preStop\");\n                    }\n                };\n            }\n        });\n        kubernetesDeployerProperties.setLifecycle(lifecycle);\n        deployer = k8sAppDeployer(kubernetesDeployerProperties);\n        PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);\n        assertThat(podSpec.getContainers().get(0).getLifecycle().getPreStop().getExec().getCommand())\n                .containsExactlyInAnyOrder(\n                        \"/bin/sh\", \"-c\", \"nginx -s quit; while killall -0 nginx; do sleep 1; done\");\n        assertThat(podSpec.getContainers().get(0).getLifecycle().getPostStart().getExec().getCommand())\n                .containsExactlyInAnyOrder(\"echo\", \"postStart\");\n    }\n\n    @Test\n    public void terminationGracePeriodFromDeployerProp() {\n        Map<String, String> props = new HashMap<>();\n        props.put(\"spring.cloud.deployer.kubernetes.terminationGracePeriodSeconds\", \"5150\");\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), props);\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n        kubernetesDeployerProperties.setTerminationGracePeriodSeconds(6160L);\n        deployer = k8sAppDeployer(kubernetesDeployerProperties);\n        PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);\n        assertThat(podSpec.getTerminationGracePeriodSeconds()).isEqualTo(5150L);\n    }\n\n    @Test\n    public void terminationGracePeriodFromGlobalProp() {\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), Collections.emptyMap());\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n        kubernetesDeployerProperties.setTerminationGracePeriodSeconds(6160L);\n        deployer = k8sAppDeployer(kubernetesDeployerProperties);\n        PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);\n        assertThat(podSpec.getTerminationGracePeriodSeconds()).isEqualTo(6160L);\n    }\n\n    @Test\n    public void terminationGracePeriodNotSpecified() {\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), Collections.emptyMap());\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n        deployer = k8sAppDeployer(kubernetesDeployerProperties);\n        PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);\n        assertThat(podSpec.getTerminationGracePeriodSeconds()).isNull();\n    }\n\n    private Resource getResource() {\n        return new DockerResource(\"springcloud/spring-cloud-deployer-spi-test-app:latest\");\n    }\n\n    private KubernetesDeployerProperties bindDeployerProperties() throws Exception {\n        YamlPropertiesFactoryBean properties = new YamlPropertiesFactoryBean();\n        properties.setResources(new ClassPathResource(\"dataflow-server.yml\"),\n                new ClassPathResource(\"dataflow-server-tolerations.yml\"),\n                new ClassPathResource(\"dataflow-server-secretKeyRef.yml\"),\n                new ClassPathResource(\"dataflow-server-configMapKeyRef.yml\"),\n                new ClassPathResource(\"dataflow-server-podsecuritycontext.yml\"),\n                new ClassPathResource(\"dataflow-server-containerSecurityContext.yml\"),\n                new ClassPathResource(\"dataflow-server-nodeAffinity.yml\"),\n                new ClassPathResource(\"dataflow-server-podAffinity.yml\"),\n                new ClassPathResource(\"dataflow-server-podAntiAffinity.yml\"));\n        Properties yaml = properties.getObject();\n        MapConfigurationPropertySource source = new MapConfigurationPropertySource(yaml);\n        return new Binder(source).bind(\"\", Bindable.of(KubernetesDeployerProperties.class)).get();\n    }\n\n    protected KubernetesAppDeployer k8sAppDeployer() throws Exception {\n        return k8sAppDeployer(bindDeployerProperties());\n    }\n\n    protected KubernetesAppDeployer k8sAppDeployer(KubernetesDeployerProperties kubernetesDeployerProperties) {\n        return new KubernetesAppDeployer(kubernetesDeployerProperties, null);\n    }\n\n}\n"
  },
  {
    "path": "spring-cloud-deployer-kubernetes/src/test/java/org/springframework/cloud/deployer/spi/kubernetes/KubernetesConfigurationPropertiesTests.java",
    "content": "/*\n * Copyright 2018-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage org.springframework.cloud.deployer.spi.kubernetes;\n\nimport java.net.MalformedURLException;\nimport java.net.URL;\n\nimport io.fabric8.kubernetes.client.KubernetesClient;\nimport org.junit.jupiter.api.Test;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * @author Ilayaperumal Gopinathan\n * @author Chris Schaefer\n * @author Corneil du Plessis\n */\npublic class KubernetesConfigurationPropertiesTests {\n\n\t@Test\n\tpublic void testFabric8Namespacing() throws MalformedURLException {\n\t\tKubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n\t\tkubernetesDeployerProperties.getFabric8().setTrustCerts(true);\n\t\tkubernetesDeployerProperties.getFabric8().setMasterUrl(\"http://localhost:8090\");\n\t\t// this can be set programatically in properties as well as an environment variable\n\t\t// (ie: CI, cmd line, etc) so ensure we have a clean slate here\n\t\tkubernetesDeployerProperties.setNamespace(null);\n\t\tkubernetesDeployerProperties.getFabric8().setNamespace(\"testing\");\n\n\t\tKubernetesClient kubernetesClient = KubernetesClientFactory\n\t\t\t\t.getKubernetesClient(kubernetesDeployerProperties);\n\n\t\tassertThat(kubernetesClient.getMasterUrl()).isEqualTo(new URL(\"http://localhost:8090/\"));\n\t\tassertThat(kubernetesClient.getNamespace()).isEqualTo(\"testing\");\n\t\tassertThat(kubernetesClient.getConfiguration().getMasterUrl()).isEqualTo(\"http://localhost:8090/\");\n\t\tassertThat(kubernetesClient.getConfiguration().isTrustCerts()).isTrue();\n\t}\n\n\t@Test\n\tpublic void testTopLevelNamespacing() throws MalformedURLException {\n\t\tKubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n\t\tkubernetesDeployerProperties.getFabric8().setTrustCerts(true);\n\t\tkubernetesDeployerProperties.getFabric8().setMasterUrl(\"http://localhost:8090/\");\n\t\tkubernetesDeployerProperties.setNamespace(\"toplevel\");\n\n\t\tKubernetesClient kubernetesClient = KubernetesClientFactory\n\t\t\t\t.getKubernetesClient(kubernetesDeployerProperties);\n\n\t\tassertThat(kubernetesClient.getMasterUrl()).isEqualTo(new URL(\"http://localhost:8090/\"));\n\t\tassertThat(kubernetesClient.getNamespace()).isEqualTo(\"toplevel\");\n\t\tassertThat(kubernetesClient.getConfiguration().getMasterUrl()).isEqualTo(\"http://localhost:8090/\");\n\t\tassertThat(kubernetesClient.getConfiguration().isTrustCerts()).isTrue();\n\t}\n\n\t@Test\n\tpublic void testTopLevelNamespacingOverride() throws MalformedURLException {\n\t\tKubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n\t\tkubernetesDeployerProperties.getFabric8().setTrustCerts(true);\n\t\tkubernetesDeployerProperties.getFabric8().setMasterUrl(\"http://localhost:8090/\");\n\t\tkubernetesDeployerProperties.getFabric8().setNamespace(\"toplevel\");\n\t\tkubernetesDeployerProperties.setNamespace(\"toplevel\");\n\n\t\tKubernetesClient kubernetesClient = KubernetesClientFactory\n\t\t\t\t.getKubernetesClient(kubernetesDeployerProperties);\n\n\t\tassertThat(kubernetesClient.getMasterUrl()).isEqualTo(new URL(\"http://localhost:8090/\"));\n\t\tassertThat(kubernetesClient.getNamespace()).isEqualTo(\"toplevel\");\n\t\tassertThat(kubernetesClient.getConfiguration().getMasterUrl()).isEqualTo(\"http://localhost:8090/\");\n\t\tassertThat(kubernetesClient.getConfiguration().isTrustCerts()).isTrue();\n\t}\n}\n"
  },
  {
    "path": "spring-cloud-deployer-kubernetes/src/test/java/org/springframework/cloud/deployer/spi/kubernetes/KubernetesDeployerPropertiesTests.java",
    "content": "/*\n * Copyright 2021-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.kubernetes;\n\nimport org.junit.jupiter.api.Test;\n\nimport org.springframework.util.StringUtils;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\n/**\n * Tests for {@link KubernetesDeployerProperties}.\n *\n * @author Glenn Renfro\n */\npublic class KubernetesDeployerPropertiesTests {\n\n\t@Test\n\tpublic void testImagePullPolicyDefault() {\n\t\tKubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n\t\tassertThat(kubernetesDeployerProperties.getImagePullPolicy()).as(\"Image pull policy should not be null\").isNotNull();\n\t\tassertEquals(ImagePullPolicy.IfNotPresent,\n\t\t\t\tkubernetesDeployerProperties.getImagePullPolicy(),\n\t\t\t\t\"Invalid default image pull policy\");\n\t}\n\n\t@Test\n\tpublic void testImagePullPolicyCanBeCustomized() {\n\t\tKubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n\t\tkubernetesDeployerProperties.setImagePullPolicy(ImagePullPolicy.Never);\n\t\tassertThat(kubernetesDeployerProperties.getImagePullPolicy()).as(\"Image pull policy should not be null\").isNotNull();\n\t\tassertEquals(ImagePullPolicy.Never,\n\t\t\t\tkubernetesDeployerProperties.getImagePullPolicy(),\n\t\t\t\t\"Unexpected image pull policy\");\n\t}\n\n\t@Test\n\tpublic void testRestartPolicyDefault() {\n\t\tKubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n\t\tassertThat(kubernetesDeployerProperties.getRestartPolicy()).as(\"Restart policy should not be null\").isNotNull();\n\t\tassertEquals(RestartPolicy.Always,\n\t\t\t\tkubernetesDeployerProperties.getRestartPolicy(),\n\t\t\t\t\"Invalid default restart policy\");\n\t}\n\n\t@Test\n\tpublic void testRestartPolicyCanBeCustomized() {\n\t\tKubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n\t\tkubernetesDeployerProperties.setRestartPolicy(RestartPolicy.OnFailure);\n\t\tassertThat(kubernetesDeployerProperties.getRestartPolicy()).as(\"Restart policy should not be null\").isNotNull();\n\t\tassertEquals(RestartPolicy.OnFailure,\n\t\t\t\tkubernetesDeployerProperties.getRestartPolicy(),\n\t\t\t\t\"Unexpected restart policy\");\n\t}\n\n\t@Test\n\tpublic void testEntryPointStyleDefault() {\n\t\tKubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n\t\tassertThat(kubernetesDeployerProperties.getEntryPointStyle()).as(\"Entry point style should not be null\").isNotNull();\n\t\tassertEquals(EntryPointStyle.exec,\n\t\t\t\tkubernetesDeployerProperties.getEntryPointStyle(),\n\t\t\t\t\"Invalid default entry point style\");\n\t}\n\n\t@Test\n\tpublic void testEntryPointStyleCanBeCustomized() {\n\t\tKubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n\t\tkubernetesDeployerProperties.setEntryPointStyle(EntryPointStyle.shell);\n\t\tassertThat(kubernetesDeployerProperties.getEntryPointStyle()).as(\"Entry point style should not be null\").isNotNull();\n\t\tassertEquals(EntryPointStyle.shell,\n\t\t\t\tkubernetesDeployerProperties.getEntryPointStyle(),\n\t\t\t\t\"Unexpected entry point stype\");\n\t}\n\n\t@Test\n\tpublic void testNamespaceDefault() {\n\t\tKubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n\t\tif (kubernetesDeployerProperties.getNamespace() == null) {\n\t\t\tkubernetesDeployerProperties.setNamespace(\"default\");\n\n\t\t\tassertThat(StringUtils.hasText(kubernetesDeployerProperties.getNamespace())).as(\"Namespace should not be empty or null\").isTrue();\n\t\t\tassertEquals(\"default\", kubernetesDeployerProperties.getNamespace(), \"Invalid default namespace\");\n\t\t}\n\t}\n\n\t@Test\n\tpublic void testNamespaceCanBeCustomized() {\n\t\tKubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n\t\tkubernetesDeployerProperties.setNamespace(\"myns\");\n\t\tassertThat(StringUtils.hasText(kubernetesDeployerProperties.getNamespace())).as(\"Namespace should not be empty or null\").isTrue();\n\t\tassertEquals(\"myns\", kubernetesDeployerProperties.getNamespace(), \"Unexpected namespace\");\n\t}\n\n\t@Test\n\tpublic void testImagePullSecretDefault() {\n\t\tKubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n\t\tassertThat(kubernetesDeployerProperties.getImagePullSecret()).as(\"No default image pull secret should be set\").isNull();\n\t}\n\n\t@Test\n\tpublic void testImagePullSecretCanBeCustomized() {\n\t\tString secret = \"mysecret\";\n\t\tKubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n\t\tkubernetesDeployerProperties.setImagePullSecret(secret);\n\t\tassertThat(kubernetesDeployerProperties.getImagePullSecret()).as(\"Image pull secret should not be null\").isNotNull();\n\t\tassertEquals(secret, kubernetesDeployerProperties.getImagePullSecret(), \"Unexpected image pull secret\");\n\t}\n\n\t@Test\n\tpublic void testEnvironmentVariablesDefault() {\n\t\tKubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n\t\tassertEquals(0,\n\t\t\t\tkubernetesDeployerProperties.getEnvironmentVariables().length,\n\t\t\t\t\"No default environment variables should be set\");\n\t}\n\n\t@Test\n\tpublic void testEnvironmentVariablesCanBeCustomized() {\n\t\tString[] envVars = new String[] { \"var1=val1\", \"var2=val2\" };\n\t\tKubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n\t\tkubernetesDeployerProperties.setEnvironmentVariables(envVars);\n\t\tassertThat(kubernetesDeployerProperties.getEnvironmentVariables()).as(\"Environment variables should not be null\").isNotNull();\n\t\tassertEquals(2,\n\t\t\t\tkubernetesDeployerProperties.getEnvironmentVariables().length,\n\t\t\t\t\"Unexpected number of environment variables\");\n\t}\n\n\t@Test\n\tpublic void testTaskServiceAccountNameDefault() {\n\t\tKubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n\t\tassertThat(kubernetesDeployerProperties.getTaskServiceAccountName()).as(\"Task service account name should not be null\").isNotNull();\n\t\tassertEquals(kubernetesDeployerProperties.DEFAULT_TASK_SERVICE_ACCOUNT_NAME,\n\t\t\t\tkubernetesDeployerProperties.getTaskServiceAccountName(),\n\t\t\t\t\"Unexpected default task service account name\");\n\t}\n\n\t@Test\n\tpublic void testTaskServiceAccountNameCanBeCustomized() {\n\t\tString taskServiceAccountName = \"mysa\";\n\t\tKubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n\t\tkubernetesDeployerProperties.setTaskServiceAccountName(taskServiceAccountName);\n\t\tassertThat(kubernetesDeployerProperties.getTaskServiceAccountName()).as(\"Task service account name should not be null\").isNotNull();\n\t\tassertEquals(taskServiceAccountName,\n\t\t\t\tkubernetesDeployerProperties.getTaskServiceAccountName(),\n\t\t\t\t\"Unexpected task service account name\");\n\t}\n}\n"
  },
  {
    "path": "spring-cloud-deployer-kubernetes/src/test/java/org/springframework/cloud/deployer/spi/kubernetes/KubernetesSchedulerIT.java",
    "content": "/*\n * Copyright 2018-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.kubernetes;\n\nimport java.io.IOException;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.UUID;\n\nimport com.fasterxml.jackson.core.type.TypeReference;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport io.fabric8.kubernetes.api.model.Container;\nimport io.fabric8.kubernetes.api.model.EnvVar;\nimport io.fabric8.kubernetes.api.model.LocalObjectReference;\nimport io.fabric8.kubernetes.api.model.ObjectMeta;\nimport io.fabric8.kubernetes.api.model.PodSpec;\nimport io.fabric8.kubernetes.api.model.PodTemplateSpec;\nimport io.fabric8.kubernetes.api.model.Status;\nimport io.fabric8.kubernetes.api.model.StatusCause;\nimport io.fabric8.kubernetes.api.model.StatusDetails;\nimport io.fabric8.kubernetes.api.model.batch.v1.CronJob;\nimport io.fabric8.kubernetes.api.model.batch.v1.CronJobList;\nimport io.fabric8.kubernetes.api.model.batch.v1.CronJobSpec;\nimport io.fabric8.kubernetes.api.model.batch.v1.JobSpec;\nimport io.fabric8.kubernetes.api.model.batch.v1.JobTemplateSpec;\nimport io.fabric8.kubernetes.client.KubernetesClient;\nimport io.fabric8.kubernetes.client.KubernetesClientBuilder;\nimport io.fabric8.kubernetes.client.KubernetesClientException;\nimport org.junit.jupiter.api.AfterAll;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.ValueSource;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.boot.autoconfigure.EnableAutoConfiguration;\nimport org.springframework.boot.context.properties.EnableConfigurationProperties;\nimport org.springframework.boot.test.context.SpringBootTest;\nimport org.springframework.boot.test.context.SpringBootTest.WebEnvironment;\nimport org.springframework.cloud.deployer.resource.docker.DockerResource;\nimport org.springframework.cloud.deployer.spi.core.AppDefinition;\nimport org.springframework.cloud.deployer.spi.scheduler.CreateScheduleException;\nimport org.springframework.cloud.deployer.spi.scheduler.ScheduleInfo;\nimport org.springframework.cloud.deployer.spi.scheduler.ScheduleRequest;\nimport org.springframework.cloud.deployer.spi.scheduler.Scheduler;\nimport org.springframework.cloud.deployer.spi.scheduler.SchedulerPropertyKeys;\nimport org.springframework.cloud.deployer.spi.scheduler.test.AbstractSchedulerIntegrationJUnit5Tests;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.core.io.Resource;\nimport org.springframework.test.context.ContextConfiguration;\nimport org.springframework.test.context.junit.jupiter.SpringExtension;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.assertThatThrownBy;\n\n/**\n * Tests for Kubernetes {@link Scheduler} implementation.\n *\n * @author Chris Schaefer\n * @author Ilayaperumal Gopinathan\n * @author Corneil du Plessis\n */\n@ExtendWith(SpringExtension.class)\n@SpringBootTest(webEnvironment = WebEnvironment.NONE)\n@ContextConfiguration(classes = {KubernetesSchedulerIT.Config.class})\npublic class KubernetesSchedulerIT extends AbstractSchedulerIntegrationJUnit5Tests {\n\n\tprivate static final Logger LOGGER = LoggerFactory.getLogger(KubernetesSchedulerIT.class);\n\n\t@Autowired\n\tprivate Scheduler scheduler;\n\n\t@Autowired\n\tprivate KubernetesClient kubernetesClient;\n\n\t@Override\n\tprotected Scheduler provideScheduler() {\n\t\treturn this.scheduler;\n\t}\n\n\t@Override\n\tprotected List<String> getCommandLineArgs() {\n\t\tList<String> commandLineArguments = new ArrayList<>();\n\t\tcommandLineArguments.add(\"arg1=value1\");\n\t\tcommandLineArguments.add(\"arg2=value2\");\n\n\t\treturn commandLineArguments;\n\t}\n\n\t@Override\n\tprotected Map<String, String> getSchedulerProperties() {\n\t\treturn Collections.singletonMap(SchedulerPropertyKeys.CRON_EXPRESSION, \"57 13 ? * *\");\n\t}\n\n\n\tprivate Map<String, String> getSchedulerProperties(String concurrencyPolicy) {\n\t\tMap<String, String> schedulerProperties = new HashMap<>(getSchedulerProperties());\n\t\tschedulerProperties.put(KubernetesScheduler.KUBERNETES_DEPLOYER_CRON_CONCURRENCY_POLICY, concurrencyPolicy);\n\t\treturn schedulerProperties;\n\t}\n\n\t@Override\n\tprotected Map<String, String> getDeploymentProperties() {\n\t\treturn Collections.singletonMap(SchedulerPropertyKeys.CRON_EXPRESSION, \"57 13 ? * *\");\n\t}\n\n\t@Override\n\tprotected Map<String, String> getAppProperties() {\n\t\tMap<String, String> applicationProperties = new HashMap<>();\n\t\tapplicationProperties.put(\"prop.1.key\", \"prop.1.value\");\n\t\tapplicationProperties.put(\"prop.2.key\", \"prop.2.value\");\n\n\t\treturn applicationProperties;\n\t}\n\n\t@Override\n\t// schedule name must match \"^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\" and size must be between 0\n\t// and 63\n\tprotected String randomName() {\n\t\treturn UUID.randomUUID().toString().substring(0, 18);\n\t}\n\n\t@Override\n\t// schedule name must match \"^[a-z0-9]([-a-z0-9]*[a-z0-9])?$\" and size must be between 0\n\t// and 63\n\tprotected String scheduleName() {\n\t\treturn \"schedulename-\";\n\t}\n\n\tprotected Resource testApplication() {\n\t\treturn new DockerResource(\"springcloud/spring-cloud-deployer-spi-scheduler-test-app:latest\");\n\t}\n\n\t@Test\n\tpublic void test() {\n\t\tsuper.testListFilter();\n\t}\n\n\t@ParameterizedTest\n\t@ValueSource(booleans = {true, false})\n\tpublic void testMissingSchedule(boolean isDeprecated) {\n\t\tAppDefinition appDefinition = new AppDefinition(randomName(), null);\n\t\tScheduleRequest scheduleRequest = (isDeprecated) ?\n\t\t\t\tnew ScheduleRequest(appDefinition, null, null, null, null, testApplication()) :\n\t\t\t\tnew ScheduleRequest(appDefinition, null, (List<String>) null, null, testApplication());\n\n\t\tassertThatThrownBy(() -> {\n\t\t\tscheduler.schedule(scheduleRequest);\n\t\t}).isInstanceOf(CreateScheduleException.class);\n\t}\n\n\t@ParameterizedTest\n\t@ValueSource(booleans = {true, false})\n\tpublic void testInvalidNameSchedule(boolean isDeprecated) {\n\t\tAppDefinition appDefinition = new AppDefinition(\"AAAAAA\", null);\n\t\tScheduleRequest scheduleRequest = (isDeprecated) ?\n\t\t\t\tnew ScheduleRequest(appDefinition, null, null, null, \"AAAAA\", testApplication()) :\n\t\t\t\tnew ScheduleRequest(appDefinition, null, (List<String>) null, \"AAAAA\", testApplication());\n\n\t\tassertThatThrownBy(() -> {\n\t\t\tscheduler.schedule(scheduleRequest);\n\t\t}).isInstanceOf(CreateScheduleException.class);\n\t}\n\n\t@Test\n\tpublic void testSchedulerPropertiesMerge() {\n\t\tfinal String baseScheduleName = \"test-schedule1\";\n\t\tMap<String, String> schedulerProperties = new HashMap<>();\n\t\tschedulerProperties.put(SchedulerPropertyKeys.CRON_EXPRESSION, \"0/10 * * * *\");\n\t\tschedulerProperties.put(KubernetesSchedulerProperties.KUBERNETES_SCHEDULER_PROPERTIES_PREFIX + \".imagePullPolicy\", \"Never\");\n\t\tMap<String, String> deploymentProperties = new HashMap<>();\n\t\tdeploymentProperties.put(KubernetesDeployerProperties.KUBERNETES_DEPLOYER_PROPERTIES_PREFIX + \".environmentVariables\", \"MYVAR1=MYVAL1,MYVAR2=MYVAL2\");\n\t\tdeploymentProperties.put(KubernetesDeployerProperties.KUBERNETES_DEPLOYER_PROPERTIES_PREFIX + \".imagePullPolicy\", \"Always\");\n\t\tAppDefinition appDefinition = new AppDefinition(randomName(), null);\n\t\tScheduleRequest scheduleRequest = new ScheduleRequest(appDefinition, schedulerProperties, deploymentProperties, null,\n\t\t\t\tbaseScheduleName, testApplication());\n\n\t\tMap<String, String> mergedProperties = KubernetesScheduler.mergeSchedulerProperties(scheduleRequest);\n\n\t\tassertThat(mergedProperties\n\t\t\t\t.get(KubernetesDeployerProperties.KUBERNETES_DEPLOYER_PROPERTIES_PREFIX + \".imagePullPolicy\"))\n\t\t\t\t.as(\"Expected value from Scheduler properties, but found in Deployer properties\")\n\t\t\t\t.isEqualTo(\"Never\");\n\t\tassertThat(mergedProperties\n\t\t\t\t.get(KubernetesDeployerProperties.KUBERNETES_DEPLOYER_PROPERTIES_PREFIX + \".environmentVariables\"))\n\t\t\t\t.as(\"Deployer property is expected to be merged as scheduler property\")\n\t\t\t\t.isEqualTo(\"MYVAR1=MYVAL1,MYVAR2=MYVAL2\");\n\t}\n\n\t@Test\n\tpublic void listScheduleWithExternalCronJobs() {\n\t\tCronJobList cronJobList = new CronJobList();\n\t\tCronJobSpec cronJobSpec = new CronJobSpec();\n\t\tJobTemplateSpec jobTemplateSpec = new JobTemplateSpec();\n\t\tJobSpec jobSpec = new JobSpec();\n\t\tPodTemplateSpec podTemplateSpec = new PodTemplateSpec();\n\t\tPodSpec podSpec = new PodSpec();\n\t\tContainer container = new Container();\n\t\tcontainer.setName(\"test\");\n\t\tcontainer.setImage(\"busybox\");\n\t\tpodSpec.setContainers(List.of(container));\n\t\tpodSpec.setRestartPolicy(\"OnFailure\");\n\t\tpodTemplateSpec.setSpec(podSpec);\n\t\tjobSpec.setTemplate(podTemplateSpec);\n\t\tjobTemplateSpec.setSpec(jobSpec);\n\t\tcronJobSpec.setJobTemplate(jobTemplateSpec);\n\t\tcronJobSpec.setSchedule(\"0/10 * * * *\");\n\n\t\tCronJob cronJob1 = new CronJob();\n\t\tObjectMeta objectMeta1 = new ObjectMeta();\n\t\tMap<String, String> labels = new HashMap<>();\n\t\tlabels.put(\"spring-cronjob-id\", \"test\");\n\t\tobjectMeta1.setLabels(labels);\n\t\tobjectMeta1.setName(\"job1\");\n\t\tcronJob1.setMetadata(objectMeta1);\n\t\tcronJob1.setSpec(cronJobSpec);\n\t\tObjectMeta objectMeta2 = new ObjectMeta();\n\t\tobjectMeta2.setName(\"job2\");\n\t\tCronJob cronJob2 = new CronJob();\n\t\tcronJob2.setSpec(cronJobSpec);\n\t\tcronJob2.setMetadata(objectMeta2);\n\t\tObjectMeta objectMeta3 = new ObjectMeta();\n\t\tobjectMeta3.setName(\"job3\");\n\t\tCronJob cronJob3 = new CronJob();\n\t\tcronJob3.setSpec(cronJobSpec);\n\t\tcronJob3.setMetadata(objectMeta3);\n\t\tcronJobList.setItems(Arrays.asList(cronJob1, cronJob2, cronJob3));\n\t\tthis.kubernetesClient.batch().v1().cronjobs().inNamespace(\"default\").resource(cronJob1).create();\n\t\tthis.kubernetesClient.batch().v1().cronjobs().inNamespace(\"default\").resource(cronJob2).create();\n\t\tthis.kubernetesClient.batch().v1().cronjobs().inNamespace(\"default\").resource(cronJob3).create();\n\t\tList<ScheduleInfo> scheduleInfos = this.scheduler.list();\n\t\tassertThat(scheduleInfos.size()).isEqualTo(1);\n\t\tassertThat(scheduleInfos.get(0).getScheduleName()).isEqualTo(\"job1\");\n\t}\n\n\t@ParameterizedTest\n\t@ValueSource(booleans = {true, false})\n\tpublic void testInvalidCronSyntax(boolean isDeprecated) {\n\t\tMap<String, String> schedulerProperties = Collections.singletonMap(SchedulerPropertyKeys.CRON_EXPRESSION, \"1 2 3 4\");\n\n\t\tAppDefinition appDefinition = new AppDefinition(randomName(), null);\n\t\tScheduleRequest scheduleRequest = (isDeprecated) ? new ScheduleRequest(appDefinition, schedulerProperties, null, null, randomName(), testApplication()) :\n\t\t\t\tnew ScheduleRequest(appDefinition, schedulerProperties, (List<String>) null, randomName(), testApplication());\n\n\t\tassertThatThrownBy(() -> {\n\t\t\tscheduler.schedule(scheduleRequest);\n\t\t}).isInstanceOf(CreateScheduleException.class);\n\t}\n\n\t@ParameterizedTest\n\t@ValueSource(booleans = {true, false})\n\tpublic void testNameTooLong(boolean isDeprecated) {\n\t\tfinal String baseScheduleName = (isDeprecated) ? \"tencharlng-scdf-itcouldbesaidthatthisislongtoowayold\" :\n\t\t\t\t\"tencharlng-scdf-itcouldbesaidthatthisislongtoowaytoo\";\n\t\tMap<String, String> schedulerProperties = Collections.singletonMap(SchedulerPropertyKeys.CRON_EXPRESSION, \"0/10 * * * *\");\n\n\t\tAppDefinition appDefinition = new AppDefinition(randomName(), null);\n\t\tScheduleRequest scheduleRequest = (isDeprecated) ? new ScheduleRequest(appDefinition, schedulerProperties, null, null, baseScheduleName, testApplication()) :\n\t\t\t\tnew ScheduleRequest(appDefinition, schedulerProperties, (List<String>) null, baseScheduleName, testApplication());\n\n\t\t//verify no validation fired.\n\t\tscheduler.schedule(scheduleRequest);\n\n\t\tScheduleRequest scheduleRequest2 = (isDeprecated) ? new ScheduleRequest(appDefinition, schedulerProperties, null, null, baseScheduleName + \"1\", testApplication()) :\n\t\t\t\tnew ScheduleRequest(appDefinition, schedulerProperties, (List<String>) null, baseScheduleName + \"1\", testApplication());\n\t\tassertThatThrownBy(() -> {\n\t\t\tscheduler.schedule(scheduleRequest2);\n\t\t}).isInstanceOf(CreateScheduleException.class)\n\t\t\t\t.hasMessage(String.format(\"Failed to create schedule because Schedule Name: '%s' has too many characters.  Schedule name length must be 52 characters or less\", baseScheduleName + \"1\"));\n\t}\n\n\t@ParameterizedTest\n\t@ValueSource(booleans = {true, false})\n\tpublic void testWithExecEntryPoint(boolean isDeprecated) {\n\t\tKubernetesDeployerProperties kubernetesDeployerProperties =\n\t\t\t\tnew KubernetesDeployerProperties();\n\t\tif (kubernetesDeployerProperties.getNamespace() == null) {\n\t\t\tkubernetesDeployerProperties.setNamespace(\"default\");\n\t\t}\n\t\tkubernetesDeployerProperties.setEntryPointStyle(EntryPointStyle.exec);\n\n\t\tKubernetesClient kubernetesClient = (new KubernetesClientBuilder()).build();\n\n\t\tKubernetesScheduler kubernetesScheduler = new KubernetesScheduler(kubernetesClient,\n\t\t\t\tkubernetesDeployerProperties);\n\n\t\tAppDefinition appDefinition = new AppDefinition(randomName(), getAppProperties());\n\t\tScheduleRequest scheduleRequest = (isDeprecated) ? new ScheduleRequest(appDefinition, getSchedulerProperties(), null, getCommandLineArgs(), randomName(), testApplication()) :\n\t\t\t\tnew ScheduleRequest(appDefinition, getDeploymentProperties(), getCommandLineArgs(), randomName(), testApplication());\n\n\t\tCronJob cronJob = kubernetesScheduler.createCronJob(scheduleRequest);\n\t\tCronJobSpec cronJobSpec = cronJob.getSpec();\n\n\t\tContainer container = cronJobSpec.getJobTemplate().getSpec().getTemplate().getSpec().getContainers().get(0);\n\n\t\tassertThat(container.getArgs()).as(\"Command line arguments should not be null\").isNotNull();\n\t\tassertThat(container.getEnv()).as(\"Environment variables should not be null\").isNotNull();\n\t\tassertThat(container.getEnv()).as(\"Environment variables should only have SPRING_CLOUD_APPLICATION_GUID\")\n\t\t\t\t.hasSize(1);\n\n\t\tsafeUnschedule(kubernetesScheduler, cronJob.getMetadata().getName());\n\t}\n\n\t@ParameterizedTest\n\t@ValueSource(booleans = {true, false})\n\tpublic void testWithShellEntryPoint(boolean isDeprecated) {\n\t\tKubernetesDeployerProperties kubernetesDeployerProperties =\n\t\t\t\tnew KubernetesDeployerProperties();\n\t\tif (kubernetesDeployerProperties.getNamespace() == null) {\n\t\t\tkubernetesDeployerProperties.setNamespace(\"default\");\n\t\t}\n\t\tkubernetesDeployerProperties.setEntryPointStyle(EntryPointStyle.shell);\n\n\t\tKubernetesClient kubernetesClient = (new KubernetesClientBuilder()).build();\n\n\t\tKubernetesScheduler kubernetesScheduler = new KubernetesScheduler(kubernetesClient,\n\t\t\t\tkubernetesDeployerProperties);\n\n\t\tAppDefinition appDefinition = new AppDefinition(randomName(), getAppProperties());\n\t\tScheduleRequest scheduleRequest = (isDeprecated) ? new ScheduleRequest(appDefinition, getSchedulerProperties(), null, getCommandLineArgs(), randomName(), testApplication()) :\n\t\t\t\tnew ScheduleRequest(appDefinition, getSchedulerProperties(), getCommandLineArgs(), randomName(), testApplication());\n\t\tCronJob cronJob = kubernetesScheduler.createCronJob(scheduleRequest);\n\t\tCronJobSpec cronJobSpec = cronJob.getSpec();\n\n\t\tContainer container = cronJobSpec.getJobTemplate().getSpec().getTemplate().getSpec().getContainers().get(0);\n\n\t\tassertThat(container.getArgs()).as(\"Command line arguments should not be null\").isNotNull();\n\t\tassertThat(container.getArgs()).as(\"Invalid number of command line arguments\").isEmpty();\n\n\t\tassertThat(container.getEnv()).as(\"Environment variables should not be null\").isNotNull();\n\t\tassertThat(container.getEnv()).as(\"Invalid number of environment variables\").hasSizeGreaterThan(1);\n\n\t\tsafeUnschedule(kubernetesScheduler, cronJob.getMetadata().getName());\n\t}\n\n\t@ParameterizedTest\n\t@ValueSource(booleans = {true, false})\n\tpublic void testWithBootEntryPoint(boolean isDeprecated) throws IOException {\n\t\tKubernetesDeployerProperties kubernetesDeployerProperties =\n\t\t\t\tnew KubernetesDeployerProperties();\n\t\tkubernetesDeployerProperties.setEntryPointStyle(EntryPointStyle.boot);\n\t\tif (kubernetesDeployerProperties.getNamespace() == null) {\n\t\t\tkubernetesDeployerProperties.setNamespace(\"default\");\n\t\t}\n\t\tKubernetesClient kubernetesClient = (new KubernetesClientBuilder()).build();\n\n\t\tKubernetesScheduler kubernetesScheduler = new KubernetesScheduler(kubernetesClient,\n\t\t\t\tkubernetesDeployerProperties);\n\n\t\tAppDefinition appDefinition = new AppDefinition(randomName(), getAppProperties());\n\t\tScheduleRequest scheduleRequest = (isDeprecated) ? new ScheduleRequest(appDefinition, getSchedulerProperties(), null, getCommandLineArgs(), randomName(), testApplication()) :\n\t\t\t\tnew ScheduleRequest(appDefinition, getSchedulerProperties(), getCommandLineArgs(), randomName(), testApplication());\n\t\tCronJob cronJob = kubernetesScheduler.createCronJob(scheduleRequest);\n\t\tCronJobSpec cronJobSpec = cronJob.getSpec();\n\n\t\tContainer container = cronJobSpec.getJobTemplate().getSpec().getTemplate().getSpec().getContainers().get(0);\n\n\t\tassertThat(container.getArgs()).as(\"Command line arguments should not be null\").isNotNull();\n\t\tassertThat(container.getArgs()).as(\"Invalid number of command line arguments\").hasSize(2);\n\n\t\tassertThat(container.getEnv()).as(\"Environment variables should not be null\").isNotNull();\n\t\tassertThat(container.getEnv()).as(\"Invalid number of environment variables\").hasSizeGreaterThan(1);\n\n\t\tString springApplicationJson = container.getEnv().get(0).getValue();\n\n\t\tMap<String, String> springApplicationJsonValues = new ObjectMapper().readValue(springApplicationJson,\n\t\t\t\tnew TypeReference<HashMap<String, String>>() {\n\t\t\t\t});\n\n\t\tassertThat(springApplicationJsonValues).as(\"SPRING_APPLICATION_JSON should not be null\").isNotNull();\n\t\tassertThat(springApplicationJsonValues).as(\"Invalid number of SPRING_APPLICATION_JSON entries\").hasSize(2);\n\n\t\tsafeUnschedule(kubernetesScheduler, cronJob.getMetadata().getName());\n\t}\n\n\t@Test\n\tpublic void testGetExceptionMessageForExistingField() {\n\t\tStatusCause statusCause = new StatusCause(\"spec.schedule\", null, null);\n\t\tStatusDetails statusDetails = new StatusDetails();\n\t\tstatusDetails.setCauses(Collections.singletonList(statusCause));\n\n\t\tStatus status = new Status();\n\t\tstatus.setCode(0);\n\t\tstatus.setMessage(\"invalid cron expression\");\n\t\tstatus.setDetails(statusDetails);\n\n\t\tKubernetesClientException kubernetesClientException = new KubernetesClientException(status);\n\t\tString message = ((KubernetesScheduler) scheduler).getExceptionMessageForField(kubernetesClientException,\n\t\t\t\t\"spec.schedule\");\n\n\t\tassertThat(message).as(\"Field message should not be null\").isNotNull();\n\t\tassertThat(message).as(\"Invalid message for field\").isEqualTo(\"invalid cron expression\");\n\t}\n\n\t@Test\n\tpublic void testGetExceptionMessageForNonExistentField() {\n\t\tStatusCause statusCause = new StatusCause(\"spec.schedule\", null, null);\n\t\tStatusDetails statusDetails = new StatusDetails();\n\t\tstatusDetails.setCauses(Collections.singletonList(statusCause));\n\n\t\tStatus status = new Status();\n\t\tstatus.setCode(0);\n\t\tstatus.setMessage(\"invalid cron expression\");\n\t\tstatus.setDetails(statusDetails);\n\n\t\tKubernetesClientException kubernetesClientException = new KubernetesClientException(status);\n\t\tString message = ((KubernetesScheduler) scheduler).getExceptionMessageForField(kubernetesClientException,\n\t\t\t\t\"spec.restartpolicy\");\n\n\t\tassertThat(message).as(\"Field message should be null\").isNull();\n\t}\n\n\t@ParameterizedTest\n\t@ValueSource(booleans = {true, false})\n\tpublic void testEntryPointStyleOverride(boolean isDeprecated) throws Exception {\n\t\tKubernetesDeployerProperties kubernetesDeployerProperties =\n\t\t\t\tnew KubernetesDeployerProperties();\n\t\tif (kubernetesDeployerProperties.getNamespace() == null) {\n\t\t\tkubernetesDeployerProperties.setNamespace(\"default\");\n\t\t}\n\t\tKubernetesClient kubernetesClient = (new KubernetesClientBuilder()).build();\n\n\t\tKubernetesScheduler kubernetesScheduler = new KubernetesScheduler(kubernetesClient,\n\t\t\t\tkubernetesDeployerProperties);\n\n\t\tString prefix = KubernetesSchedulerProperties.KUBERNETES_SCHEDULER_PROPERTIES_PREFIX;\n\n\t\tMap<String, String> schedulerProperties = new HashMap<>(getSchedulerProperties());\n\t\tschedulerProperties.put(prefix + \".entryPointStyle\", \"boot\");\n\n\t\tAppDefinition appDefinition = new AppDefinition(randomName(), getAppProperties());\n\t\tScheduleRequest scheduleRequest = (isDeprecated) ? new ScheduleRequest(appDefinition, schedulerProperties, null, getCommandLineArgs(), randomName(), testApplication()) :\n\t\t\t\tnew ScheduleRequest(appDefinition, schedulerProperties, getCommandLineArgs(), randomName(), testApplication());\n\n\t\tCronJob cronJob = kubernetesScheduler.createCronJob(scheduleRequest);\n\t\tCronJobSpec cronJobSpec = cronJob.getSpec();\n\n\t\tContainer container = cronJobSpec.getJobTemplate().getSpec().getTemplate().getSpec().getContainers().get(0);\n\n\t\tassertThat(container.getEnv()).as(\"Invalid number of environment variables\").hasSizeGreaterThan(1);\n\n\t\tString springApplicationJson = container.getEnv().get(0).getValue();\n\n\t\tMap<String, String> springApplicationJsonValues = new ObjectMapper().readValue(springApplicationJson,\n\t\t\t\tnew TypeReference<HashMap<String, String>>() {\n\t\t\t\t});\n\n\t\tassertThat(springApplicationJsonValues).as(\"SPRING_APPLICATION_JSON should not be null\").isNotNull();\n\t\tassertThat(springApplicationJsonValues).as(\"Invalid number of SPRING_APPLICATION_JSON entries\").hasSize(2);\n\n\t\tsafeUnschedule(kubernetesScheduler, cronJob.getMetadata().getName());\n\t}\n\n\t@ParameterizedTest\n\t@ValueSource(booleans = {true, false})\n\tpublic void testEntryPointStyleDefault(boolean isDeprecated) {\n\t\tKubernetesDeployerProperties kubernetesDeployerProperties =\n\t\t\t\tnew KubernetesDeployerProperties();\n\t\tif (kubernetesDeployerProperties.getNamespace() == null) {\n\t\t\tkubernetesDeployerProperties.setNamespace(\"default\");\n\t\t}\n\t\tKubernetesClient kubernetesClient = (new KubernetesClientBuilder()).build();\n\n\t\tKubernetesScheduler kubernetesScheduler = new KubernetesScheduler(kubernetesClient,\n\t\t\t\tkubernetesDeployerProperties);\n\n\t\tAppDefinition appDefinition = new AppDefinition(randomName(), getAppProperties());\n\t\tScheduleRequest scheduleRequest = (isDeprecated) ? new ScheduleRequest(appDefinition, getSchedulerProperties(), Collections.emptyMap(), getCommandLineArgs(), randomName(), testApplication()) :\n\t\t\t\tnew ScheduleRequest(appDefinition, getDeploymentProperties(), getCommandLineArgs(), randomName(), testApplication());\n\n\t\tCronJob cronJob = kubernetesScheduler.createCronJob(scheduleRequest);\n\t\tCronJobSpec cronJobSpec = cronJob.getSpec();\n\n\t\tContainer container = cronJobSpec.getJobTemplate().getSpec().getTemplate().getSpec().getContainers().get(0);\n\n\t\tassertThat(container.getEnv()).as(\"Environment variables should only have SPRING_CLOUD_APPLICATION_GUID\")\n\t\t\t\t.hasSize(1);\n\t\tassertThat(container.getArgs()).as(\"Command line arguments should not be empty\").isNotEmpty();\n\n\t\tsafeUnschedule(kubernetesScheduler, cronJob.getMetadata().getName());\n\t}\n\n\t@ParameterizedTest\n\t@ValueSource(booleans = {true, false})\n\tpublic void testImagePullPolicyOverride(boolean isDeprecated) {\n\t\tKubernetesDeployerProperties kubernetesDeployerProperties =\n\t\t\t\tnew KubernetesDeployerProperties();\n\t\tif (kubernetesDeployerProperties.getNamespace() == null) {\n\t\t\tkubernetesDeployerProperties.setNamespace(\"default\");\n\t\t}\n\t\tKubernetesClient kubernetesClient = (new KubernetesClientBuilder()).build();\n\n\t\tKubernetesScheduler kubernetesScheduler = new KubernetesScheduler(kubernetesClient,\n\t\t\t\tkubernetesDeployerProperties);\n\n\t\tString prefix = KubernetesSchedulerProperties.KUBERNETES_SCHEDULER_PROPERTIES_PREFIX;\n\n\t\tMap<String, String> schedulerProperties = new HashMap<>(getSchedulerProperties());\n\t\tschedulerProperties.put(prefix + \".imagePullPolicy\", \"Always\");\n\n\t\tAppDefinition appDefinition = new AppDefinition(randomName(), getAppProperties());\n\t\tScheduleRequest scheduleRequest = (isDeprecated) ? new ScheduleRequest(appDefinition, schedulerProperties, null, getCommandLineArgs(), randomName(), testApplication()) :\n\t\t\t\tnew ScheduleRequest(appDefinition, schedulerProperties, getCommandLineArgs(), randomName(), testApplication());\n\n\t\tCronJob cronJob = kubernetesScheduler.createCronJob(scheduleRequest);\n\t\tCronJobSpec cronJobSpec = cronJob.getSpec();\n\n\t\tContainer container = cronJobSpec.getJobTemplate().getSpec().getTemplate().getSpec().getContainers().get(0);\n\n\t\tassertThat(container.getImagePullPolicy()).as(\"Unexpected image pull policy\").isEqualTo(\"Always\");\n\n\t\tsafeUnschedule(kubernetesScheduler, cronJob.getMetadata().getName());\n\t}\n\n\t@ParameterizedTest\n\t@ValueSource(booleans = {true, false})\n\tpublic void testJobAnnotationsAndLabelsFromSchedulerProperties(boolean isDeprecated) {\n\t\tKubernetesDeployerProperties kubernetesDeployerProperties =\n\t\t\t\tnew KubernetesDeployerProperties();\n\t\tkubernetesDeployerProperties.setJobAnnotations(\"test1:value1\");\n\t\tkubernetesDeployerProperties.setPodAnnotations(\"podtest1:podvalue1\");\n\t\tkubernetesDeployerProperties.setDeploymentLabels(\"label1:value1,label2:value2\");\n\t\tif (kubernetesDeployerProperties.getNamespace() == null) {\n\t\t\tkubernetesDeployerProperties.setNamespace(\"default\");\n\t\t}\n\t\tKubernetesClient kubernetesClient = (new KubernetesClientBuilder()).build();\n\n\t\tKubernetesScheduler kubernetesScheduler = new KubernetesScheduler(kubernetesClient,\n\t\t\t\tkubernetesDeployerProperties);\n\n\t\tAppDefinition appDefinition = new AppDefinition(randomName(), getAppProperties());\n\t\tScheduleRequest scheduleRequest = (isDeprecated) ? new ScheduleRequest(appDefinition, getSchedulerProperties(), null, getCommandLineArgs(), randomName(), testApplication()) :\n\t\t\t\tnew ScheduleRequest(appDefinition, getSchedulerProperties(), getCommandLineArgs(), randomName(), testApplication());\n\n\t\tCronJob cronJob = kubernetesScheduler.createCronJob(scheduleRequest);\n\n\t\tassertThat(cronJob.getMetadata().getAnnotations().get(\"test1\")).as(\"Job annotation is not set\")\n\t\t\t\t.isEqualTo(\"value1\");\n\t\tassertThat(cronJob.getSpec().getJobTemplate().getSpec().getTemplate().getMetadata().getAnnotations()\n\t\t\t\t.get(\"podtest1\")).as(\"Pod annotation is not set\").isEqualTo(\"podvalue1\");\n\t\tassertThat(cronJob.getSpec().getJobTemplate().getSpec().getTemplate().getMetadata().getLabels()\n\t\t\t\t.get(\"label1\")).as(\"Pod Label1 is not set\").isEqualTo(\"value1\");\n\t\tassertThat(cronJob.getSpec().getJobTemplate().getSpec().getTemplate().getMetadata().getLabels()\n\t\t\t\t.get(\"label2\")).as(\"Pod Label2 is not set\").isEqualTo(\"value2\");\n\n\t\tsafeUnschedule(kubernetesScheduler, cronJob.getMetadata().getName());\n\t}\n\n\t@Test\n\tpublic void testDefaultLabel() {\n\t\tKubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n\t\tif (kubernetesDeployerProperties.getNamespace() == null) {\n\t\t\tkubernetesDeployerProperties.setNamespace(\"default\");\n\t\t}\n\t\tKubernetesClient kubernetesClient = (new KubernetesClientBuilder()).build();\n\n\t\tKubernetesScheduler kubernetesScheduler = new KubernetesScheduler(kubernetesClient,\n\t\t\t\tkubernetesDeployerProperties);\n\n\t\tAppDefinition appDefinition = new AppDefinition(randomName(), getAppProperties());\n\t\tScheduleRequest scheduleRequest = new ScheduleRequest(appDefinition, getSchedulerProperties(),\n\t\t\t\tnull, getCommandLineArgs(), randomName(), testApplication());\n\n\t\tCronJob cronJob = kubernetesScheduler.createCronJob(scheduleRequest);\n\n\t\tassertThat(cronJob.getSpec().getJobTemplate().getSpec().getTemplate().getMetadata().getAnnotations()).isEmpty();\n\t\tassertThat(cronJob.getSpec().getJobTemplate().getSpec().getTemplate().getMetadata().getLabels()\n\t\t\t\t.size()).as(\"Should have one label\").isEqualTo(1);\n\t\tassertThat(cronJob.getSpec().getJobTemplate().getSpec().getTemplate().getMetadata().getLabels().get(\n\t\t\t\tKubernetesScheduler.SPRING_CRONJOB_ID_KEY)).as(\"Default label is not set\").isNotNull();\n\t\tsafeUnschedule(kubernetesScheduler, cronJob.getMetadata().getName());\n\t}\n\n\t@ParameterizedTest\n\t@ValueSource(booleans = {true, false})\n\tpublic void testJobAnnotationsAndLabelsFromSchedulerRequest(boolean isDeprecated) {\n\t\tKubernetesDeployerProperties kubernetesDeployerProperties =\n\t\t\t\tnew KubernetesDeployerProperties();\n\t\tkubernetesDeployerProperties.setJobAnnotations(\"test1:value1\");\n\t\tkubernetesDeployerProperties.setPodAnnotations(\"podtest1:podvalue1\");\n\t\tkubernetesDeployerProperties.setDeploymentLabels(\"label1:value1,label2:value2\");\n\t\tif (kubernetesDeployerProperties.getNamespace() == null) {\n\t\t\tkubernetesDeployerProperties.setNamespace(\"default\");\n\t\t}\n\t\tKubernetesClient kubernetesClient = (new KubernetesClientBuilder()).build();\n\n\t\tKubernetesScheduler kubernetesScheduler = new KubernetesScheduler(kubernetesClient,\n\t\t\t\tkubernetesDeployerProperties);\n\n\t\tAppDefinition appDefinition = new AppDefinition(randomName(), getAppProperties());\n\t\tMap<String, String> scheduleProperties = new HashMap<>();\n\t\tscheduleProperties.putAll(getSchedulerProperties());\n\t\tif (isDeprecated) {\n\t\t\tscheduleProperties.put(\"spring.cloud.scheduler.kubernetes.deploymentLabels\", \"requestLabel1:requestValue1,requestLabel2:requestValue2\");\n\t\t\tscheduleProperties.put(\"spring.cloud.scheduler.kubernetes.podAnnotations\", \"requestPod1:requestPodValue1\");\n\t\t}\n\t\telse {\n\t\t\tscheduleProperties.put(\"spring.cloud.deployer.kubernetes.deploymentLabels\", \"requestLabel1:requestValue1,requestLabel2:requestValue2\");\n\t\t\tscheduleProperties.put(\"spring.cloud.deployer.kubernetes.podAnnotations\", \"requestPod1:requestPodValue1\");\n\t\t}\n\t\tScheduleRequest scheduleRequest = (isDeprecated) ? new ScheduleRequest(appDefinition, scheduleProperties, null, getCommandLineArgs(), randomName(), testApplication()) :\n\t\t\t\tnew ScheduleRequest(appDefinition, scheduleProperties, getCommandLineArgs(), randomName(), testApplication());\n\t\tCronJob cronJob = kubernetesScheduler.createCronJob(scheduleRequest);\n\n\t\tassertThat(cronJob.getMetadata().getAnnotations().get(\"test1\")).as(\"Job annotation is not set\")\n\t\t\t\t.isEqualTo(\"value1\");\n\t\t// Pod annotation from the request should override the top level property values\n\t\tassertThat(cronJob.getSpec().getJobTemplate().getSpec().getTemplate().getMetadata().getAnnotations()\n\t\t\t\t.get(\"requestPod1\")).as(\"Pod annotation is not set\").isEqualTo(\"requestPodValue1\");\n\t\t// Deployment label from the request should get appended to the top level property values\n\t\tassertThat(cronJob.getSpec().getJobTemplate().getSpec().getTemplate().getMetadata().getLabels()\n\t\t\t\t.get(\"requestLabel1\")).as(\"Pod Label1 from the request is not set\").isEqualTo(\"requestValue1\");\n\t\tassertThat(cronJob.getSpec().getJobTemplate().getSpec().getTemplate().getMetadata().getLabels()\n\t\t\t\t.get(\"requestLabel2\")).as(\"Pod Label2 from the request is not set\").isEqualTo(\"requestValue2\");\n\t\tassertThat(cronJob.getSpec().getJobTemplate().getSpec().getTemplate().getMetadata().getLabels()\n\t\t\t\t.get(\"label1\")).as(\"Pod Label1 is not set\").isEqualTo(\"value1\");\n\t\tassertThat(cronJob.getSpec().getJobTemplate().getSpec().getTemplate().getMetadata().getLabels()\n\t\t\t\t.get(\"label2\")).as(\"Pod Label2 is not set\").isEqualTo(\"value2\");\n\n\t\tsafeUnschedule(kubernetesScheduler, cronJob.getMetadata().getName());\n\t}\n\n\t@ParameterizedTest\n\t@ValueSource(booleans = {true, false})\n\tpublic void testJobAnnotationsOverride(boolean isDeprecated) {\n\t\tKubernetesDeployerProperties kubernetesDeployerProperties =\n\t\t\t\tnew KubernetesDeployerProperties();\n\t\tkubernetesDeployerProperties.setJobAnnotations(\"test1:value1\");\n\t\tif (kubernetesDeployerProperties.getNamespace() == null) {\n\t\t\tkubernetesDeployerProperties.setNamespace(\"default\");\n\t\t}\n\t\tKubernetesClient kubernetesClient = (new KubernetesClientBuilder()).build();\n\n\t\tKubernetesScheduler kubernetesScheduler = new KubernetesScheduler(kubernetesClient,\n\t\t\t\tkubernetesDeployerProperties);\n\n\t\tAppDefinition appDefinition = new AppDefinition(randomName(), getAppProperties());\n\t\tString prefix = KubernetesSchedulerProperties.KUBERNETES_SCHEDULER_PROPERTIES_PREFIX;\n\t\tMap<String, String> schedulerProperties = new HashMap<>(getSchedulerProperties());\n\t\tschedulerProperties.put(prefix + \".jobAnnotations\", \"test1:value2\");\n\n\t\tScheduleRequest scheduleRequest = (isDeprecated) ? new ScheduleRequest(appDefinition, schedulerProperties, null, getCommandLineArgs(), randomName(), testApplication()) :\n\t\t\t\tnew ScheduleRequest(appDefinition, schedulerProperties, getCommandLineArgs(), randomName(), testApplication());\n\n\t\tCronJob cronJob = kubernetesScheduler.createCronJob(scheduleRequest);\n\n\t\tassertThat(cronJob.getMetadata().getAnnotations().get(\"test1\")).as(\"Job annotation is not set\")\n\t\t\t\t.isEqualTo(\"value2\");\n\n\t\tsafeUnschedule(kubernetesScheduler, cronJob.getMetadata().getName());\n\t}\n\n\t@ParameterizedTest\n\t@ValueSource(booleans = {true, false})\n\tpublic void testImagePullPolicyDefault(boolean isDeprecated) {\n\t\tKubernetesDeployerProperties kubernetesDeployerProperties =\n\t\t\t\tnew KubernetesDeployerProperties();\n\t\tif (kubernetesDeployerProperties.getNamespace() == null) {\n\t\t\tkubernetesDeployerProperties.setNamespace(\"default\");\n\t\t}\n\t\tKubernetesClient kubernetesClient = (new KubernetesClientBuilder()).build();\n\n\t\tKubernetesScheduler kubernetesScheduler = new KubernetesScheduler(kubernetesClient,\n\t\t\t\tkubernetesDeployerProperties);\n\n\t\tAppDefinition appDefinition = new AppDefinition(randomName(), getAppProperties());\n\t\tScheduleRequest scheduleRequest = (isDeprecated) ? new ScheduleRequest(appDefinition, getSchedulerProperties(), Collections.emptyMap(), getCommandLineArgs(), randomName(), testApplication()) :\n\t\t\t\tnew ScheduleRequest(appDefinition, getDeploymentProperties(), getCommandLineArgs(), randomName(), testApplication());\n\t\tCronJob cronJob = kubernetesScheduler.createCronJob(scheduleRequest);\n\t\tCronJobSpec cronJobSpec = cronJob.getSpec();\n\n\t\tContainer container = cronJobSpec.getJobTemplate().getSpec().getTemplate().getSpec().getContainers().get(0);\n\n\t\tassertThat(ImagePullPolicy.relaxedValueOf(container.getImagePullPolicy())).as(\"Unexpected default image pull policy\")\n\t\t\t\t.isEqualTo(ImagePullPolicy.IfNotPresent);\n\n\t\tsafeUnschedule(kubernetesScheduler, cronJob.getMetadata().getName());\n\t}\n\n\t@ParameterizedTest\n\t@ValueSource(booleans = {true, false})\n\tpublic void testImagePullSecret(boolean isDeprecated) {\n\t\tKubernetesDeployerProperties kubernetesDeployerProperties =\n\t\t\t\tnew KubernetesDeployerProperties();\n\t\tif (kubernetesDeployerProperties.getNamespace() == null) {\n\t\t\tkubernetesDeployerProperties.setNamespace(\"default\");\n\t\t}\n\t\tKubernetesClient kubernetesClient = (new KubernetesClientBuilder()).build();\n\n\t\tKubernetesScheduler kubernetesScheduler = new KubernetesScheduler(kubernetesClient,\n\t\t\t\tkubernetesDeployerProperties);\n\n\t\tString secretName = \"mysecret\";\n\t\tString prefix = KubernetesSchedulerProperties.KUBERNETES_SCHEDULER_PROPERTIES_PREFIX;\n\n\t\tMap<String, String> schedulerProperties = new HashMap<>(getSchedulerProperties());\n\t\tschedulerProperties.put(prefix + \".imagePullSecret\", secretName);\n\n\t\tAppDefinition appDefinition = new AppDefinition(randomName(), getAppProperties());\n\t\tScheduleRequest scheduleRequest = (isDeprecated) ? new ScheduleRequest(appDefinition, schedulerProperties, null, getCommandLineArgs(), randomName(), testApplication()) :\n\t\t\t\tnew ScheduleRequest(appDefinition, schedulerProperties, getCommandLineArgs(), randomName(), testApplication());\n\t\tCronJob cronJob = kubernetesScheduler.createCronJob(scheduleRequest);\n\t\tCronJobSpec cronJobSpec = cronJob.getSpec();\n\n\t\tList<LocalObjectReference> secrets = cronJobSpec.getJobTemplate().getSpec().getTemplate().getSpec()\n\t\t\t\t.getImagePullSecrets();\n\t\tassertThat(secrets.get(0).getName()).as(\"Unexpected image pull secret\").isEqualTo(secretName);\n\n\t\tsafeUnschedule(kubernetesScheduler, cronJob.getMetadata().getName());\n\t}\n\n\t@ParameterizedTest\n\t@ValueSource(booleans = {true, false})\n\tpublic void testImagePullSecretDefault(boolean isDeprecated) {\n\t\tKubernetesDeployerProperties kubernetesDeployerProperties =\n\t\t\t\tnew KubernetesDeployerProperties();\n\t\tif (kubernetesDeployerProperties.getNamespace() == null) {\n\t\t\tkubernetesDeployerProperties.setNamespace(\"default\");\n\t\t}\n\t\tKubernetesClient kubernetesClient = (new KubernetesClientBuilder()).build();\n\n\t\tKubernetesScheduler kubernetesScheduler = new KubernetesScheduler(kubernetesClient,\n\t\t\t\tkubernetesDeployerProperties);\n\n\t\tAppDefinition appDefinition = new AppDefinition(randomName(), getAppProperties());\n\t\tScheduleRequest scheduleRequest = (isDeprecated) ? new ScheduleRequest(appDefinition, getSchedulerProperties(), Collections.emptyMap(), getCommandLineArgs(), randomName(), testApplication()) :\n\t\t\t\tnew ScheduleRequest(appDefinition, getDeploymentProperties(), getCommandLineArgs(), randomName(), testApplication());\n\n\t\tCronJob cronJob = kubernetesScheduler.createCronJob(scheduleRequest);\n\t\tCronJobSpec cronJobSpec = cronJob.getSpec();\n\n\t\tList<LocalObjectReference> secrets = cronJobSpec.getJobTemplate().getSpec().getTemplate().getSpec()\n\t\t\t\t.getImagePullSecrets();\n\t\tassertThat(secrets).as(\"There should be no secrets\").isEmpty();\n\n\t\tsafeUnschedule(kubernetesScheduler, cronJob.getMetadata().getName());\n\t}\n\n\t@ParameterizedTest\n\t@ValueSource(booleans = {true, false})\n\tpublic void testImagePullSecretFromSchedulerProperties(boolean isDeprecated) {\n\t\tKubernetesDeployerProperties kubernetesDeployerProperties =\n\t\t\t\tnew KubernetesDeployerProperties();\n\t\tif (kubernetesDeployerProperties.getNamespace() == null) {\n\t\t\tkubernetesDeployerProperties.setNamespace(\"default\");\n\t\t}\n\n\t\tString secretName = \"image-secret\";\n\t\tkubernetesDeployerProperties.setImagePullSecret(secretName);\n\t\tKubernetesClient kubernetesClient = (new KubernetesClientBuilder()).build();\n\n\t\tKubernetesScheduler kubernetesScheduler = new KubernetesScheduler(kubernetesClient,\n\t\t\t\tkubernetesDeployerProperties);\n\n\t\tAppDefinition appDefinition = new AppDefinition(randomName(), getAppProperties());\n\t\tScheduleRequest scheduleRequest = (isDeprecated) ? new ScheduleRequest(appDefinition, getSchedulerProperties(), Collections.emptyMap(), getCommandLineArgs(), randomName(), testApplication()) :\n\t\t\t\tnew ScheduleRequest(appDefinition, getDeploymentProperties(), getCommandLineArgs(), randomName(), testApplication());\n\n\t\tCronJob cronJob = kubernetesScheduler.createCronJob(scheduleRequest);\n\t\tCronJobSpec cronJobSpec = cronJob.getSpec();\n\n\t\tList<LocalObjectReference> secrets = cronJobSpec.getJobTemplate().getSpec().getTemplate().getSpec()\n\t\t\t\t.getImagePullSecrets();\n\t\tassertThat(secrets.get(0).getName()).as(\"Unexpected image pull secret\").isEqualTo(secretName);\n\n\t\tsafeUnschedule(kubernetesScheduler, cronJob.getMetadata().getName());\n\t}\n\n\t@ParameterizedTest\n\t@ValueSource(booleans = {true, false})\n\tpublic void testCustomEnvironmentVariables(boolean isDeprecated) {\n\t\tString prefix = (isDeprecated) ? KubernetesSchedulerProperties.KUBERNETES_SCHEDULER_PROPERTIES_PREFIX :\n\t\t\t\tKubernetesDeployerProperties.KUBERNETES_DEPLOYER_PROPERTIES_PREFIX;\n\n\t\tMap<String, String> schedulerProperties = new HashMap<>(getSchedulerProperties());\n\t\tschedulerProperties.put(prefix + \".environmentVariables\", \"MYVAR1=MYVAL1,MYVAR2=MYVAL2\");\n\n\t\tEnvVar[] expectedVars = new EnvVar[] {new EnvVar(\"MYVAR1\", \"MYVAL1\", null),\n\t\t\t\tnew EnvVar(\"MYVAR2\", \"MYVAL2\", null)};\n\n\t\ttestEnvironmentVariables(new KubernetesDeployerProperties(), schedulerProperties, expectedVars, isDeprecated);\n\t}\n\n\t@ParameterizedTest\n\t@ValueSource(booleans = {true, false})\n\tpublic void testGlobalEnvironmentVariables(boolean isDeprecated) {\n\t\tKubernetesDeployerProperties kubernetesDeployerProperties =\n\t\t\t\tnew KubernetesDeployerProperties();\n\t\tif (kubernetesDeployerProperties.getNamespace() == null) {\n\t\t\tkubernetesDeployerProperties.setNamespace(\"default\");\n\t\t}\n\t\tkubernetesDeployerProperties.setEnvironmentVariables(new String[] {\"MYVAR1=MYVAL1\", \"MYVAR2=MYVAL2\"});\n\n\t\tEnvVar[] expectedVars = new EnvVar[] {new EnvVar(\"MYVAR1\", \"MYVAL1\", null),\n\t\t\t\tnew EnvVar(\"MYVAR2\", \"MYVAL2\", null)};\n\n\t\ttestEnvironmentVariables(kubernetesDeployerProperties, getSchedulerProperties(), expectedVars, isDeprecated);\n\t}\n\n\t@ParameterizedTest\n\t@ValueSource(booleans = {true, false})\n\tpublic void testCustomEnvironmentVariablesWithNestedComma(boolean isDeprecated) {\n\t\tString prefix = (isDeprecated) ? KubernetesSchedulerProperties.KUBERNETES_SCHEDULER_PROPERTIES_PREFIX :\n\t\t\t\tKubernetesDeployerProperties.KUBERNETES_DEPLOYER_PROPERTIES_PREFIX;\n\n\t\tMap<String, String> schedulerProperties = new HashMap<>(getSchedulerProperties());\n\t\tschedulerProperties.put(prefix + \".environmentVariables\", \"MYVAR='VAL1,VAL2',MYVAR2=MYVAL2\");\n\n\t\tEnvVar[] expectedVars = new EnvVar[] {new EnvVar(\"MYVAR\", \"VAL1,VAL2\", null),\n\t\t\t\tnew EnvVar(\"MYVAR2\", \"MYVAL2\", null)};\n\n\t\ttestEnvironmentVariables((isDeprecated) ? new KubernetesSchedulerProperties() : new KubernetesDeployerProperties(), schedulerProperties, expectedVars, isDeprecated);\n\t}\n\n\t@ParameterizedTest\n\t@ValueSource(booleans = {true, false})\n\tpublic void testGlobalAndCustomEnvironmentVariables(boolean isDeprecated) {\n\t\tKubernetesDeployerProperties kubernetesDeployerProperties =\n\t\t\t\tnew KubernetesDeployerProperties();\n\n\t\tif (kubernetesDeployerProperties.getNamespace() == null) {\n\t\t\tkubernetesDeployerProperties.setNamespace(\"default\");\n\t\t}\n\t\tkubernetesDeployerProperties.setEnvironmentVariables(new String[] {\"MYVAR1=MYVAL1\", \"MYVAR2=MYVAL2\"});\n\n\t\tString prefix = (isDeprecated) ? KubernetesSchedulerProperties.KUBERNETES_SCHEDULER_PROPERTIES_PREFIX :\n\t\t\t\tKubernetesDeployerProperties.KUBERNETES_DEPLOYER_PROPERTIES_PREFIX;\n\n\t\tMap<String, String> schedulerProperties = new HashMap<>(getSchedulerProperties());\n\t\tschedulerProperties.put(prefix + \".environmentVariables\", \"MYVAR3=MYVAL3,MYVAR4=MYVAL4\");\n\n\t\tEnvVar[] expectedVars = new EnvVar[] {new EnvVar(\"MYVAR1\", \"MYVAL1\", null),\n\t\t\t\tnew EnvVar(\"MYVAR2\", \"MYVAL2\", null), new EnvVar(\"MYVAR3\", \"MYVAL3\", null),\n\t\t\t\tnew EnvVar(\"MYVAR4\", \"MYVAL4\", null)};\n\n\t\ttestEnvironmentVariables(kubernetesDeployerProperties, schedulerProperties, expectedVars, isDeprecated);\n\t}\n\n\t@ParameterizedTest\n\t@ValueSource(booleans = {true, false})\n\tpublic void testCustomEnvironmentVariablesOverrideGlobal(boolean isDeprecated) {\n\t\tKubernetesDeployerProperties kubernetesDeployerProperties =\n\t\t\t\tnew KubernetesDeployerProperties();\n\t\tif (kubernetesDeployerProperties.getNamespace() == null) {\n\t\t\tkubernetesDeployerProperties.setNamespace(\"default\");\n\t\t}\n\t\tkubernetesDeployerProperties.setEnvironmentVariables(new String[] {\"MYVAR1=MYVAL1\", \"MYVAR2=MYVAL2\"});\n\n\t\tString prefix = (isDeprecated) ? KubernetesSchedulerProperties.KUBERNETES_SCHEDULER_PROPERTIES_PREFIX :\n\t\t\t\tKubernetesDeployerProperties.KUBERNETES_DEPLOYER_PROPERTIES_PREFIX;\n\n\t\tMap<String, String> schedulerProperties = new HashMap<>(getSchedulerProperties());\n\t\tschedulerProperties.put(prefix + \".environmentVariables\", \"MYVAR2=OVERRIDE\");\n\n\t\tEnvVar[] expectedVars = new EnvVar[] {new EnvVar(\"MYVAR1\", \"MYVAL1\", null),\n\t\t\t\tnew EnvVar(\"MYVAR2\", \"OVERRIDE\", null)};\n\n\t\ttestEnvironmentVariables(kubernetesDeployerProperties, schedulerProperties, expectedVars, isDeprecated);\n\t}\n\n\tprivate void testEnvironmentVariables(KubernetesDeployerProperties kubernetesDeployerProperties,\n\t\t\tMap<String, String> schedulerProperties, EnvVar[] expectedVars, boolean isDeprecated) {\n\t\tif (kubernetesDeployerProperties.getNamespace() == null) {\n\t\t\tkubernetesDeployerProperties.setNamespace(\"default\");\n\t\t}\n\t\tKubernetesClient kubernetesClient = (new KubernetesClientBuilder()).build();\n\n\t\tKubernetesScheduler kubernetesScheduler = new KubernetesScheduler(kubernetesClient,\n\t\t\t\tkubernetesDeployerProperties);\n\n\t\tAppDefinition appDefinition = new AppDefinition(randomName(), getAppProperties());\n\t\tScheduleRequest scheduleRequest = (isDeprecated) ? new ScheduleRequest(appDefinition, schedulerProperties, null, getCommandLineArgs(), randomName(), testApplication()) :\n\t\t\t\tnew ScheduleRequest(appDefinition, schedulerProperties, getCommandLineArgs(), randomName(), testApplication());\n\t\tCronJob cronJob = kubernetesScheduler.createCronJob(scheduleRequest);\n\t\tCronJobSpec cronJobSpec = cronJob.getSpec();\n\n\t\tContainer container = cronJobSpec.getJobTemplate().getSpec().getTemplate().getSpec().getContainers().get(0);\n\n\t\tassertThat(container.getEnv()).as(\"Environment variables should not be empty\").isNotEmpty();\n\n\t\tassertThat(container.getEnv()).contains(expectedVars);\n\n\t\tsafeUnschedule(kubernetesScheduler, cronJob.getMetadata().getName());\n\t}\n\n\t@ParameterizedTest\n\t@ValueSource(booleans = {true, false})\n\tpublic void testTaskServiceAccountNameOverride(boolean isDeprecated) {\n\t\tKubernetesDeployerProperties kubernetesDeployerProperties =\n\t\t\t\tnew KubernetesDeployerProperties();\n\t\tif (kubernetesDeployerProperties.getNamespace() == null) {\n\t\t\tkubernetesDeployerProperties.setNamespace(\"default\");\n\t\t}\n\t\tKubernetesClient kubernetesClient = (new KubernetesClientBuilder()).build();\n\n\t\tKubernetesScheduler kubernetesScheduler = new KubernetesScheduler(kubernetesClient,\n\t\t\t\tkubernetesDeployerProperties);\n\n\t\tString taskServiceAccountName = \"mysa\";\n\t\tString prefix = KubernetesSchedulerProperties.KUBERNETES_SCHEDULER_PROPERTIES_PREFIX;\n\n\t\tMap<String, String> schedulerProperties = new HashMap<>(getSchedulerProperties());\n\t\tschedulerProperties.put(prefix + \".taskServiceAccountName\", taskServiceAccountName);\n\n\t\tAppDefinition appDefinition = new AppDefinition(randomName(), getAppProperties());\n\t\tScheduleRequest scheduleRequest = new ScheduleRequest(appDefinition, schedulerProperties,\n\t\t\t\tnull, getCommandLineArgs(), randomName(), testApplication());\n\n\t\tCronJob cronJob = kubernetesScheduler.createCronJob(scheduleRequest);\n\t\tCronJobSpec cronJobSpec = cronJob.getSpec();\n\n\t\tString serviceAccountName = cronJobSpec.getJobTemplate().getSpec().getTemplate().getSpec()\n\t\t\t\t.getServiceAccountName();\n\t\tassertThat(serviceAccountName).as(\"Unexpected service account name\").isEqualTo(taskServiceAccountName);\n\n\t\tsafeUnschedule(kubernetesScheduler, cronJob.getMetadata().getName());\n\t}\n\n\t@ParameterizedTest\n\t@ValueSource(booleans = {true, false})\n\tpublic void testTaskServiceAccountNameDefault(boolean isDeprecated) {\n\t\tKubernetesDeployerProperties kubernetesDeployerProperties =\n\t\t\t\tnew KubernetesDeployerProperties();\n\t\tif (kubernetesDeployerProperties.getNamespace() == null) {\n\t\t\tkubernetesDeployerProperties.setNamespace(\"default\");\n\t\t}\n\t\tKubernetesClient kubernetesClient = (new KubernetesClientBuilder()).build();\n\n\t\tKubernetesScheduler kubernetesScheduler = new KubernetesScheduler(kubernetesClient,\n\t\t\t\tkubernetesDeployerProperties);\n\n\t\tAppDefinition appDefinition = new AppDefinition(randomName(), getAppProperties());\n\t\tScheduleRequest scheduleRequest = (isDeprecated) ? new ScheduleRequest(appDefinition, getSchedulerProperties(),\n\t\t\t\tgetDeploymentProperties(), getCommandLineArgs(), randomName(), testApplication()) :\n\t\t\t\tnew ScheduleRequest(appDefinition, getDeploymentProperties(),\n\t\t\t\t\t\tgetCommandLineArgs(), randomName(), testApplication());\n\n\t\tCronJob cronJob = kubernetesScheduler.createCronJob(scheduleRequest);\n\t\tCronJobSpec cronJobSpec = cronJob.getSpec();\n\n\t\tString serviceAccountName = cronJobSpec.getJobTemplate().getSpec().getTemplate().getSpec()\n\t\t\t\t.getServiceAccountName();\n\t\tassertThat(serviceAccountName).as(\"Unexpected service account name\")\n\t\t\t\t.isEqualTo(KubernetesSchedulerProperties.DEFAULT_TASK_SERVICE_ACCOUNT_NAME);\n\n\t\tsafeUnschedule(kubernetesScheduler, cronJob.getMetadata().getName());\n\t}\n\n\t@ParameterizedTest\n\t@ValueSource(strings = {\"Forbid\", \"Allow\", \"Replace\"})\n\tpublic void testConcurrencyPolicy(String concurrencyPolicy) {\n\t\tKubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n\t\tif (kubernetesDeployerProperties.getNamespace() == null) {\n\t\t\tkubernetesDeployerProperties.setNamespace(\"default\");\n\t\t}\n\t\tKubernetesClient kubernetesClient = (new KubernetesClientBuilder()).build();\n\n\t\tKubernetesScheduler kubernetesScheduler = new KubernetesScheduler(kubernetesClient,\n\t\t\t\tkubernetesDeployerProperties);\n\n\t\tAppDefinition appDefinition = new AppDefinition(randomName(), getAppProperties());\n\t\tScheduleRequest scheduleRequest = new ScheduleRequest(appDefinition, getSchedulerProperties(concurrencyPolicy),\n\t\t\t\tgetCommandLineArgs(), randomName(), testApplication());\n\n\t\tCronJob cronJob = kubernetesScheduler.createCronJob(scheduleRequest);\n\n\t\tassertThat(cronJob.getSpec().getConcurrencyPolicy()).isEqualTo(concurrencyPolicy);\n\n\n\t\tsafeUnschedule(kubernetesScheduler, cronJob.getMetadata().getName());\n\t}\n\n\t@Test\n\tpublic void testConcurrencyPolicyDefault() {\n\t\tKubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n\t\tif (kubernetesDeployerProperties.getNamespace() == null) {\n\t\t\tkubernetesDeployerProperties.setNamespace(\"default\");\n\t\t}\n\t\tKubernetesClient kubernetesClient = (new KubernetesClientBuilder()).build();\n\n\t\tKubernetesScheduler kubernetesScheduler = new KubernetesScheduler(kubernetesClient,\n\t\t\t\tkubernetesDeployerProperties);\n\n\t\tAppDefinition appDefinition = new AppDefinition(randomName(), getAppProperties());\n\t\tScheduleRequest scheduleRequest = new ScheduleRequest(appDefinition, getSchedulerProperties(),\n\t\t\t\tgetCommandLineArgs(), randomName(), testApplication());\n\n\t\tCronJob cronJob = kubernetesScheduler.createCronJob(scheduleRequest);\n\n\t\tassertThat(cronJob.getSpec().getConcurrencyPolicy()).isEqualTo(\"Allow\");\n\n\n\t\tsafeUnschedule(kubernetesScheduler, cronJob.getMetadata().getName());\n\t}\n\n\t@Test\n\tpublic void testConcurrencyPolicyFromServerProperties() {\n\t\tKubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n\t\tif (kubernetesDeployerProperties.getNamespace() == null) {\n\t\t\tkubernetesDeployerProperties.setNamespace(\"default\");\n\t\t}\n\t\tkubernetesDeployerProperties.getCron().setConcurrencyPolicy(\"Forbid\");\n\t\tKubernetesClient kubernetesClient = (new KubernetesClientBuilder()).build();\n\n\t\tKubernetesScheduler kubernetesScheduler = new KubernetesScheduler(kubernetesClient,\n\t\t\t\tkubernetesDeployerProperties);\n\n\t\tAppDefinition appDefinition = new AppDefinition(randomName(), getAppProperties());\n\t\tScheduleRequest scheduleRequest = new ScheduleRequest(appDefinition, getSchedulerProperties(),\n\t\t\t\tgetCommandLineArgs(), randomName(), testApplication());\n\n\t\tCronJob cronJob = kubernetesScheduler.createCronJob(scheduleRequest);\n\n\t\tassertThat(cronJob.getSpec().getConcurrencyPolicy()).isEqualTo(\"Forbid\");\n\n\n\t\tsafeUnschedule(kubernetesScheduler, cronJob.getMetadata().getName());\n\t}\n\n\t@ParameterizedTest\n\t@ValueSource(strings = {\"100\", \"86400\"})\n\tpublic void testTtlSecondsAfterFinished(String ttlSecondsAfterFinished) {\n\t\tKubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n\t\tif (kubernetesDeployerProperties.getNamespace() == null) {\n\t\t\tkubernetesDeployerProperties.setNamespace(\"default\");\n\t\t}\n\t\tKubernetesClient kubernetesClient = (new KubernetesClientBuilder()).build();\n\n\t\tKubernetesScheduler kubernetesScheduler = new KubernetesScheduler(kubernetesClient,\n\t\t\t\tkubernetesDeployerProperties);\n\n\t\tAppDefinition appDefinition = new AppDefinition(randomName(), getAppProperties());\n\t\tMap<String, String> schedulerProperties = new HashMap<>(getSchedulerProperties());\n\t\tschedulerProperties.put(KubernetesScheduler.KUBERNETES_DEPLOYER_CRON_TTL_SECONDS_AFTER_FINISHED, ttlSecondsAfterFinished);\n\n\t\tScheduleRequest scheduleRequest = new ScheduleRequest(appDefinition, schedulerProperties,\n\t\t\t\tgetCommandLineArgs(), randomName(), testApplication());\n\n\t\tCronJob cronJob = kubernetesScheduler.createCronJob(scheduleRequest);\n\n\t\tassertThat(cronJob.getSpec().getJobTemplate().getSpec().getTtlSecondsAfterFinished())\n\t\t\t\t.isEqualTo(Integer.parseInt(ttlSecondsAfterFinished));\n\n\t\tsafeUnschedule(kubernetesScheduler, cronJob.getMetadata().getName());\n\t}\n\n\t@ParameterizedTest\n\t@ValueSource(strings = {\"100 2\", \"one\", \"10'\"})\n\tpublic void testInvalidTtlSecondsAfterFinished(String ttlSecondsAfterFinished) {\n\t\tAppDefinition appDefinition = new AppDefinition(randomName(), getAppProperties());\n\t\tMap<String, String> schedulerProperties = new HashMap<>(getSchedulerProperties());\n\t\tschedulerProperties.put(KubernetesScheduler.KUBERNETES_DEPLOYER_CRON_TTL_SECONDS_AFTER_FINISHED, ttlSecondsAfterFinished);\n\n\t\tScheduleRequest scheduleRequest = new ScheduleRequest(appDefinition, schedulerProperties,\n\t\t\t\tgetCommandLineArgs(), randomName(), testApplication());\n\n\t\tassertThatThrownBy(() -> {\n\t\t\tscheduler.schedule(scheduleRequest);\n\t\t}).isInstanceOf(NumberFormatException.class);\n\t}\n\n\t@Test\n\tpublic void testTtlSecondsAfterFinishedDefault() {\n\t\tKubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n\t\tif (kubernetesDeployerProperties.getNamespace() == null) {\n\t\t\tkubernetesDeployerProperties.setNamespace(\"default\");\n\t\t}\n\t\tKubernetesClient kubernetesClient = (new KubernetesClientBuilder()).build();\n\n\t\tKubernetesScheduler kubernetesScheduler = new KubernetesScheduler(kubernetesClient,\n\t\t\t\tkubernetesDeployerProperties);\n\n\t\tAppDefinition appDefinition = new AppDefinition(randomName(), getAppProperties());\n\n\t\tScheduleRequest scheduleRequest = new ScheduleRequest(appDefinition, getSchedulerProperties(),\n\t\t\t\tgetCommandLineArgs(), randomName(), testApplication());\n\n\t\tCronJob cronJob = kubernetesScheduler.createCronJob(scheduleRequest);\n\n\t\tassertThat(cronJob.getSpec().getJobTemplate().getSpec().getTtlSecondsAfterFinished())\n\t\t\t\t.isNull();\n\n\t\tsafeUnschedule(kubernetesScheduler, cronJob.getMetadata().getName());\n\t}\n\n\t@Test\n\tpublic void testTtlSecondsAfterFinishedFromServerProperties() {\n\t\tKubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n\t\tif (kubernetesDeployerProperties.getNamespace() == null) {\n\t\t\tkubernetesDeployerProperties.setNamespace(\"default\");\n\t\t}\n\t\tkubernetesDeployerProperties.getCron().setTtlSecondsAfterFinished(86400);\n\t\tKubernetesClient kubernetesClient = (new KubernetesClientBuilder()).build();\n\n\t\tKubernetesScheduler kubernetesScheduler = new KubernetesScheduler(kubernetesClient,\n\t\t\t\tkubernetesDeployerProperties);\n\n\t\tAppDefinition appDefinition = new AppDefinition(randomName(), getAppProperties());\n\t\tScheduleRequest scheduleRequest = new ScheduleRequest(appDefinition, getSchedulerProperties(),\n\t\t\t\tgetCommandLineArgs(), randomName(), testApplication());\n\n\t\tCronJob cronJob = kubernetesScheduler.createCronJob(scheduleRequest);\n\n\t\tassertThat(cronJob.getSpec().getJobTemplate().getSpec().getTtlSecondsAfterFinished()).isEqualTo(86400);\n\n\n\t\tsafeUnschedule(kubernetesScheduler, cronJob.getMetadata().getName());\n\t}\n\n\t@Test\n\tpublic void testBackoffLimit() {\n\t\tKubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n\t\tif (kubernetesDeployerProperties.getNamespace() == null) {\n\t\t\tkubernetesDeployerProperties.setNamespace(\"default\");\n\t\t}\n\t\tKubernetesClient kubernetesClient = (new KubernetesClientBuilder()).build();\n\t\tKubernetesScheduler kubernetesScheduler = new KubernetesScheduler(kubernetesClient, kubernetesDeployerProperties);\n\n\t\tAppDefinition appDefinition = new AppDefinition(randomName(), getAppProperties());\n\n\t\tMap<String, String> schedulerProperties = new HashMap<>(getSchedulerProperties());\n\t\tschedulerProperties.put(KubernetesScheduler.KUBERNETES_DEPLOYER_CRON_BACKOFF_LIMIT, \"5\");\n\n\t\tScheduleRequest scheduleRequest = new ScheduleRequest(appDefinition, schedulerProperties,\n\t\t\t\tgetCommandLineArgs(), randomName(), testApplication());\n\t\tCronJob cronJob = kubernetesScheduler.createCronJob(scheduleRequest);\n\t\tassertThat(cronJob.getSpec().getJobTemplate().getSpec().getBackoffLimit()).isEqualTo(5);\n\n\t\tsafeUnschedule(kubernetesScheduler, cronJob.getMetadata().getName());\n\t}\n\n\t@Test\n\tpublic void testBackoffLimitDefault() {\n\t\tKubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n\t\tif (kubernetesDeployerProperties.getNamespace() == null) {\n\t\t\tkubernetesDeployerProperties.setNamespace(\"default\");\n\t\t}\n\t\tKubernetesClient kubernetesClient = (new KubernetesClientBuilder()).build();\n\t\tKubernetesScheduler kubernetesScheduler = new KubernetesScheduler(kubernetesClient, kubernetesDeployerProperties);\n\n\t\tAppDefinition appDefinition = new AppDefinition(randomName(), getAppProperties());\n\t\tScheduleRequest scheduleRequest = new ScheduleRequest(appDefinition, getSchedulerProperties(),\n\t\t\t\tgetCommandLineArgs(), randomName(), testApplication());\n\t\tCronJob cronJob = kubernetesScheduler.createCronJob(scheduleRequest);\n\t\tassertThat(cronJob.getSpec().getJobTemplate().getSpec().getBackoffLimit()).isNull();\n\n\t\tsafeUnschedule(kubernetesScheduler, cronJob.getMetadata().getName());\n\t}\n\n\t@Test\n\tpublic void testBackoffLimitFromServerProperties() {\n\t\tKubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n\t\tif (kubernetesDeployerProperties.getNamespace() == null) {\n\t\t\tkubernetesDeployerProperties.setNamespace(\"default\");\n\t\t}\n\t\tkubernetesDeployerProperties.getCron().setBackoffLimit(7);\n\t\tKubernetesClient kubernetesClient = (new KubernetesClientBuilder()).build();\n\t\tKubernetesScheduler kubernetesScheduler = new KubernetesScheduler(kubernetesClient, kubernetesDeployerProperties);\n\n\t\tAppDefinition appDefinition = new AppDefinition(randomName(), getAppProperties());\n\t\tScheduleRequest scheduleRequest = new ScheduleRequest(appDefinition, getSchedulerProperties(),\n\t\t\t\tgetCommandLineArgs(), randomName(), testApplication());\n\t\tCronJob cronJob = kubernetesScheduler.createCronJob(scheduleRequest);\n\t\tassertThat(cronJob.getSpec().getJobTemplate().getSpec().getBackoffLimit()).isEqualTo(7);\n\n\t\tsafeUnschedule(kubernetesScheduler, cronJob.getMetadata().getName());\n\t}\n\n\t@AfterAll\n\tpublic static void cleanup() {\n\t\tKubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n\n\t\tKubernetesClient kubernetesClient = (new KubernetesClientBuilder()).build();\n\n\t\tKubernetesScheduler kubernetesScheduler = new KubernetesScheduler(kubernetesClient,\n\t\t\t\tkubernetesDeployerProperties);\n\n\t\tList<ScheduleInfo> scheduleInfos = kubernetesScheduler.list();\n\n\t\tfor (ScheduleInfo scheduleInfo : scheduleInfos) {\n\t\t\tsafeUnschedule(kubernetesScheduler, scheduleInfo.getScheduleName());\n\t\t}\n\t\t// Cleanup the schedules that aren't part of the list() - created from listScheduleWithExternalCronJobs test\n\t\tsafeUnschedule(kubernetesScheduler, \"job2\");\n\t\tsafeUnschedule(kubernetesScheduler, \"job3\");\n\t}\n\n\tprivate static void safeUnschedule(KubernetesScheduler scheduler, String scheduleName) {\n\t\ttry {\n\t\t\tscheduler.unschedule(scheduleName);\n\t\t}\n\t\tcatch (Exception ex) {\n\t\t\tLOGGER.warn(\"Failed to unschedule '\" + scheduleName + \"'\", ex);\n\t\t}\n\t}\n\n\t@Configuration\n\t@EnableAutoConfiguration\n\t@EnableConfigurationProperties\n\tpublic static class Config {\n\t\tprivate final KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n\n\t\t@Bean\n\t\tpublic Scheduler scheduler(KubernetesClient kubernetesClient) {\n\t\t\treturn new KubernetesScheduler(kubernetesClient, kubernetesDeployerProperties);\n\t\t}\n\n\t\t@Bean\n\t\tpublic KubernetesClient kubernetesClient() {\n\t\t\tif (kubernetesDeployerProperties.getNamespace() == null) {\n\t\t\t\tkubernetesDeployerProperties.setNamespace(\"default\");\n\t\t\t}\n\n\t\t\treturn KubernetesClientFactory.getKubernetesClient(kubernetesDeployerProperties);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "spring-cloud-deployer-kubernetes/src/test/java/org/springframework/cloud/deployer/spi/kubernetes/KubernetesSchedulerPropertiesTests.java",
    "content": "/*\n * Copyright 2018-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.kubernetes;\n\nimport org.junit.jupiter.api.Test;\n\nimport org.springframework.util.StringUtils;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\n/**\n * Tests for {@link KubernetesSchedulerProperties}.\n *\n * @author Chris Schaefer\n */\npublic class KubernetesSchedulerPropertiesTests {\n\n\t@Test\n\tpublic void testImagePullPolicyDefault() {\n\t\tKubernetesSchedulerProperties kubernetesSchedulerProperties = new KubernetesSchedulerProperties();\n\t\tassertThat(kubernetesSchedulerProperties.getImagePullPolicy()).as(\"Image pull policy should not be null\").isNotNull();\n\t\tassertEquals(ImagePullPolicy.IfNotPresent,\n\t\t\t\tkubernetesSchedulerProperties.getImagePullPolicy(),\n\t\t\t\t\"Invalid default image pull policy\");\n\t}\n\n\t@Test\n\tpublic void testImagePullPolicyCanBeCustomized() {\n\t\tKubernetesSchedulerProperties kubernetesSchedulerProperties = new KubernetesSchedulerProperties();\n\t\tkubernetesSchedulerProperties.setImagePullPolicy(ImagePullPolicy.Never);\n\t\tassertThat(kubernetesSchedulerProperties.getImagePullPolicy()).as(\"Image pull policy should not be null\").isNotNull();\n\t\tassertEquals(ImagePullPolicy.Never,\n\t\t\t\tkubernetesSchedulerProperties.getImagePullPolicy(),\n\t\t\t\t\"Unexpected image pull policy\");\n\t}\n\n\t@Test\n\tpublic void testRestartPolicyDefault() {\n\t\tKubernetesSchedulerProperties kubernetesSchedulerProperties = new KubernetesSchedulerProperties();\n\t\tassertThat(kubernetesSchedulerProperties.getRestartPolicy()).as(\"Restart policy should not be null\").isNotNull();\n\t\tassertEquals(RestartPolicy.Never,\n\t\t\t\tkubernetesSchedulerProperties.getRestartPolicy(),\n\t\t\t\t\"Invalid default restart policy\");\n\t}\n\n\t@Test\n\tpublic void testRestartPolicyCanBeCustomized() {\n\t\tKubernetesSchedulerProperties kubernetesSchedulerProperties = new KubernetesSchedulerProperties();\n\t\tkubernetesSchedulerProperties.setRestartPolicy(RestartPolicy.OnFailure);\n\t\tassertThat(kubernetesSchedulerProperties.getRestartPolicy()).as(\"Restart policy should not be null\").isNotNull();\n\t\tassertEquals(RestartPolicy.OnFailure,\n\t\t\t\tkubernetesSchedulerProperties.getRestartPolicy(),\n\t\t\t\t\"Unexpected restart policy\");\n\t}\n\n\t@Test\n\tpublic void testEntryPointStyleDefault() {\n\t\tKubernetesSchedulerProperties kubernetesSchedulerProperties = new KubernetesSchedulerProperties();\n\t\tassertThat(kubernetesSchedulerProperties.getEntryPointStyle()).as(\"Entry point style should not be null\").isNotNull();\n\t\tassertEquals(EntryPointStyle.exec,\n\t\t\t\tkubernetesSchedulerProperties.getEntryPointStyle(),\n\t\t\t\t\"Invalid default entry point style\");\n\t}\n\n\t@Test\n\tpublic void testEntryPointStyleCanBeCustomized() {\n\t\tKubernetesSchedulerProperties kubernetesSchedulerProperties = new KubernetesSchedulerProperties();\n\t\tkubernetesSchedulerProperties.setEntryPointStyle(EntryPointStyle.shell);\n\t\tassertThat(kubernetesSchedulerProperties.getEntryPointStyle()).as(\"Entry point style should not be null\").isNotNull();\n\t\tassertEquals(EntryPointStyle.shell,\n\t\t\t\tkubernetesSchedulerProperties.getEntryPointStyle(),\n\t\t\t\t\"Unexpected entry point stype\");\n\t}\n\n\t@Test\n\tpublic void testNamespaceDefault() {\n\t\tKubernetesSchedulerProperties kubernetesSchedulerProperties = new KubernetesSchedulerProperties();\n\t\tif (kubernetesSchedulerProperties.getNamespace() == null) {\n\t\t\tkubernetesSchedulerProperties.setNamespace(\"default\");\n\n\t\t\tassertThat(StringUtils.hasText(kubernetesSchedulerProperties.getNamespace())).as(\"Namespace should not be empty or null\").isTrue();\n\t\t\tassertEquals(\"default\", kubernetesSchedulerProperties.getNamespace(), \"Invalid default namespace\");\n\t\t}\n\t}\n\n\t@Test\n\tpublic void testNamespaceCanBeCustomized() {\n\t\tKubernetesSchedulerProperties kubernetesSchedulerProperties = new KubernetesSchedulerProperties();\n\t\tkubernetesSchedulerProperties.setNamespace(\"myns\");\n\t\tassertThat(StringUtils.hasText(kubernetesSchedulerProperties.getNamespace())).as(\"Namespace should not be empty or null\").isTrue();\n\t\tassertEquals(\"myns\", kubernetesSchedulerProperties.getNamespace(), \"Unexpected namespace\");\n\t}\n\n\t@Test\n\tpublic void testImagePullSecretDefault() {\n\t\tKubernetesSchedulerProperties kubernetesSchedulerProperties = new KubernetesSchedulerProperties();\n\t\tassertThat(kubernetesSchedulerProperties.getImagePullSecret()).as(\"No default image pull secret should be set\").isNull();\n\t}\n\n\t@Test\n\tpublic void testImagePullSecretCanBeCustomized() {\n\t\tString secret = \"mysecret\";\n\t\tKubernetesSchedulerProperties kubernetesSchedulerProperties = new KubernetesSchedulerProperties();\n\t\tkubernetesSchedulerProperties.setImagePullSecret(secret);\n\t\tassertThat(kubernetesSchedulerProperties.getImagePullSecret()).as(\"Image pull secret should not be null\").isNotNull();\n\t\tassertEquals(secret, kubernetesSchedulerProperties.getImagePullSecret(), \"Unexpected image pull secret\");\n\t}\n\n\t@Test\n\tpublic void testEnvironmentVariablesDefault() {\n\t\tKubernetesSchedulerProperties kubernetesSchedulerProperties = new KubernetesSchedulerProperties();\n\t\tassertEquals(0,\n\t\t\t\tkubernetesSchedulerProperties.getEnvironmentVariables().length,\n\t\t\t\t\"No default environment variables should be set\");\n\t}\n\n\t@Test\n\tpublic void testEnvironmentVariablesCanBeCustomized() {\n\t\tString[] envVars = new String[] { \"var1=val1\", \"var2=val2\" };\n\t\tKubernetesSchedulerProperties kubernetesSchedulerProperties = new KubernetesSchedulerProperties();\n\t\tkubernetesSchedulerProperties.setEnvironmentVariables(envVars);\n\t\tassertThat(kubernetesSchedulerProperties.getEnvironmentVariables()).as(\"Environment variables should not be null\").isNotNull();\n\t\tassertEquals(2,\n\t\t\t\tkubernetesSchedulerProperties.getEnvironmentVariables().length,\n\t\t\t\t\"Unexpected number of environment variables\");\n\t}\n\n\t@Test\n\tpublic void testTaskServiceAccountNameDefault() {\n\t\tKubernetesSchedulerProperties kubernetesSchedulerProperties = new KubernetesSchedulerProperties();\n\t\tassertThat(kubernetesSchedulerProperties.getTaskServiceAccountName()).as(\"Task service account name should not be null\").isNotNull();\n\t\tassertEquals(KubernetesSchedulerProperties.DEFAULT_TASK_SERVICE_ACCOUNT_NAME,\n\t\t\t\tkubernetesSchedulerProperties.getTaskServiceAccountName(),\n\t\t\t\t\"Unexpected default task service account name\");\n\t}\n\n\t@Test\n\tpublic void testTaskServiceAccountNameCanBeCustomized() {\n\t\tString taskServiceAccountName = \"mysa\";\n\t\tKubernetesSchedulerProperties kubernetesSchedulerProperties = new KubernetesSchedulerProperties();\n\t\tkubernetesSchedulerProperties.setTaskServiceAccountName(taskServiceAccountName);\n\t\tassertThat(kubernetesSchedulerProperties.getTaskServiceAccountName()).as(\"Task service account name should not be null\").isNotNull();\n\t\tassertEquals(taskServiceAccountName,\n\t\t\t\tkubernetesSchedulerProperties.getTaskServiceAccountName(),\n\t\t\t\t\"Unexpected task service account name\");\n\t}\n\n\t// Re-implement when we have a proper env binding via boot\n\t// @RunWith(PowerMockRunner.class)\n\t// @PrepareForTest({ KubernetesSchedulerProperties.class })\n\t// public static class EnvTests {\n\t// \t@Test\n\t// \tpublic void testNamespaceFromEnvironment() throws Exception {\n\t// \t\tPowerMockito.mockStatic(System.class);\n\t// \t\tPowerMockito.when(System.getenv(KubernetesSchedulerProperties.ENV_KEY_KUBERNETES_NAMESPACE))\n\t// \t\t\t\t.thenReturn(\"nsfromenv\");\n\t// \t\tKubernetesSchedulerProperties kubernetesSchedulerProperties = new KubernetesSchedulerProperties();\n\t// \t\tassertTrue(\"Namespace should not be empty or null\",\n\t// \t\t\t\tStringUtils.hasText(kubernetesSchedulerProperties.getNamespace()));\n\t// \t\tassertEquals(\"Unexpected namespace from environment\", \"nsfromenv\",\n\t// \t\t\t\tkubernetesSchedulerProperties.getNamespace());\n\t// \t}\n\t// }\n}\n"
  },
  {
    "path": "spring-cloud-deployer-kubernetes/src/test/java/org/springframework/cloud/deployer/spi/kubernetes/KubernetesTaskLauncherIntegrationIT.java",
    "content": "/*\n * Copyright 2016-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.kubernetes;\n\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.function.Consumer;\n\nimport io.fabric8.kubernetes.api.model.Container;\nimport io.fabric8.kubernetes.api.model.Pod;\nimport io.fabric8.kubernetes.client.utils.PodStatusUtil;\nimport org.assertj.core.api.InstanceOfAssertFactories;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.TestInfo;\nimport org.junit.jupiter.api.extension.ExtendWith;\n\nimport org.springframework.boot.test.context.SpringBootTest;\nimport org.springframework.boot.test.system.CapturedOutput;\nimport org.springframework.boot.test.system.OutputCaptureExtension;\nimport org.springframework.cloud.deployer.spi.core.AppDefinition;\nimport org.springframework.cloud.deployer.spi.core.AppDeploymentRequest;\nimport org.springframework.cloud.deployer.spi.task.LaunchState;\nimport org.springframework.core.io.Resource;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.entry;\n\n/**\n * Integration tests for {@link KubernetesTaskLauncher}.\n *\n * <p>NOTE: The tests do not call {@code TaskLauncher.destroy/cleanup} in a finally block but instead rely on the\n * {@link AbstractKubernetesTaskLauncherIntegrationTests#cleanupLingeringApps() AfterEach method} to clean any stray apps.\n *\n * @author Thomas Risberg\n * @author Chris Schaefer\n * @author Chris Bono\n * @author Glenn Renfro\n */\n@SpringBootTest(classes = {KubernetesAutoConfiguration.class}, properties = {\n\t\t\"spring.cloud.deployer.kubernetes.namespace=default\"\n})\n@ExtendWith(OutputCaptureExtension.class)\npublic class KubernetesTaskLauncherIntegrationIT extends AbstractKubernetesTaskLauncherIntegrationTests {\n\n\t@Test\n\tvoid taskLaunchedWithJobPodAnnotations(TestInfo testInfo) {\n\t\tlogTestInfo(testInfo);\n\t\tlaunchTaskPodAndValidateCreatedPodWithCleanup(\n\t\t\t\tCollections.singletonMap(\"spring.cloud.deployer.kubernetes.jobAnnotations\", \"key1:val1,key2:val2,key3:val31:val32\"),\n\t\t\t\t(pod) -> {\n\t\t\t\t\tassertThat(pod.getSpec().getContainers()).isNotEmpty()\n\t\t\t\t\t\t\t.element(0).extracting(Container::getPorts).asInstanceOf(InstanceOfAssertFactories.LIST).isEmpty();\n\t\t\t\t\tassertThat(pod.getMetadata().getAnnotations()).isNotEmpty()\n\t\t\t\t\t\t\t.contains(entry(\"key1\", \"val1\"), entry(\"key2\", \"val2\"), entry(\"key3\", \"val31:val32\"));\n\t\t\t\t});\n\t}\n\n\t@Test\n\tvoid taskLaunchedWithDeploymentLabels(TestInfo testInfo) {\n\t\tlogTestInfo(testInfo);\n\t\tlaunchTaskPodAndValidateCreatedPodWithCleanup(\n\t\t\t\tCollections.singletonMap(\"spring.cloud.deployer.kubernetes.deploymentLabels\", \"label1:value1,label2:value2\"),\n\t\t\t\t(pod) -> {\n\t\t\t\t\tassertThat(pod.getSpec().getContainers()).isNotEmpty()\n\t\t\t\t\t\t\t.element(0).extracting(Container::getPorts).asInstanceOf(InstanceOfAssertFactories.LIST).isEmpty();\n\t\t\t\t\tassertThat(pod.getMetadata().getLabels()).isNotEmpty()\n\t\t\t\t\t\t\t.contains(entry(\"label1\", \"value1\"), entry(\"label2\", \"value2\"));\n\t\t\t\t});\n\t}\n\n\t@Test\n\tvoid tasksLaunchedWithAdditionalContainers(TestInfo testInfo) {\n\t\tlogTestInfo(testInfo);\n\t\tlaunchTaskPodAndValidateCreatedPodWithCleanup(\n\t\t\t\tCollections.singletonMap(\"spring.cloud.deployer.kubernetes.additionalContainers\",\n\t\t\t\t\t\t\"[{name: 'test', image: 'busybox:latest', command: ['sh', '-c', 'echo hello']}]\"),\n\t\t\t\t(pod) -> assertThat(pod.getSpec().getContainers()).hasSize(2)\n\t\t\t\t\t\t.filteredOn(\"name\", \"test\").singleElement()\n\t\t\t\t\t\t.hasFieldOrPropertyWithValue(\"image\", \"busybox:latest\")\n\t\t\t\t\t\t.hasFieldOrPropertyWithValue(\"command\", Arrays.asList(\"sh\", \"-c\", \"echo hello\")));\n\t}\n\n\tprivate void launchTaskPodAndValidateCreatedPodWithCleanup(Map<String, String> deploymentProps, Consumer<Pod> assertingConsumer) {\n\t\tString taskName = randomName();\n\t\tAppDefinition definition = new AppDefinition(taskName, null);\n\t\tResource resource = testApplication();\n\t\tAppDeploymentRequest request = new AppDeploymentRequest(definition, resource, deploymentProps);\n\n\t\tlog.info(\"Launching {}...\", taskName);\n\t\tString launchId = taskLauncher().launch(request);\n\t\tawaitWithPollAndTimeout(deploymentTimeout())\n\t\t\t\t.untilAsserted(() -> assertThat(taskLauncher().status(launchId).getState()).isEqualTo(LaunchState.running));\n\n\t\tlog.info(\"Checking task Pod for {}...\", taskName);\n\t\tList<Pod> pods = getPodsForTask(taskName);\n\t\tassertThat(pods).hasSize(1);\n\t\tassertThat(pods).singleElement().satisfies(assertingConsumer);\n\n\t\tlog.info(\"Destroying {}...\", taskName);\n\t\ttaskLauncher().destroy(taskName);\n\t\tawaitWithPollAndTimeout(undeploymentTimeout())\n\t\t\t\t.untilAsserted(() -> assertThat(taskLauncher().status(launchId).getState()).isEqualTo(LaunchState.unknown));\n\t}\n\n\t@Test\n\tvoid cleanupDeletesTaskPod(TestInfo testInfo) {\n\t\tlogTestInfo(testInfo);\n\t\tAppDefinition definition = new AppDefinition(randomName(), null);\n\t\tResource resource = testApplication();\n\t\tAppDeploymentRequest request = new AppDeploymentRequest(definition, resource, null);\n\t\tString taskName = request.getDefinition().getName();\n\n\t\tlog.info(\"Launching {}...\", taskName);\n\t\tString launchId = taskLauncher().launch(request);\n\t\tawaitWithPollAndTimeout(deploymentTimeout())\n\t\t\t\t.untilAsserted(() -> assertThat(taskLauncher().status(launchId).getState()).isEqualTo(LaunchState.running));\n\n\t\tList<Pod> pods = getPodsForTask(taskName);\n\t\tassertThat(pods).hasSize(1);\n\t\tassertThat(PodStatusUtil.isRunning(pods.get(0))).isTrue();\n\n\t\tlog.info(\"Cleaning up {}...\", taskName);\n\t\ttaskLauncher().cleanup(launchId);\n\t\tawaitWithPollAndTimeout(undeploymentTimeout())\n\t\t\t\t.untilAsserted(() -> assertThat(taskLauncher().status(launchId).getState()).isEqualTo(LaunchState.unknown));\n\n\t\tpods = getPodsForTask(taskName);\n\t\tassertThat(pods).isEmpty();\n\t}\n\n\t@Test\n\tvoid cleanupForNonExistentTaskThrowsException(TestInfo testInfo, CapturedOutput taskOutput) {\n\t\tlogTestInfo(testInfo);\n\t\ttaskLauncher().cleanup(\"foo\");\n\t\tassertThat(taskOutput.getAll()).contains(\"Cannot delete pod for task \\\"foo\\\" (reason: pod does not exist)\");\n\t}\n}\n"
  },
  {
    "path": "spring-cloud-deployer-kubernetes/src/test/java/org/springframework/cloud/deployer/spi/kubernetes/KubernetesTaskLauncherMaximumConcurrentTasksTests.java",
    "content": "/*\n * Copyright 2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.kubernetes;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\n\nimport io.fabric8.kubernetes.api.model.Pod;\nimport io.fabric8.kubernetes.api.model.PodBuilder;\nimport io.fabric8.kubernetes.api.model.PodList;\nimport io.fabric8.kubernetes.client.KubernetesClient;\nimport io.fabric8.kubernetes.client.dsl.FilterWatchListDeletable;\nimport io.fabric8.kubernetes.client.dsl.MixedOperation;\nimport io.fabric8.kubernetes.client.dsl.PodResource;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\n\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.boot.test.context.SpringBootTest;\nimport org.springframework.boot.test.mock.mockito.MockBean;\nimport org.springframework.cloud.deployer.spi.core.AppDefinition;\nimport org.springframework.cloud.deployer.spi.core.AppDeploymentRequest;\nimport org.springframework.cloud.deployer.spi.task.TaskLauncher;\nimport org.springframework.core.io.Resource;\nimport org.springframework.test.context.junit.jupiter.SpringExtension;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.assertThatThrownBy;\nimport static org.mockito.ArgumentMatchers.anyString;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\n/**\n * @author David Turanski\n **/\n@SpringBootTest(classes = { KubernetesAutoConfiguration.class }, properties = {\n\t\t\"spring.cloud.deployer.kubernetes.maximum-concurrent-tasks=10\" })\n@ExtendWith(SpringExtension.class)\npublic class KubernetesTaskLauncherMaximumConcurrentTasksTests {\n\n\t@Autowired\n\tprivate TaskLauncher taskLauncher;\n\n\t@MockBean\n\tprivate KubernetesClient client;\n\n\tprivate List<Pod> pods;\n\n\t@Test\n\tpublic void getMaximumConcurrentTasksExceeded() {\n\t\tassertThat(taskLauncher).isNotNull();\n\n\t\tpods = stubForRunningPods(10);\n\n\t\tMixedOperation podsOperation = mock(MixedOperation.class);\n\t\tFilterWatchListDeletable filterWatchListDeletable = mock(FilterWatchListDeletable.class);\n\t\twhen(podsOperation.withLabel(\"task-name\")).thenReturn(filterWatchListDeletable);\n\t\twhen(filterWatchListDeletable.list()).thenAnswer(invocation -> {\n\t\t\tPodList podList = new PodList();\n\t\t\tList<Pod> items = new ArrayList<>();\n\t\t\tpodList.setItems(pods);\n\t\t\treturn podList;\n\t\t});\n\n\t\twhen(client.pods()).thenReturn(podsOperation);\n\n\t\twhen(podsOperation.withName(anyString())).thenAnswer(invocation -> {\n\t\t\tPod p = pods.stream().filter(pod -> pod.getMetadata().getName().equals(invocation.getArgument(0)))\n\t\t\t\t\t.findFirst().orElse(null);\n\t\t\tPodResource podResource = mock(PodResource.class);\n\t\t\twhen(podResource.get()).thenReturn(p);\n\t\t\treturn podResource;\n\t\t});\n\n\t\tint executionCount = taskLauncher.getRunningTaskExecutionCount();\n\n\t\tassertThat(executionCount).isEqualTo(10);\n\n\t\tassertThat(taskLauncher.getMaximumConcurrentTasks()).isEqualTo(taskLauncher.getRunningTaskExecutionCount());\n\n\t\tAppDefinition appDefinition = new AppDefinition(\"task\", Collections.emptyMap());\n\t\tAppDeploymentRequest request = new AppDeploymentRequest(appDefinition, mock(Resource.class),\n\t\t\t\tCollections.emptyMap());\n\n\t\tassertThatThrownBy(() -> {\n\t\t\ttaskLauncher.launch(request);\n\t\t}).isInstanceOf(IllegalStateException.class).hasMessageContaining(\n\t\t\t\t\"Cannot launch task task. The maximum concurrent task executions is at its limit [10].\");\n\n\t}\n\n\tprivate List<Pod> stubForRunningPods(int numTasks) {\n\t\tList<Pod> items = new ArrayList<>();\n\t\tfor (int i = 0; i < numTasks; i++) {\n\t\t\titems.add(new PodBuilder().withNewMetadata()\n\t\t\t\t\t.withName(\"task-\" + i).endMetadata()\n\t\t\t\t\t.withNewStatus()\n\t\t\t\t\t.withPhase(\"Running\")\n\t\t\t\t\t.endStatus().build());\n\t\t}\n\t\treturn items;\n\t}\n}\n"
  },
  {
    "path": "spring-cloud-deployer-kubernetes/src/test/java/org/springframework/cloud/deployer/spi/kubernetes/KubernetesTaskLauncherWithJobIntegrationIT.java",
    "content": "/*\n * Copyright 2016-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.kubernetes;\n\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.function.Consumer;\n\nimport io.fabric8.kubernetes.api.model.Pod;\nimport io.fabric8.kubernetes.api.model.batch.v1.Job;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.TestInfo;\nimport org.junit.jupiter.api.extension.ExtendWith;\n\nimport org.springframework.boot.test.context.SpringBootTest;\nimport org.springframework.boot.test.system.CapturedOutput;\nimport org.springframework.boot.test.system.OutputCaptureExtension;\nimport org.springframework.cloud.deployer.spi.core.AppDefinition;\nimport org.springframework.cloud.deployer.spi.core.AppDeploymentRequest;\nimport org.springframework.cloud.deployer.spi.task.LaunchState;\nimport org.springframework.core.io.Resource;\nimport org.springframework.test.context.TestPropertySource;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.assertThatThrownBy;\nimport static org.assertj.core.api.Assertions.entry;\n\n/**\n * Integration tests for {@link KubernetesTaskLauncher} using jobs instead of bare pods.\n *\n * <p>NOTE: The tests do not call {@code TaskLauncher.destroy/cleanup} in a finally block but instead rely on the\n * {@link AbstractKubernetesTaskLauncherIntegrationTests#cleanupLingeringApps() AfterEach method} to clean any stray apps.\n *\n * @author Leonardo Diniz\n * @author Chris Schaefer\n * @author Ilayaperumal Gopinathan\n * @author Chris Bono\n * @author Glenn Renfro\n */\n@SpringBootTest(classes = {KubernetesAutoConfiguration.class})\n@TestPropertySource(properties = \"spring.cloud.deployer.kubernetes.create-job=true\")\n@ExtendWith(OutputCaptureExtension.class)\npublic class KubernetesTaskLauncherWithJobIntegrationIT extends AbstractKubernetesTaskLauncherIntegrationTests {\n\n\t@BeforeEach\n\tpublic void setup() {\n\t\tif (kubernetesClient.getNamespace() == null) {\n\t\t\tkubernetesClient.getConfiguration().setNamespace(\"default\");\n\t\t}\n\t}\n\n\t@Test\n\tvoid taskLaunchedWithJobAnnotations(TestInfo testInfo) {\n\t\tlogTestInfo(testInfo);\n\t\tlaunchTaskJobAndValidateCreatedJobAndPodWithCleanup(\n\t\t\t\tCollections.singletonMap(\"spring.cloud.deployer.kubernetes.jobAnnotations\", \"key1:val1,key2:val2,key3:val31:val32\"),\n\t\t\t\t(job) -> assertThat(job.getMetadata().getAnnotations()).isNotEmpty()\n\t\t\t\t\t\t.contains(entry(\"key1\", \"val1\"), entry(\"key2\", \"val2\"), entry(\"key3\", \"val31:val32\")),\n\t\t\t\t(pod) -> assertThat(pod.getMetadata().getAnnotations()).isNotEmpty()\n\t\t\t\t\t\t.contains(entry(\"key1\", \"val1\"), entry(\"key2\", \"val2\"), entry(\"key3\", \"val31:val32\")));\n\t}\n\n\t@Test\n\tvoid taskLaunchedWithJobSpecProperties(TestInfo testInfo) {\n\t\tlogTestInfo(testInfo);\n\t\tMap<String, String> deploymentProps = new HashMap<>();\n\t\tdeploymentProps.put(\"spring.cloud.deployer.kubernetes.restartPolicy\", \"OnFailure\");\n\t\tdeploymentProps.put(\"spring.cloud.deployer.kubernetes.backoffLimit\", \"5\");\n\t\tlaunchTaskJobAndValidateCreatedJobAndPodWithCleanup(\n\t\t\t\tdeploymentProps,\n\t\t\t\t(job) -> assertThat(job.getSpec().getBackoffLimit()).isEqualTo(5),\n\t\t\t\t(pod) -> {});\n\t}\n\n\tprivate void launchTaskJobAndValidateCreatedJobAndPodWithCleanup(Map<String, String> deploymentProps,\n\t\t\tConsumer<Job> assertingJobConsumer, Consumer<Pod> assertingPodConsumer) {\n\t\tString taskName = randomName();\n\t\tAppDefinition definition = new AppDefinition(taskName, null);\n\t\tResource resource = testApplication();\n\t\tAppDeploymentRequest request = new AppDeploymentRequest(definition, resource, deploymentProps);\n\n\t\tlog.info(\"Launching {}...\", taskName);\n\t\tString launchId = taskLauncher().launch(request);\n\t\tawaitWithPollAndTimeout(deploymentTimeout())\n\t\t\t\t.untilAsserted(() -> assertThat(taskLauncher().status(launchId).getState()).isEqualTo(LaunchState.launching));\n\n\t\tlog.info(\"Checking task Job for {}...\", taskName);\n\t\tList<Job> jobs = getJobsForTask(taskName);\n\t\tassertThat(jobs).hasSize(1);\n\t\tassertThat(jobs).singleElement().satisfies(assertingJobConsumer);\n\n\t\tlog.info(\"Checking task Pod for {}...\", taskName);\n\t\tList<Pod> pods = getPodsForTask(taskName);\n\t\tassertThat(pods).hasSize(1);\n\t\tassertThat(pods).singleElement().satisfies(assertingPodConsumer);\n\n\t\tlog.info(\"Destroying {}...\", taskName);\n\t\ttaskLauncher().destroy(taskName);\n\t\tawaitWithPollAndTimeout(undeploymentTimeout())\n\t\t\t\t.untilAsserted(() -> assertThat(taskLauncher().status(launchId).getState()).isEqualTo(LaunchState.unknown));\n\t}\n\n\t@Test\n\tvoid taskLaunchedWithInvalidRestartPolicyThrowsException(TestInfo testInfo) {\n\t\tlogTestInfo(testInfo);\n\t\tAppDefinition definition = new AppDefinition(randomName(), null);\n\t\tResource resource = testApplication();\n\t\tMap<String, String> deploymentProps = new HashMap<>();\n\t\tdeploymentProps.put(\"spring.cloud.deployer.kubernetes.restartPolicy\", \"Always\");\n\t\tdeploymentProps.put(\"spring.cloud.deployer.kubernetes.backoffLimit\", \"5\");\n\t\tAppDeploymentRequest request = new AppDeploymentRequest(definition, resource, deploymentProps);\n\n\t\tlog.info(\"Launching {}...\", request.getDefinition().getName());\n\t\tassertThatThrownBy(() -> taskLauncher.launch(request))\n\t\t\t\t.isInstanceOf(Exception.class)\n\t\t\t\t.hasMessage(\"RestartPolicy should not be 'Always' when the JobSpec is used.\");\n\t}\n\n\t@Test\n\tvoid cleanupDeletesTaskJob(TestInfo testInfo) {\n\t\tlogTestInfo(testInfo);\n\t\tAppDefinition definition = new AppDefinition(randomName(), null);\n\t\tResource resource = testApplication();\n\t\tAppDeploymentRequest request = new AppDeploymentRequest(definition, resource, null);\n\t\tString taskName = request.getDefinition().getName();\n\n\t\tlog.info(\"Launching {}...\", taskName);\n\t\tString launchId = taskLauncher().launch(request);\n\t\tawaitWithPollAndTimeout(deploymentTimeout())\n\t\t\t\t.untilAsserted(() -> assertThat(taskLauncher().status(launchId).getState()).isEqualTo(LaunchState.launching));\n\n\t\tList<Job> jobs = getJobsForTask(taskName);\n\t\tassertThat(jobs).hasSize(1);\n\n\t\tlog.info(\"Cleaning up {}...\", taskName);\n\t\ttaskLauncher().cleanup(launchId);\n\t\tawaitWithPollAndTimeout(undeploymentTimeout())\n\t\t\t\t.untilAsserted(() -> assertThat(taskLauncher().status(launchId).getState()).isEqualTo(LaunchState.unknown));\n\n\t\tjobs = getJobsForTask(taskName);\n\t\tassertThat(jobs).isEmpty();\n\t}\n\n\t@Test\n\tvoid cleanupForNonExistentTaskThrowsException(TestInfo testInfo, CapturedOutput taskOutput) {\n\t\tlogTestInfo(testInfo);\n\t\ttaskLauncher().cleanup(\"foo\");\n\t\tassertThat(taskOutput.getAll()).contains(\"Cannot delete job for task \\\"foo\\\" (reason: job does not exist)\");\n\t}\n\n\t@Test\n\tvoid deleteJobAfterTtlSecondsOnAfterFinishedExpire(TestInfo testInfo) {\n\t\tlogTestInfo(testInfo);\n\n\t\tMap<String, String> applicationProps = new HashMap<>();\n\t\tapplicationProps.put(\"killDelay\", \"1\");\n\t\tAppDefinition definition = new AppDefinition(randomName(), applicationProps);\n\n\t\tResource resource = testApplication();\n\n\t\tMap<String, String> deploymentProps = new HashMap<>();\n\t\tdeploymentProps.put(\"spring.cloud.deployer.kubernetes.restartPolicy\", \"Never\");\n\t\tdeploymentProps.put(\"spring.cloud.deployer.kubernetes.backoffLimit\", \"0\");\n\t\tdeploymentProps.put(\"spring.cloud.deployer.kubernetes.ttlSecondsAfterFinished\", \"3\");\n\t\tAppDeploymentRequest request = new AppDeploymentRequest(definition, resource, deploymentProps);\n\t\tString taskName = request.getDefinition().getName();\n\n\t\tlog.info(\"Launching {}...\", taskName);\n\t\tString launchId = taskLauncher().launch(request);\n\t\tawaitWithPollAndTimeout(deploymentTimeout())\n\t\t\t\t.untilAsserted(() -> assertThat(taskLauncher().status(launchId).getState()).isEqualTo(LaunchState.launching));\n\n\t\tList<Job> jobs = getJobsForTask(taskName);\n\t\tassertThat(jobs).hasSize(1);\n\n\t\tlog.info(\"Waiting for deleting the job {}...\", taskName);\n\n\t\tawaitWithPollAndTimeout(undeploymentTimeout())\n\t\t\t\t.untilAsserted(() -> assertThat(taskLauncher().status(launchId).getState()).isEqualTo(LaunchState.unknown));\n\n\t\tjobs = getJobsForTask(taskName);\n\t\tassertThat(jobs).isEmpty();\n\t}\n}\n"
  },
  {
    "path": "spring-cloud-deployer-kubernetes/src/test/java/org/springframework/cloud/deployer/spi/kubernetes/PropertyParserUtilsTests.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.kubernetes;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport org.junit.jupiter.api.Test;\n\nimport org.springframework.cloud.deployer.spi.kubernetes.support.PropertyParserUtils;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.assertThatThrownBy;\n\n/**\n * Tests for PropertyParserUtils\n *\n * @author Chris Schaefer\n * @author Ilayaperumal Gopinathan\n * @author Glenn Renfro\n */\npublic class PropertyParserUtilsTests {\n\n\t@Test\n\tpublic void testAnnotationParseSingle() {\n\t\tMap<String, String> annotations = PropertyParserUtils.getStringPairsToMap(\"annotation:value\");\n\t\tassertThat(annotations.isEmpty()).isFalse();\n\t\tassertThat(annotations.size() == 1).isTrue();\n\t\tassertThat(annotations.containsKey(\"annotation\")).isTrue();\n\t\tassertThat(annotations.get(\"annotation\").equals(\"value\")).isTrue();\n\t}\n\n\t@Test\n\tpublic void testAnnotationParseMultiple() {\n\t\tMap<String, String> annotations = PropertyParserUtils.getStringPairsToMap(\"annotation1:value1,annotation2:value2\");\n\t\tassertThat(annotations.isEmpty()).isFalse();\n\t\tassertThat(annotations.size() == 2).isTrue();\n\t\tassertThat(annotations.containsKey(\"annotation1\")).isTrue();\n\t\tassertThat(annotations.get(\"annotation1\").equals(\"value1\")).isTrue();\n\t\tassertThat(annotations.containsKey(\"annotation2\")).isTrue();\n\t\tassertThat(annotations.get(\"annotation2\").equals(\"value2\")).isTrue();\n\t}\n\n\t@Test\n\tpublic void testAnnotationParseMultipleWithCommas() {\n\t\tassertThat(PropertyParserUtils.getStringPairsToMap(\"annotation1:\\\"value1,a,b,c,d\\\",annotation2:value2\"))\n\t\t\t\t.isNotEmpty()\n\t\t\t\t.hasSize(2)\n\t\t\t\t.containsEntry(\"annotation1\", \"\\\"value1,a,b,c,d\\\"\")\n\t\t\t\t.containsEntry(\"annotation2\", \"value2\");\n\t\tassertThat(PropertyParserUtils.getStringPairsToMap(\"annotation1:value1,annotation2:\\\"value2,a,b,c,d\\\"\"))\n\t\t\t\t.isNotEmpty()\n\t\t\t\t.hasSize(2)\n\t\t\t\t.containsEntry(\"annotation1\", \"value1\")\n\t\t\t\t.containsEntry(\"annotation2\", \"\\\"value2,a,b,c,d\\\"\");\n\t\tassertThat(PropertyParserUtils.getStringPairsToMap(\"annotation1:\\\"value1,a,b,c,d\\\",annotation2:\\\"value2,a,b,c,d\\\"\"))\n\t\t\t\t.isNotEmpty()\n\t\t\t\t.hasSize(2)\n\t\t\t\t.containsEntry(\"annotation1\", \"\\\"value1,a,b,c,d\\\"\")\n\t\t\t\t.containsEntry(\"annotation2\", \"\\\"value2,a,b,c,d\\\"\");\n// Test even number of quotes not to be used as token for ignoring commas boundary.\n\t\tassertThat(PropertyParserUtils.getStringPairsToMap(\"annotation1:\\\"value1,a,b,\\\"\\\"c,d\\\",annotation2:\\\"value2,a,b,c,d\\\"\"))\n\t\t\t\t.isNotEmpty()\n\t\t\t\t.hasSize(2)\n\t\t\t\t.containsEntry(\"annotation1\", \"\\\"value1,a,b,\\\"\\\"c,d\\\"\")\n\t\t\t\t.containsEntry(\"annotation2\", \"\\\"value2,a,b,c,d\\\"\");\n\t}\n\n\t@Test\n\tpublic void testAnnotationWithQuotes() {\n\t\tMap<String, String> annotations = PropertyParserUtils.getStringPairsToMap(\"annotation1:\\\"value1\\\",annotation2:value2\");\n\t\tassertThat(annotations.isEmpty()).isFalse();\n\t\tassertThat(annotations.size() == 2).isTrue();\n\t\tassertThat(annotations.containsKey(\"annotation1\")).isTrue();\n\t\tassertThat(annotations.get(\"annotation1\").equals(\"\\\"value1\\\"\")).isTrue();\n\t\tassertThat(annotations.containsKey(\"annotation2\")).isTrue();\n\t\tassertThat(annotations.get(\"annotation2\").equals(\"value2\")).isTrue();\n\t}\n\n\t@Test\n\tpublic void testAnnotationMultipleColon() {\n\t\tString annotation = \"iam.amazonaws.com/role:arn:aws:iam::12345678:role/role-name,key1:val1:val2:val3,\" +\n\t\t\t\t\"key2:val4::val5:val6::val7:val8\";\n\t\tMap<String, String> annotations = PropertyParserUtils.getStringPairsToMap(annotation);\n\t\tassertThat(annotations.isEmpty()).isFalse();\n\t\tassertThat(annotations.size() == 3).isTrue();\n\t\tassertThat(annotations.containsKey(\"iam.amazonaws.com/role\")).isTrue();\n\t\tassertThat(annotations.get(\"iam.amazonaws.com/role\").equals(\"arn:aws:iam::12345678:role/role-name\")).isTrue();\n\t\tassertThat(annotations.containsKey(\"key1\")).isTrue();\n\t\tassertThat(annotations.get(\"key1\").equals(\"val1:val2:val3\")).isTrue();\n\t\tassertThat(annotations.containsKey(\"key2\")).isTrue();\n\t\tassertThat(annotations.get(\"key2\").equals(\"val4::val5:val6::val7:val8\")).isTrue();\n\t}\n\n\t@Test\n\tpublic void testAnnotationParseInvalidValue() {\n\t\tassertThatThrownBy(() -> {\n\t\t\tPropertyParserUtils.getStringPairsToMap(\"annotation1:value1,annotation2,annotation3:value3\");\n\t\t}).isInstanceOf(IllegalArgumentException.class);\n\t}\n\n\t@Test\n\tpublic void testDeploymentPropertyParsing() {\n\t\tMap<String, String> deploymentProps = new HashMap<>();\n\t\tdeploymentProps.put(\"SPRING_CLOUD_DEPLOYER_KUBERNETES_IMAGEPULLPOLICY\", \"Never\");\n\t\tdeploymentProps.put(\"spring.cloud.deployer.kubernetes.pod-annotations\", \"key1:value1,key2:value2\");\n\t\tdeploymentProps.put(\"spring.cloud.deployer.kubernetes.serviceAnnotations\", \"key3:value3,key4:value4\");\n\t\tdeploymentProps.put(\"spring.cloud.deployer.kubernetes.init-container.image-name\", \"springcloud/openjdk\");\n\t\tdeploymentProps.put(\"spring.cloud.deployer.kubernetes.initContainer.containerName\", \"test\");\n\t\tdeploymentProps.put(\"spring.cloud.deployer.kubernetes.shareProcessNamespace\", \"true\");\n\t\tdeploymentProps.put(\"spring.cloud.deployer.kubernetes.priority-class-name\", \"high-priority\");\n\t\tdeploymentProps.put(\"spring.cloud.deployer.kubernetes.init-container.commands\", \"['sh','echo hello']\");\n\t\tassertThat(PropertyParserUtils.getDeploymentPropertyValue(deploymentProps, \"spring.cloud.deployer.kubernetes.podAnnotations\").equals(\"key1:value1,key2:value2\")).isTrue();\n\t\tassertThat(PropertyParserUtils.getDeploymentPropertyValue(deploymentProps, \"spring.cloud.deployer.kubernetes.serviceAnnotations\").equals(\"key3:value3,key4:value4\")).isTrue();\n\t\tassertThat(PropertyParserUtils.getDeploymentPropertyValue(deploymentProps, \"spring.cloud.deployer.kubernetes.initContainer.imageName\").equals(\"springcloud/openjdk\")).isTrue();\n\t\tassertThat(PropertyParserUtils.getDeploymentPropertyValue(deploymentProps, \"spring.cloud.deployer.kubernetes.initContainer.imageName\").equals(\"springcloud/openjdk\")).isTrue();\n\t\tassertThat(PropertyParserUtils.getDeploymentPropertyValue(deploymentProps, \"spring.cloud.deployer.kubernetes.imagePullPolicy\").equals(\"Never\")).isTrue();\n\t\tassertThat(PropertyParserUtils.getDeploymentPropertyValue(deploymentProps, \"spring.cloud.deployer.kubernetes.priority-class-name\").equals(\"high-priority\")).isTrue();\n\t\tassertThat(PropertyParserUtils.getDeploymentPropertyValue(deploymentProps, \"spring.cloud.deployer.kubernetes.shareProcessNamespace\").equals(\"true\")).isTrue();\n\t}\n}\n"
  },
  {
    "path": "spring-cloud-deployer-kubernetes/src/test/java/org/springframework/cloud/deployer/spi/kubernetes/RunAbstractKubernetesDeployerTests.java",
    "content": "/*\n * Copyright 2016-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.kubernetes;\n\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport io.fabric8.kubernetes.api.model.Quantity;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\n\nimport org.springframework.cloud.deployer.spi.core.AppDefinition;\nimport org.springframework.cloud.deployer.spi.core.AppDeploymentRequest;\nimport org.springframework.core.io.FileSystemResource;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * Unit test for {@link AbstractKubernetesDeployer}.\n *\n * @author Moritz Schulze\n * @author Chris Schaefer\n * @author Ilayaperumal Gopinathan\n */\npublic class RunAbstractKubernetesDeployerTests {\n\n\tprivate AppDeploymentRequest deploymentRequest;\n\tprivate KubernetesDeployerProperties kubernetesDeployerProperties;\n\tprivate Map<String, String> deploymentProperties;\n\tprivate DeploymentPropertiesResolver deploymentPropertiesResolver;\n\n\n\t@BeforeEach\n\tpublic void setUp() throws Exception {\n\t\tthis.deploymentProperties = new HashMap<>();\n\t\tthis.deploymentRequest = new AppDeploymentRequest(new AppDefinition(\"foo\", Collections.emptyMap()), new FileSystemResource(\"\"), deploymentProperties);\n\t\tthis.kubernetesDeployerProperties = new KubernetesDeployerProperties();\n\t\tthis.deploymentPropertiesResolver = new DeploymentPropertiesResolver(\n\t\t\t\tKubernetesDeployerProperties.KUBERNETES_DEPLOYER_PROPERTIES_PREFIX, this.kubernetesDeployerProperties);\n\t}\n\n\t@Test\n\tpublic void deduceImagePullPolicy_fallsBackToIfNotPresentIfOverrideNotParseable() {\n\t\tdeploymentProperties.put(\"spring.cloud.deployer.kubernetes.imagePullPolicy\", \"not-a-real-value\");\n\t\tImagePullPolicy pullPolicy = this.deploymentPropertiesResolver.deduceImagePullPolicy(deploymentRequest.getDeploymentProperties());\n\t\tassertThat(pullPolicy).isEqualTo(ImagePullPolicy.IfNotPresent);\n\t}\n\n\t@Test\n\tpublic void limitGpu_noDeploymentProperty_incompleteServerProperty1_noGpu() {\n\t\tkubernetesDeployerProperties.getLimits().setGpuVendor(\"nvidia.com\");\n\t\tMap<String, Quantity> limits = this.deploymentPropertiesResolver.deduceResourceLimits(deploymentRequest.getDeploymentProperties());\n\t\tassertThat(limits.get(\"nvidia.com/gpu\")).isNull();\n\t}\n\n\t@Test\n\tpublic void limitGpu_noDeploymentProperty_incompleteServerProperty2_noGpu() {\n\t\tkubernetesDeployerProperties.getLimits().setGpuCount(\"2\");\n\t\tMap<String, Quantity> limits = this.deploymentPropertiesResolver.deduceResourceLimits(deploymentRequest.getDeploymentProperties());\n\t\tassertThat(limits.get(\"nvidia.com/gpu\")).isNull();\n\t}\n\n\t@Test\n\tpublic void limitGpu_noDeploymentProperty_serverProperty_usesServerProperty() {\n\t\tkubernetesDeployerProperties.getLimits().setGpuVendor(\"nvidia.com/gpu\");\n\t\tkubernetesDeployerProperties.getLimits().setGpuCount(\"2\");\n\t\tMap<String, Quantity> limits = this.deploymentPropertiesResolver.deduceResourceLimits(deploymentRequest.getDeploymentProperties());\n\t\tassertThat(limits.get(\"nvidia.com/gpu\")).isEqualTo(new Quantity(\"2\"));\n\t}\n\n\t@Test\n\tpublic void limitGpu_deploymentPropertyVendor_usesDeploymentProperty() {\n\t\tkubernetesDeployerProperties.getLimits().setGpuVendor(\"nvidia.com/gpu\");\n\t\tkubernetesDeployerProperties.getLimits().setGpuCount(\"2\");\n\t\tdeploymentProperties.put(\"spring.cloud.deployer.kubernetes.limits.gpu_vendor\", \"ati.com/gpu\");\n\t\tMap<String, Quantity> limits = this.deploymentPropertiesResolver.deduceResourceLimits(deploymentRequest.getDeploymentProperties());\n\t\tassertThat(limits.get(\"ati.com/gpu\")).isEqualTo(new Quantity(\"2\"));\n\t}\n\n\t@Test\n\tpublic void limitGpu_deploymentPropertyCount_usesDeploymentProperty() {\n\t\tkubernetesDeployerProperties.getLimits().setGpuVendor(\"nvidia.com/gpu\");\n\t\tkubernetesDeployerProperties.getLimits().setGpuCount(\"2\");\n\t\tdeploymentProperties.put(\"spring.cloud.deployer.kubernetes.limits.gpu_count\", \"1\");\n\t\tMap<String, Quantity> limits = this.deploymentPropertiesResolver.deduceResourceLimits(deploymentRequest.getDeploymentProperties());\n\t\tassertThat(limits.get(\"nvidia.com/gpu\")).isEqualTo(new Quantity(\"1\"));\n\t}\n\n\t@Test\n\tpublic void limitGpu_deploymentPropertyBoth_usesDeploymentProperty() {\n\t\tkubernetesDeployerProperties.getLimits().setGpuVendor(\"nvidia.com/gpu\");\n\t\tkubernetesDeployerProperties.getLimits().setGpuCount(\"2\");\n\t\tdeploymentProperties.put(\"spring.cloud.deployer.kubernetes.limits.gpu_vendor\", \"ati.com/gpu\");\n\t\tdeploymentProperties.put(\"spring.cloud.deployer.kubernetes.limits.gpu_count\", \"1\");\n\t\tMap<String, Quantity> limits = this.deploymentPropertiesResolver.deduceResourceLimits(deploymentRequest.getDeploymentProperties());\n\t\tassertThat(limits.get(\"ati.com/gpu\")).isEqualTo(new Quantity(\"1\"));\n\t}\n\n\t@Test\n\tpublic void limitCpu_noDeploymentProperty_serverProperty_usesServerProperty() {\n\t\tkubernetesDeployerProperties.getLimits().setCpu(\"400m\");\n\t\tMap<String, Quantity> limits = this.deploymentPropertiesResolver.deduceResourceLimits(deploymentRequest.getDeploymentProperties());\n\t\tassertThat(limits.get(\"cpu\")).isEqualTo(new Quantity(\"400m\"));\n\t}\n\n\t@Test\n\tpublic void limitMemory_noDeploymentProperty_serverProperty_usesServerProperty() {\n\t\tkubernetesDeployerProperties.getLimits().setMemory(\"540Mi\");\n\t\tMap<String, Quantity> limits = this.deploymentPropertiesResolver.deduceResourceLimits(deploymentRequest.getDeploymentProperties());\n\t\tassertThat(limits.get(\"memory\")).isEqualTo(new Quantity(\"540Mi\"));\n\t}\n\n\t@Test\n\tpublic void limitCpu_deploymentProperty_usesDeploymentProperty() {\n\t\tkubernetesDeployerProperties.getLimits().setCpu(\"100m\");\n\t\tdeploymentProperties.put(\"spring.cloud.deployer.kubernetes.limits.cpu\", \"400m\");\n\t\tMap<String, Quantity> limits = this.deploymentPropertiesResolver.deduceResourceLimits(deploymentRequest.getDeploymentProperties());\n\t\tassertThat(limits.get(\"cpu\")).isEqualTo(new Quantity(\"400m\"));\n\t}\n\n\t@Test\n\tpublic void limitMemory_deploymentProperty_usesDeploymentProperty() {\n\t\tkubernetesDeployerProperties.getLimits().setMemory(\"640Mi\");\n\t\tdeploymentProperties.put(\"spring.cloud.deployer.kubernetes.limits.memory\", \"256Mi\");\n\t\tMap<String, Quantity> limits = this.deploymentPropertiesResolver.deduceResourceLimits(deploymentRequest.getDeploymentProperties());\n\t\tassertThat(limits.get(\"memory\")).isEqualTo(new Quantity(\"256Mi\"));\n\t}\n\n\t@Test\n\tpublic void requestCpu_noDeploymentProperty_serverProperty_usesServerProperty() {\n\t\tkubernetesDeployerProperties.getRequests().setCpu(\"400m\");\n\t\tMap<String, Quantity> requests = this.deploymentPropertiesResolver.deduceResourceRequests(deploymentRequest.getDeploymentProperties());\n\t\tassertThat(requests.get(\"cpu\")).isEqualTo(new Quantity(\"400m\"));\n\t}\n\n\t@Test\n\tpublic void requestMemory_noDeploymentProperty_serverProperty_usesServerProperty() {\n\t\tkubernetesDeployerProperties.getRequests().setMemory(\"120Mi\");\n\t\tMap<String, Quantity> requests = this.deploymentPropertiesResolver.deduceResourceRequests(deploymentRequest.getDeploymentProperties());\n\t\tassertThat(requests.get(\"memory\")).isEqualTo(new Quantity(\"120Mi\"));\n\t}\n\n\t@Test\n\tpublic void requestCpu_deploymentProperty_usesDeploymentProperty() {\n\t\tkubernetesDeployerProperties.getRequests().setCpu(\"1000m\");\n\t\tdeploymentProperties.put(\"spring.cloud.deployer.kubernetes.requests.cpu\", \"461m\");\n\t\tMap<String, Quantity> requests = this.deploymentPropertiesResolver.deduceResourceRequests(deploymentRequest.getDeploymentProperties());\n\t\tassertThat(requests.get(\"cpu\")).isEqualTo(new Quantity(\"461m\"));\n\t}\n\n\t@Test\n\tpublic void requestMemory_deploymentProperty_usesDeploymentProperty() {\n\t\tkubernetesDeployerProperties.getRequests().setMemory(\"640Mi\");\n\t\tdeploymentProperties.put(\"spring.cloud.deployer.kubernetes.requests.memory\", \"256Mi\");\n\t\tMap<String, Quantity> requests = this.deploymentPropertiesResolver.deduceResourceRequests(deploymentRequest.getDeploymentProperties());\n\t\tassertThat(requests.get(\"memory\")).isEqualTo(new Quantity(\"256Mi\"));\n\t}\n\t@Test\n\tpublic void requestEphemeralStorage_deploymentProperty_usesDeploymentProperty() {\n\t\tkubernetesDeployerProperties.getRequests().setEphemeralStorage(\"2Gi\");\n\t\tdeploymentProperties.put(\"spring.cloud.deployer.kubernetes.requests.ephemeral-storage\", \"2Gi\");\n\t\tMap<String, Quantity> requests = this.deploymentPropertiesResolver.deduceResourceRequests(deploymentRequest.getDeploymentProperties());\n\t\tassertThat(requests.get(\"ephemeral-storage\")).isEqualTo(new Quantity(\"2Gi\"));\n\t}\n\n\t@Test\n\tpublic void limitEphemeralStorage_deploymentProperty_usesDeploymentProperty() {\n\t\tkubernetesDeployerProperties.getLimits().setEphemeralStorage(\"2Gi\");\n\t\tdeploymentProperties.put(\"spring.cloud.deployer.kubernetes.limits.ephemeral-storage\", \"2Gi\");\n\t\tMap<String, Quantity> limits = this.deploymentPropertiesResolver.deduceResourceLimits(deploymentRequest.getDeploymentProperties());\n\t\tassertThat(limits.get(\"ephemeral-storage\")).isEqualTo(new Quantity(\"2Gi\"));\n\t}\n\n\t@Test\n\tpublic void requestHugepages1Gi_deploymentProperty_usesDeploymentProperty() {\n\t\tkubernetesDeployerProperties.getRequests().setHugepages1Gi(\"4\");\n\t\tdeploymentProperties.put(\"spring.cloud.deployer.kubernetes.requests.hugepages-1Gi\", \"4\");\n\t\tMap<String, Quantity> requests = this.deploymentPropertiesResolver.deduceResourceRequests(deploymentRequest.getDeploymentProperties());\n\t\tassertThat(requests.get(\"hugepages-1Gi\")).isEqualTo(new Quantity(\"4\"));\n\t}\n\n\t@Test\n\tpublic void limitHugepages1Gi_deploymentProperty_usesDeploymentProperty() {\n\t\tkubernetesDeployerProperties.getLimits().setHugepages1Gi(\"4\");\n\t\tdeploymentProperties.put(\"spring.cloud.deployer.kubernetes.limits.hugepages-1Gi\", \"4\");\n\t\tMap<String, Quantity> limits = this.deploymentPropertiesResolver.deduceResourceLimits(deploymentRequest.getDeploymentProperties());\n\t\tassertThat(limits.get(\"hugepages-1Gi\")).isEqualTo(new Quantity(\"4\"));\n\t}\n\n\t@Test\n\tpublic void requestHugepages2Mi_deploymentProperty_usesDeploymentProperty() {\n\t\tkubernetesDeployerProperties.getRequests().setHugepages2Mi(\"40\");\n\t\tdeploymentProperties.put(\"spring.cloud.deployer.kubernetes.requests.hugepages-2Mi\", \"40\");\n\t\tMap<String, Quantity> requests = this.deploymentPropertiesResolver.deduceResourceRequests(deploymentRequest.getDeploymentProperties());\n\t\tassertThat(requests.get(\"hugepages-2Mi\")).isEqualTo(new Quantity(\"40\"));\n\t}\n\n\t@Test\n\tpublic void limitHugepages2Mi_deploymentProperty_usesDeploymentProperty() {\n\t\tkubernetesDeployerProperties.getLimits().setHugepages2Mi(\"40\");\n\t\tdeploymentProperties.put(\"spring.cloud.deployer.kubernetes.limits.hugepages-2Mi\", \"40\");\n\t\t\t\tMap<String, Quantity> limits = this.deploymentPropertiesResolver.deduceResourceLimits(deploymentRequest.getDeploymentProperties());\n\t\tassertThat(limits.get(\"hugepages-2Mi\")).isEqualTo(new Quantity(\"40\"));\n\t}\n}\n"
  },
  {
    "path": "spring-cloud-deployer-kubernetes/src/test/resources/dataflow-server-configMapKeyRef.yml",
    "content": "# spring.cloud.deployer.kubernetes.configMapKeyRefs:\nconfigMapKeyRefs:\n  - envVarName: \"MY_ENV\"\n    configMapName: \"myConfigMap\"\n    dataKey: \"envName\"\n"
  },
  {
    "path": "spring-cloud-deployer-kubernetes/src/test/resources/dataflow-server-containerSecurityContext.yml",
    "content": "# spring.cloud.deployer.kubernetes.containerSecurityContext:\ncontainerSecurityContext:\n  allowPrivilegeEscalation: true\n  capabilities:\n    add:\n      - \"a\"\n      - \"b\"\n    drop:\n      - \"c\"\n  privileged: true\n  procMount: DefaultProcMount\n  readOnlyRootFilesystem: true\n  runAsUser: 65534\n  runAsGroup: 65534\n  runAsNonRoot: true\n  seLinuxOptions:\n    level: \"s0:c123,c456\"\n  seccompProfile:\n    type: Localhost\n    localhostProfile: my-profiles/profile-allow.json\n  windowsOptions:\n    gmsaCredentialSpec: specA\n    gmsaCredentialSpecName: specA-name\n    hostProcess: true\n    runAsUserName: userA\n"
  },
  {
    "path": "spring-cloud-deployer-kubernetes/src/test/resources/dataflow-server-nodeAffinity.yml",
    "content": "# spring.cloud.deployer.kubernetes.nodeAffinity:\nnodeAffinity:\n  requiredDuringSchedulingIgnoredDuringExecution:\n    nodeSelectorTerms:\n      - matchExpressions:\n          - key: kubernetes.io/e2e-az-name\n            operator: In\n            values:\n              - e2e-az1\n              - e2e-az2\n  preferredDuringSchedulingIgnoredDuringExecution:\n    - weight: 1\n      preference:\n        matchExpressions:\n          - key: another-node-label-key\n            operator: In\n            values:\n              - another-node-label-value\n"
  },
  {
    "path": "spring-cloud-deployer-kubernetes/src/test/resources/dataflow-server-podAffinity.yml",
    "content": "# spring.cloud.deployer.kubernetes.podAffinity:\npodAffinity:\n  requiredDuringSchedulingIgnoredDuringExecution:\n    - labelSelector:\n        matchExpressions:\n          - key: security\n            operator: In\n            values:\n              - S1\n      topologyKey: failure-domain.beta.kubernetes.io/zone\n  preferredDuringSchedulingIgnoredDuringExecution:\n    - weight: 100\n      podAffinityTerm:\n        labelSelector:\n          matchExpressions:\n            - key: security\n              operator: In\n              values:\n                - S2\n        topologyKey: failure-domain.beta.kubernetes.io/zone"
  },
  {
    "path": "spring-cloud-deployer-kubernetes/src/test/resources/dataflow-server-podAntiAffinity.yml",
    "content": "# spring.cloud.deployer.kubernetes.podAntiAffinity:\npodAntiAffinity:\n  requiredDuringSchedulingIgnoredDuringExecution:\n    - labelSelector:\n        matchExpressions:\n          - key: app\n            operator: In\n            values:\n              - store\n      topologyKey: \"kubernetes.io/hostname\"\n  preferredDuringSchedulingIgnoredDuringExecution:\n    - weight: 100\n      podAffinityTerm:\n        labelSelector:\n          matchExpressions:\n            - key: security\n              operator: In\n              values:\n                - S2\n        topologyKey: failure-domain.beta.kubernetes.io/zone"
  },
  {
    "path": "spring-cloud-deployer-kubernetes/src/test/resources/dataflow-server-podsecuritycontext.yml",
    "content": "# spring.cloud.deployer.kubernetes.podSecurityContext:\npodSecurityContext:\n  fsGroup: 65534\n  fsGroupChangePolicy: Always\n  runAsUser: 65534\n  runAsGroup: 65534\n  runAsNonRoot: true\n  seLinuxOptions:\n    level: \"s0:c123,c456\"\n  seccompProfile:\n    type: Localhost\n    localhostProfile: my-profiles/profile-allow.json\n  supplementalGroups:\n    - 65534\n    - 65535\n  sysctls:\n    - name: \"kernel.shm_rmid_forced\"\n      value: 0\n    - name: \"net.core.somaxconn\"\n      value: 1024\n  windowsOptions:\n    gmsaCredentialSpec: specA\n    gmsaCredentialSpecName: specA-name\n    hostProcess: true\n    runAsUserName: userA\n"
  },
  {
    "path": "spring-cloud-deployer-kubernetes/src/test/resources/dataflow-server-secretKeyRef.yml",
    "content": "# spring.cloud.deployer.kubernetes.secretKeyRefs:\nsecretKeyRefs:\n  - envVarName: \"SECRET_PASSWORD\"\n    secretName: \"mySecret\"\n    dataKey: \"myPassword\"\n"
  },
  {
    "path": "spring-cloud-deployer-kubernetes/src/test/resources/dataflow-server-tolerations.yml",
    "content": "# spring.cloud.deployer.kubernetes.tolerations:\ntolerations:\n  - key: \"scdf\"\n    operator: \"Equal\"\n    value: \"true\"\n    effect: \"NoSchedule\"\n"
  },
  {
    "path": "spring-cloud-deployer-kubernetes/src/test/resources/dataflow-server.yml",
    "content": "# spring.cloud.deployer.kubernetes.volumes:\nvolumes:\n  - name: testhostpath\n    hostPath:\n      path: /test/hostPath\n\n  - name: testpvc\n    persistentVolumeClaim:\n      claimName: testClaim\n      readOnly: true\n\n  - name: testnfs\n    nfs:\n      server: 10.0.0.1:111\n      path: /test/nfs\n"
  },
  {
    "path": "spring-cloud-deployer-local/README.adoc",
    "content": "== Spring Cloud Local Deployer\n\nSpring Cloud Local Deployer is an implementation of the Spring Cloud Deployer SPI for use\nto deploy applications on the same machine.  This occurs by this application spawning a\nnew JVM process for the deployed application.\n\nNOTE:  It's important to note that this deployer spawns new JVMs that are not monitored\nor maintained by this deployer.  No attempts at high availability, fault tolerance, or\nresiliency are provided by the deployer.  Since the deployer SPI expects an underlying\nplatform to provide that level of resiliency, any use of this deployer in a production\nenvironment should be accompanied with additional monitoring at the app level (the apps\nthis deployer deploys).  This deployer will not be updated to take on those requirements.\nTherefore the user is encouraged to explore the CloudFoundry and Kubernetes variants as\nways to meet them.\n\n=== Building\n\nBuild and skip all tests:\n\n[source,shell]\n----\n./mvnw clean package -DskipTests\n----\n\nOr build project and run tests:\n\n[source,shell]\n----\n./mvnw clean package\n----\n\nRun the integration tests in Docker mode:\n\n[source,shell]\n----\n./mvnw clean install -pl :spring-cloud-deployer-local -Dspring-cloud-deployer-spi-test-use-docker=true -Dspring.cloud.deployer.local.hostname=localhost\n----\n"
  },
  {
    "path": "spring-cloud-deployer-local/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd\">\n\t<modelVersion>4.0.0</modelVersion>\n\n\t<artifactId>spring-cloud-deployer-local</artifactId>\n\t<packaging>jar</packaging>\n\t<name>Spring Cloud Deployer Local</name>\n\n\t<parent>\n\t\t<groupId>org.springframework.cloud</groupId>\n\t\t<artifactId>spring-cloud-deployer-parent</artifactId>\n\t\t<version>3.0.0-SNAPSHOT</version>\n\t\t<relativePath>..</relativePath>\n\t</parent>\n\n\t<dependencies>\n\t\t<dependency>\n\t\t\t<groupId>org.springframework.cloud</groupId>\n\t\t\t<artifactId>spring-cloud-deployer-spi</artifactId>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.springframework.cloud</groupId>\n\t\t\t<artifactId>spring-cloud-deployer-resource-maven</artifactId>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.springframework.cloud</groupId>\n\t\t\t<artifactId>spring-cloud-deployer-resource-docker</artifactId>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.springframework.boot</groupId>\n\t\t\t<artifactId>spring-boot-starter</artifactId>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.springframework</groupId>\n\t\t\t<artifactId>spring-web</artifactId>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>com.fasterxml.jackson.core</groupId>\n\t\t\t<artifactId>jackson-databind</artifactId>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.springframework.boot</groupId>\n\t\t\t<artifactId>spring-boot-starter-validation</artifactId>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.hibernate.validator</groupId>\n\t\t\t<artifactId>hibernate-validator-annotation-processor</artifactId>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.springframework.boot</groupId>\n\t\t\t<artifactId>spring-boot-configuration-processor</artifactId>\n\t\t\t<optional>true</optional>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.springframework.cloud</groupId>\n\t\t\t<artifactId>spring-cloud-deployer-spi-test</artifactId>\n\t\t\t<version>${project.version}</version>\n\t\t\t<scope>test</scope>\n\t\t\t<exclusions>\n\t\t\t\t<exclusion>\n\t\t\t\t\t<groupId>junit</groupId>\n\t\t\t\t\t<artifactId>junit</artifactId>\n\t\t\t\t</exclusion>\n\t\t\t</exclusions>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.awaitility</groupId>\n\t\t\t<artifactId>awaitility</artifactId>\n\t\t\t<scope>test</scope>\n\t\t</dependency>\n\t</dependencies>\n\t<build>\n\t\t<plugins>\n\t\t\t<plugin>\n\t\t\t\t<groupId>org.apache.maven.plugins</groupId>\n\t\t\t\t<artifactId>maven-surefire-plugin</artifactId>\n\t\t\t\t<version>3.1.2</version>\n\t\t\t\t<configuration>\n\t\t\t\t\t<forkCount>1</forkCount>\n\t\t\t\t\t<reuseForks>false</reuseForks>\n\t\t\t\t\t<threadCount>1</threadCount>\n\t\t\t\t\t<parallel>methods</parallel>\n\t\t\t\t</configuration>\n\t\t\t</plugin>\n\t\t</plugins>\n\t</build>\n</project>\n"
  },
  {
    "path": "spring-cloud-deployer-local/src/main/java/org/springframework/cloud/deployer/spi/local/AbstractLocalDeployerSupport.java",
    "content": "/*\n * Copyright 2016-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.local;\n\nimport java.io.IOException;\nimport java.net.URL;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.LinkedHashMap;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.Set;\n\nimport com.fasterxml.jackson.core.JsonProcessingException;\nimport com.fasterxml.jackson.core.type.TypeReference;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport org.springframework.boot.context.properties.bind.Bindable;\nimport org.springframework.boot.context.properties.bind.Binder;\nimport org.springframework.boot.context.properties.source.MapConfigurationPropertySource;\nimport org.springframework.cloud.deployer.resource.docker.DockerResource;\nimport org.springframework.cloud.deployer.spi.core.AppDeploymentRequest;\nimport org.springframework.cloud.deployer.spi.core.RuntimeEnvironmentInfo;\nimport org.springframework.cloud.deployer.spi.util.RuntimeVersionUtils;\nimport org.springframework.http.ResponseEntity;\nimport org.springframework.http.client.SimpleClientHttpRequestFactory;\nimport org.springframework.util.Assert;\nimport org.springframework.web.client.RestTemplate;\n\n/**\n * Base class for local app deployer and task launcher providing support for common\n * functionality.\n *\n * @author Janne Valkealahti\n * @author Mark Fisher\n * @author Ilayaperumal Gopinathan\n * @author Thomas Risberg\n * @author Oleg Zhurakousky\n * @author Vinicius Carvalho\n * @author Michael Minella\n * @author David Turanski\n * @author Christian Tzolov\n */\npublic abstract class AbstractLocalDeployerSupport {\n\n\tprotected static Set<Integer> usedPorts = Collections.newSetFromMap(new LinkedHashMap<Integer, Boolean>() {\n\t\t@Override\n\t\tprotected boolean removeEldestEntry(Map.Entry<Integer, Boolean> eldest) {\n\t\t\treturn size() > 1000;\n\t\t}\n\t});\n\n\tprotected final Logger logger = LoggerFactory.getLogger(getClass());\n\n\tpublic static final String SPRING_APPLICATION_JSON = \"SPRING_APPLICATION_JSON\";\n\n\tpublic static final int DEFAULT_SERVER_PORT = 8080;\n\n\tprivate static final String USE_SPRING_APPLICATION_JSON_KEY = LocalDeployerProperties.PREFIX\n\t\t\t+ \".use-spring-application-json\";\n\n\tstatic final String SERVER_PORT_KEY = \"server.port\";\n\n\tstatic final String SERVER_PORT_KEY_COMMAND_LINE_ARG = \"--\" + SERVER_PORT_KEY + \"=\";\n\n\tprivate static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();\n\n\tprivate final LocalDeployerProperties localDeployerProperties;\n\n\tprivate final RestTemplate restTemplate;\n\n\tprivate final JavaCommandBuilder javaCommandBuilder;\n\n\tprivate final DockerCommandBuilder dockerCommandBuilder;\n\n\t/**\n\t * Instantiates a new abstract deployer support.\n\t *\n\t * @param localDeployerProperties the local deployer properties\n\t */\n\tpublic AbstractLocalDeployerSupport(LocalDeployerProperties localDeployerProperties) {\n\t\tAssert.notNull(localDeployerProperties, \"LocalDeployerProperties must not be null\");\n\t\tthis.localDeployerProperties = localDeployerProperties;\n\t\tthis.javaCommandBuilder = new JavaCommandBuilder(localDeployerProperties);\n\t\tthis.dockerCommandBuilder = new DockerCommandBuilder(localDeployerProperties.getDocker().getNetwork());\n\t\tthis.restTemplate = buildRestTemplate(localDeployerProperties);\n\t}\n\n\t/**\n\t * Builds a {@link RestTemplate} used for calling app's shutdown endpoint. If needed can\n\t * be overridden from an implementing class. This default implementation sets connection\n\t * and read timeouts for {@link SimpleClientHttpRequestFactory} and configures\n\t * {@link RestTemplate} to use that factory. If shutdown timeout in properties negative,\n\t * returns default {@link RestTemplate} which doesn't use timeouts.\n\t *\n\t * @param properties the local deployer properties\n\t * @return the rest template\n\t */\n\tprotected RestTemplate buildRestTemplate(LocalDeployerProperties properties) {\n\t\tif (properties != null && properties.getShutdownTimeout() > -1) {\n\t\t\tSimpleClientHttpRequestFactory clientHttpRequestFactory = new SimpleClientHttpRequestFactory();\n\t\t\tclientHttpRequestFactory.setConnectTimeout(properties.getShutdownTimeout() * 1000);\n\t\t\tclientHttpRequestFactory.setReadTimeout(properties.getShutdownTimeout() * 1000);\n\t\t\treturn new RestTemplate(clientHttpRequestFactory);\n\t\t}\n\t\t// fall back to plain default constructor\n\t\treturn new RestTemplate();\n\t}\n\n\t/**\n\t * Create the RuntimeEnvironmentInfo.\n\t *\n\t * @return the local runtime environment info\n\t */\n\tprotected RuntimeEnvironmentInfo createRuntimeEnvironmentInfo(Class<?> spiClass, Class<?> implementationClass) {\n\t\treturn new RuntimeEnvironmentInfo.Builder().spiClass(spiClass)\n\t\t\t\t.implementationName(implementationClass.getSimpleName())\n\t\t\t\t.implementationVersion(RuntimeVersionUtils.getVersion(implementationClass)).platformType(\"Local\")\n\t\t\t\t.platformApiVersion(System.getProperty(\"os.name\") + \" \" + System.getProperty(\"os.version\"))\n\t\t\t\t.platformClientVersion(System.getProperty(\"os.version\"))\n\t\t\t\t.platformHostVersion(System.getProperty(\"os.version\")).build();\n\t}\n\n\t/**\n\t * Gets the local deployer properties.\n\t *\n\t * @return the local deployer properties\n\t */\n\tfinal protected LocalDeployerProperties getLocalDeployerProperties() {\n\t\treturn localDeployerProperties;\n\t}\n\n\t/**\n\t * Builds the process builder. Application properties are expected to be calculated prior\n\t * to this method. No additional consolidation of application properties is done while\n\t * creating the {@code ProcessBuilder}.\n\t *\n\t * @param request the request\n\t * @param appInstanceEnv the instance environment variables\n\t * @return the process builder\n\t */\n\tprotected ProcessBuilder buildProcessBuilder(AppDeploymentRequest request, Map<String, String> appInstanceEnv,\n\t\t\tOptional<Integer> appInstanceNumber, String deploymentId) {\n\t\tAssert.notNull(request, \"AppDeploymentRequest must be set\");\n\n\t\tMap<String, String> appPropertiesToUse = formatApplicationProperties(request, appInstanceEnv);\n\t\tif (logger.isInfoEnabled()) {\n\t\t\tlogger.info(\"Preparing to run an application from {}. This may take some time if the artifact must be \" +\n\t\t\t\t\t\"downloaded from a remote host.\", request.getResource());\n\t\t}\n\n\t\tLocalDeployerProperties localDeployerProperties = bindDeploymentProperties(request.getDeploymentProperties());\n\n\t\tOptional<DebugAddress> debugAddressOption = DebugAddress.from(localDeployerProperties, appInstanceNumber.orElse(0));\n\n\t\tProcessBuilder builder = getCommandBuilder(request)\n\t\t\t\t.buildExecutionCommand(request, appPropertiesToUse, deploymentId, appInstanceNumber,\n\t\t\t\t\t\tlocalDeployerProperties, debugAddressOption);\n\n\t\tlogger.info(String.format(\"Command to be executed: %s\", String.join(\" \", builder.command())));\n\t\t//logger.debug(String.format(\"Environment Variables to be used : %s\", builder.environment().entrySet().stream()\n\t\t//\t\t.map(entry -> entry.getKey() + \" : \" + entry.getValue()).collect(Collectors.joining(\", \"))));\n\t\treturn builder;\n\t}\n\n\t/**\n\t * Detects the Command builder by the type of the resource in the requests.\n\t * @param request deployment request containing information about the resource to be deployed.\n\t * @return Returns a command builder compatible with the type of the resource set in the request.\n\t */\n\tprotected CommandBuilder getCommandBuilder(AppDeploymentRequest request) {\n\t\treturn (request.getResource() instanceof DockerResource) ? this.dockerCommandBuilder : this.javaCommandBuilder;\n\t}\n\n\t/**\n\t * tweak escaping double quotes needed for windows\n\t * @param commands\n\t * @return\n\t */\n\tpublic static String[] windowsSupport(String[] commands) {\n\t\tif (LocalDeployerUtils.isWindows()) {\n\t\t\tfor (int i = 0; i < commands.length; i++) {\n\t\t\t\tcommands[i] = commands[i].replace(\"\\\"\", \"\\\\\\\"\");\n\t\t\t}\n\t\t}\n\t\treturn commands;\n\t}\n\n\t/**\n\t * This will merge the deployment properties that were passed in at runtime with the\n\t * deployment properties of the Deployer instance.\n\t * @param runtimeDeploymentProperties deployment properties passed in at runtime\n\t * @return merged deployer properties\n\t */\n\tprotected LocalDeployerProperties bindDeploymentProperties(Map<String, String> runtimeDeploymentProperties) {\n\t\tLocalDeployerProperties copyOfDefaultProperties = new LocalDeployerProperties(this.localDeployerProperties);\n\t\treturn new Binder(new MapConfigurationPropertySource(runtimeDeploymentProperties))\n\t\t\t\t.bind(LocalDeployerProperties.PREFIX, Bindable.ofInstance(copyOfDefaultProperties))\n\t\t\t\t.orElse(copyOfDefaultProperties);\n\t}\n\n\tprotected Map<String, String> formatApplicationProperties(AppDeploymentRequest request,\n\t\t\tMap<String, String> appInstanceEnvToUse) {\n\t\tMap<String, String> applicationPropertiesToUse = new HashMap<>(appInstanceEnvToUse);\n\n\t\tif (useSpringApplicationJson(request)) {\n\t\t\ttry {\n\t\t\t\t// If SPRING_APPLICATION_JSON is found, explode it and merge back into appProperties\n\t\t\t\tif (applicationPropertiesToUse.containsKey(SPRING_APPLICATION_JSON)) {\n\t\t\t\t\tapplicationPropertiesToUse\n\t\t\t\t\t\t\t.putAll(OBJECT_MAPPER.readValue(applicationPropertiesToUse.get(SPRING_APPLICATION_JSON),\n\t\t\t\t\t\t\t\t\tnew TypeReference<HashMap<String, String>>() {\n\t\t\t\t\t\t\t\t\t}));\n\t\t\t\t\tapplicationPropertiesToUse.remove(SPRING_APPLICATION_JSON);\n\t\t\t\t}\n\t\t\t}\n\t\t\tcatch (IOException e) {\n\t\t\t\tthrow new IllegalArgumentException(\n\t\t\t\t\t\t\"Unable to read existing SPRING_APPLICATION_JSON to merge properties\", e);\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\tString saj = OBJECT_MAPPER.writeValueAsString(applicationPropertiesToUse);\n\n\t\t\t\tapplicationPropertiesToUse = new HashMap<>(1);\n\n\t\t\t\tapplicationPropertiesToUse.put(SPRING_APPLICATION_JSON, saj);\n\t\t\t}\n\t\t\tcatch (JsonProcessingException e) {\n\t\t\t\tthrow new IllegalArgumentException(\n\t\t\t\t\t\t\"Unable to create SPRING_APPLICATION_JSON from application properties\", e);\n\t\t\t}\n\t\t}\n\n\t\treturn applicationPropertiesToUse;\n\t}\n\n\t/**\n\t * Shut down the {@link Process} backing the application {@link Instance}. If the\n\t * application exposes a {@code /shutdown} endpoint, that will be invoked followed by a\n\t * wait that will not exceed the number of seconds indicated by\n\t * {@link LocalDeployerProperties#getShutdownTimeout()} . If the timeout period is exceeded (or\n\t * if the {@code /shutdown} endpoint is not exposed), the process will be shut down via\n\t * {@link Process#destroy()}.\n\t *\n\t * @param instance the application instance to shut down\n\t */\n\tprotected void shutdownAndWait(Instance instance) {\n\t\ttry {\n\t\t\tint timeout = getLocalDeployerProperties().getShutdownTimeout();\n\t\t\tif (timeout > 0) {\n\t\t\t\tlogger.debug(\"About to call shutdown endpoint for the instance {}\", instance);\n\t\t\t\tResponseEntity<String> response = restTemplate.postForEntity(\n\t\t\t\t\t\tinstance.getBaseUrl() + \"/shutdown\", null, String.class);\n\t\t\t\tlogger.debug(\"Response for shutdown endpoint completed for the instance {} with response {}\", instance,\n\t\t\t\t\t\tresponse);\n\t\t\t\tif (response.getStatusCode().is2xxSuccessful()) {\n\t\t\t\t\tlong timeoutTimestamp = System.currentTimeMillis() + (timeout * 1000);\n\t\t\t\t\twhile (isAlive(instance.getProcess()) && System.currentTimeMillis() < timeoutTimestamp) {\n\t\t\t\t\t\tThread.sleep(1000);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tcatch (InterruptedException e) {\n\t\t\tThread.currentThread().interrupt();\n\t\t}\n\t\tcatch (Exception e) {\n\t\t\t// ignore all other errors as we're going to\n\t\t\t// destroy process if it's alive\n\t\t}\n\t\tfinally {\n\t\t\tif (isAlive(instance.getProcess())) {\n\t\t\t\tlogger.debug(\"About to call destroy the process for the instance {}\", instance);\n\t\t\t\tinstance.getProcess().destroy();\n\t\t\t\tlogger.debug(\"Call completed to destroy the process for the instance {}\", instance);\n\t\t\t}\n\t\t}\n\t}\n\n\t// Copy-pasting of JDK8+ isAlive method to retain JDK7 compatibility\n\tprotected boolean isAlive(Process process) {\n\t\ttry {\n\t\t\tlogger.debug(\"About to call exitValue of the process {}\", process);\n\t\t\tprocess.exitValue();\n\t\t\tlogger.debug(\"Call to exitValue of the process {} complete, return false\", process);\n\t\t\treturn false;\n\t\t}\n\t\tcatch (IllegalThreadStateException e) {\n\t\t\tlogger.debug(\"Call to exitValue of the process {} threw exception, return true\", process);\n\t\t\treturn true;\n\t\t}\n\t}\n\n\tprotected boolean useSpringApplicationJson(AppDeploymentRequest request) {\n\t\treturn request.getDefinition().getProperties().containsKey(USE_SPRING_APPLICATION_JSON_KEY)\n\t\t\t\t|| this.localDeployerProperties.isUseSpringApplicationJson();\n\t}\n\n\t// TODO (tzolov): This method has a treacherous side affect! Apart from returning the computed Port it also modifies\n\t//  the appInstanceEnvVars map! Later is used for down stream app deployment configuration.\n\t//  As a consequence if you place the calcServerPort in wrong place (for example after the buildProcessBuilder(..)\n\t//  call then the Port configuration won't be known to the command builder).\n\t//  Proper solution is to (1) either make the method void and rename it to calcAndSetServerPort or (2) make the\n\t//  method return the mutated appInstanceEnvVars. Sync with the SCT team because the method is used by the\n\t//  LocalTaskLauncher (e.g. prod. grade)\n\tprotected int calcServerPort(AppDeploymentRequest request, boolean useDynamicPort,\n\t\t\tMap<String, String> appInstanceEnvVars) {\n\n\t\tint port = DEFAULT_SERVER_PORT;\n\t\tInteger commandLineArgPort = isServerPortKeyPresentOnArgs(request);\n\n\t\tif (useDynamicPort) {\n\t\t\tport = getRandomPort(request);\n\t\t\tappInstanceEnvVars.put(LocalAppDeployer.SERVER_PORT_KEY, String.valueOf(port));\n\t\t}\n\t\telse if (commandLineArgPort != null) {\n\t\t\tport = commandLineArgPort;\n\t\t}\n\t\telse if (request.getDefinition().getProperties().containsKey(LocalAppDeployer.SERVER_PORT_KEY)) {\n\t\t\tport = Integer.parseInt(request.getDefinition().getProperties().get(LocalAppDeployer.SERVER_PORT_KEY));\n\t\t}\n\n\t\treturn port;\n\t}\n\n\t/**\n\t * Will check if {@link LocalDeployerProperties#INHERIT_LOGGING} is set by checking\n\t * deployment properties.\n\t */\n\tprotected boolean shouldInheritLogging(AppDeploymentRequest request) {\n\t\tLocalDeployerProperties bindDeployerProperties = bindDeploymentProperties(request.getDeploymentProperties());\n\t\treturn bindDeployerProperties.isInheritLogging();\n\t}\n\n\tpublic synchronized int getRandomPort(AppDeploymentRequest request) {\n\t\tSet<Integer> availPorts = new HashSet<>();\n\t\t// SocketUtils.findAvailableTcpPorts retries 6 times, add additional retry on top.\n\t\tfor (int retryCount = 0; retryCount < 5; retryCount++) {\n\t\t\tint randomInt = getCommandBuilder(request).getPortSuggestion(localDeployerProperties);\n\t\t\ttry {\n\t\t\t\tavailPorts = DeployerSocketUtils.findAvailableTcpPorts(5, randomInt, randomInt + 5);\n\t\t\t\ttry {\n\t\t\t\t\t// Give some time for the system to release up ports that were scanned.\n\t\t\t\t\tThread.sleep(100);\n\t\t\t\t}\n\t\t\t\tcatch (InterruptedException e) {\n\t\t\t\t\tlogger.debug(e.getMessage() + \"Retrying to find available ports.\");\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcatch (IllegalStateException e) {\n\t\t\t\tlogger.debug(e.getMessage() + \"  Retrying to find available ports.\");\n\t\t\t}\n\t\t}\n\t\tif (availPorts.isEmpty()) {\n\t\t\tthrow new IllegalStateException(\n\t\t\t\t\t\"Could not find an available TCP port in the range\" + localDeployerProperties.getPortRange());\n\t\t}\n\n\t\tint finalPort = -1;\n\t\tlogger.debug(\"Available Ports: \" + availPorts);\n\t\tfor (Integer freePort : availPorts) {\n\t\t\tif (!usedPorts.contains(freePort)) {\n\t\t\t\tfinalPort = freePort;\n\t\t\t\tusedPorts.add(finalPort);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\tif (finalPort == -1) {\n\t\t\tthrow new IllegalStateException(\n\t\t\t\t\t\"Could not find a free random port range \" + localDeployerProperties.getPortRange());\n\t\t}\n\t\tlogger.debug(\"Using Port: \" + finalPort);\n\t\treturn finalPort;\n\t}\n\n\tprotected Integer isServerPortKeyPresentOnArgs(AppDeploymentRequest request) {\n\t\treturn request.getCommandlineArguments().stream()\n\t\t\t\t.filter(argument -> argument.startsWith(SERVER_PORT_KEY_COMMAND_LINE_ARG))\n\t\t\t\t.map(argument -> Integer.parseInt(argument.replace(SERVER_PORT_KEY_COMMAND_LINE_ARG, \"\").trim()))\n\t\t\t\t.findFirst()\n\t\t\t\t.orElse(null);\n\t}\n\n\tprotected interface Instance {\n\n\t\tURL getBaseUrl();\n\n\t\tProcess getProcess();\n\t}\n}\n"
  },
  {
    "path": "spring-cloud-deployer-local/src/main/java/org/springframework/cloud/deployer/spi/local/CommandBuilder.java",
    "content": "/*\n * Copyright 2016-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.local;\n\nimport java.net.URL;\nimport java.util.Map;\nimport java.util.Optional;\n\nimport org.springframework.cloud.deployer.spi.core.AppDeploymentRequest;\n\n/**\n * Strategy interface for Execution Command builder.\n *\n * @author Ilayaperumal Gopinathan\n * @author Thomas Risberg\n * @author Michael Minella\n * @author Christian Tzolov\n */\npublic interface CommandBuilder {\n\n\t/**\n\t * Builds the execution command for an application.\n\t *\n\t * @param request the request for the application to execute.\n\t * @param appInstanceNumber application instance id.\n\t * @param appInstanceEnv the env vars tha might be needed when building the execution command.\n\t * @param debugAddress application remote debug address.\n\t * @return the build command as a string array.\n\t */\n\tProcessBuilder buildExecutionCommand(AppDeploymentRequest request,\n\t\t\tMap<String, String> appInstanceEnv, String deployerId,\n\t\t\tOptional<Integer> appInstanceNumber,\n\t\t\tLocalDeployerProperties localDeployerProperties,\n\t\t\tOptional<DebugAddress> debugAddress);\n\n\t/**\n\t * Compute an App unique URL over apps deployerId, instance index and computed port.\n\t * @param deploymentId App deployment id.\n\t * @param index App instance index.\n\t * @param port App port.\n\t * @return Returns app's URL.\n\t */\n\tURL getBaseUrl(String deploymentId, int index, int port);\n\n\t/**\n\t * Allow the concrete implementation to suggests the target application port.\n\t * @param localDeployerProperties\n\t * @return Returns a port suggestion.\n\t */\n\tint getPortSuggestion(LocalDeployerProperties localDeployerProperties);\n\n\t/**\n\t * Computes the JDWP options with the provided suspend and address arguments.\n\t * @param suspend suspend debug argument.\n\t * @param address debug address.\n\t * @return Returns the JDWP options with the provided suspend and address arguments.\n\t */\n\tdefault String getJdwpOptions(String suspend, String address) {\n\t\treturn String.format(\"-agentlib:jdwp=transport=dt_socket,server=y,suspend=%s,address=%s\", suspend, address);\n\t}\n}\n"
  },
  {
    "path": "spring-cloud-deployer-local/src/main/java/org/springframework/cloud/deployer/spi/local/DebugAddress.java",
    "content": "/*\n * Copyright 2020-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.local;\n\nimport java.util.Optional;\nimport java.util.regex.Pattern;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport org.springframework.util.StringUtils;\n\n/**\n * Helper for parsing the Debugging address for both the legacy debug-port and the new debug-address properties.\n * The debug-port supports only Java 8 and is deprecated. The debug-address can be used for jdk 8 as well as\n * jdk 9 and newer.\n * When set the debug-address property has precedence over debug-port.\n *\n * @author Christian Tzolov\n */\npublic class DebugAddress {\n\tprivate static final Pattern HOSTNAME_PATTERN = Pattern.compile(\"^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\\\\-]*[a-zA-Z0-9])\\\\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\\\\-]*[A-Za-z0-9])$\");\n\tprivate static final Pattern IP_PATTERN = Pattern.compile(\"^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$\");\n\tprivate static final Pattern PORT_PATTERN = Pattern.compile(\"^([0-9]{1,4}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])$\");\n\n\tpublic final static Logger logger = LoggerFactory.getLogger(DebugAddress.class);\n\n\tprivate final String host;\n\tprivate final String port;\n\tprivate final String address;\n\tprivate final String suspend;\n\n\tprivate DebugAddress(String host, int port, String suspend) {\n\t\tthis.host = host;\n\t\tthis.port = \"\" + port;\n\t\tthis.suspend = (StringUtils.hasText(suspend)) ? suspend.trim() : \"y\";\n\t\tthis.address = (StringUtils.hasText(host)) ? String.format(\"%s:%s\", host, port) : this.port;\n\t}\n\n\tpublic String getHost() {\n\t\treturn host;\n\t}\n\n\tpublic String getPort() {\n\t\treturn port;\n\t}\n\n\tpublic String getSuspend() {\n\t\treturn suspend;\n\t}\n\n\tpublic String getAddress() {\n\t\treturn this.address;\n\t}\n\n\tpublic static Optional<DebugAddress> from(LocalDeployerProperties deployerProperties, int instanceNumber) {\n\n\t\tif (!StringUtils.hasText(deployerProperties.getDebugAddress()) && deployerProperties.getDebugPort() == null) {\n\t\t\treturn Optional.empty();\n\t\t}\n\n\t\tString debugHost = null;\n\t\tString debugPort = (\"\" + deployerProperties.getDebugPort()).trim();\n\n\t\tif (StringUtils.hasText(deployerProperties.getDebugAddress())) {\n\t\t\tString[] addressParts = deployerProperties.getDebugAddress().split(\":\");\n\n\t\t\tif (addressParts.length == 1) { // JDK 8 only\n\t\t\t\tdebugPort = addressParts[0].trim();\n\t\t\t}\n\t\t\telse if (addressParts.length == 2) { // JDK 9+\n\t\t\t\tdebugHost = addressParts[0].trim();\n\t\t\t\tdebugPort = addressParts[1].trim();\n\n\t\t\t\tif (!(\"*\".equals(debugHost)\n\t\t\t\t\t\t|| HOSTNAME_PATTERN.matcher(debugHost).matches()\n\t\t\t\t\t\t|| IP_PATTERN.matcher(debugHost).matches())) {\n\t\t\t\t\tlogger.warn(\"Invalid debug Host: {}\", deployerProperties.getDebugAddress());\n\t\t\t\t\treturn Optional.empty();\n\t\t\t\t}\n\t\t\t}\n\t\t\telse {\n\t\t\t\tlogger.warn(\"Invalid debug address: {}\", deployerProperties.getDebugAddress());\n\t\t\t\treturn Optional.empty();\n\t\t\t}\n\t\t}\n\n\t\tif (!PORT_PATTERN.matcher(debugPort).matches()) {\n\t\t\tlogger.warn(\"Invalid debug port: {}\", debugPort);\n\t\t\treturn Optional.empty();\n\t\t}\n\n\t\tint portToUse = Integer.parseInt(debugPort) + instanceNumber;\n\n\t\treturn Optional.of(new DebugAddress(debugHost, portToUse, deployerProperties.getDebugSuspend().toString()));\n\t}\n}\n"
  },
  {
    "path": "spring-cloud-deployer-local/src/main/java/org/springframework/cloud/deployer/spi/local/DeployerSocketUtils.java",
    "content": "/*\n * Copyright 2016-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.local;\n\nimport java.net.InetAddress;\nimport java.net.ServerSocket;\nimport java.util.Random;\nimport java.util.SortedSet;\nimport java.util.TreeSet;\n\nimport javax.net.ServerSocketFactory;\n\nimport org.springframework.util.Assert;\n\n/**\n * Simple utility methods for working with network sockets &mdash; for example, for\n * finding available ports on {@code localhost}.\n *\n * This is a replacement for SocketUtils.   SocketUtils was introduced in Spring Framework 4.0,\n * primarily to assist in writing integration tests which start an external server on an available random port.\n * However, these utilities make no guarantee about the subsequent availability\n * of a given port and are therefore unreliable. Instead of using SocketUtils to\n * find an available local port for a server, it is recommended that you rely on a\n * server's ability to start on a random port that it selects or is assigned by the operating system.\n * To interact with that server, you should query the server for the port it is currently using.\n *\n * @author Sam Brannen\n * @author Ben Hale\n * @author Arjen Poutsma\n * @author Gunnar Hillert\n * @author Gary Russell\n * @author Glenn Renfro\n * @deprecated to be replaced with a more robust mechanism in https://github.com/spring-cloud/spring-cloud-deployer-local/issues/215\n */\n@Deprecated\npublic class DeployerSocketUtils {\n\n\t/**\n\t * The default maximum value for port ranges used when finding an available socket\n\t * port.\n\t */\n\tstatic final int PORT_RANGE_MAX = 65535;\n\n\tprivate static final Random random = new Random(System.nanoTime());\n\n\t/**\n\t * Find an available TCP port randomly selected from the range [{@code minPort},\n\t * {@value #PORT_RANGE_MAX}].\n\t * @param minPort the minimum port number\n\t * @return an available TCP port number\n\t * @throws IllegalStateException if no available port could be found\n\t */\n\tpublic static int findAvailableTcpPort(int minPort) {\n\t\treturn findAvailableTcpPort(minPort, PORT_RANGE_MAX);\n\t}\n\n\t/**\n\t * Find an available TCP port randomly selected from the range [{@code minPort},\n\t * {@code maxPort}].\n\t * @param minPort the minimum port number\n\t * @param maxPort the maximum port number\n\t * @return an available TCP port number\n\t * @throws IllegalStateException if no available port could be found\n\t */\n\tpublic static int findAvailableTcpPort(int minPort, int maxPort) {\n\t\treturn SocketType.TCP.findAvailablePort(minPort, maxPort);\n\t}\n\n\t/**\n\t * Find the requested number of available TCP ports, each randomly selected from the\n\t * range [{@code minPort}, {@code maxPort}].\n\t * @param numRequested the number of available ports to find\n\t * @param minPort the minimum port number\n\t * @param maxPort the maximum port number\n\t * @return a sorted set of available TCP port numbers\n\t * @throws IllegalStateException if the requested number of available ports could not\n\t * be found\n\t */\n\tpublic static SortedSet<Integer> findAvailableTcpPorts(int numRequested, int minPort, int maxPort) {\n\t\treturn SocketType.TCP.findAvailablePorts(numRequested, minPort, maxPort);\n\t}\n\n\tprivate enum SocketType {\n\t\tTCP {\n\t\t\t@Override\n\t\t\tprotected boolean isPortAvailable(int port) {\n\t\t\t\ttry {\n\t\t\t\t\tServerSocket serverSocket = ServerSocketFactory.getDefault().createServerSocket(port, 1,\n\t\t\t\t\t\t\tInetAddress.getByName(\"localhost\"));\n\t\t\t\t\tserverSocket.close();\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t\tcatch (Exception ex) {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\n\t\t/**\n\t\t * Determine if the specified port for this {@code SocketType} is currently\n\t\t * available on {@code localhost}.\n\t\t */\n\t\tprotected abstract boolean isPortAvailable(int port);\n\n\t\t/**\n\t\t * Find a pseudo-random port number within the range [{@code minPort},\n\t\t * {@code maxPort}].\n\t\t * @param minPort the minimum port number\n\t\t * @param maxPort the maximum port number\n\t\t * @return a random port number within the specified range\n\t\t */\n\t\tprivate int findRandomPort(int minPort, int maxPort) {\n\t\t\tint portRange = maxPort - minPort;\n\t\t\treturn minPort + random.nextInt(portRange + 1);\n\t\t}\n\n\t\t/**\n\t\t * Find an available port for this {@code SocketType}, randomly selected from the\n\t\t * range [{@code minPort}, {@code maxPort}].\n\t\t * @param minPort the minimum port number\n\t\t * @param maxPort the maximum port number\n\t\t * @return an available port number for this socket type\n\t\t * @throws IllegalStateException if no available port could be found\n\t\t */\n\t\tint findAvailablePort(int minPort, int maxPort) {\n\t\t\tAssert.isTrue(minPort > 0, \"'minPort' must be greater than 0\");\n\t\t\tAssert.isTrue(maxPort >= minPort, \"'maxPort' must be greater than or equal to 'minPort'\");\n\t\t\tAssert.isTrue(maxPort <= PORT_RANGE_MAX, \"'maxPort' must be less than or equal to \" + PORT_RANGE_MAX);\n\n\t\t\tint portRange = maxPort - minPort;\n\t\t\tint candidatePort;\n\t\t\tint searchCounter = 0;\n\t\t\tdo {\n\t\t\t\tif (searchCounter > portRange) {\n\t\t\t\t\tthrow new IllegalStateException(\n\t\t\t\t\t\t\tString.format(\"Could not find an available %s port in the range [%d, %d] after %d attempts\",\n\t\t\t\t\t\t\t\t\tname(), minPort, maxPort, searchCounter));\n\t\t\t\t}\n\t\t\t\tcandidatePort = findRandomPort(minPort, maxPort);\n\t\t\t\tsearchCounter++;\n\t\t\t}\n\t\t\twhile (!isPortAvailable(candidatePort));\n\n\t\t\treturn candidatePort;\n\t\t}\n\n\t\t/**\n\t\t * Find the requested number of available ports for this {@code SocketType}, each\n\t\t * randomly selected from the range [{@code minPort}, {@code maxPort}].\n\t\t * @param numRequested the number of available ports to find\n\t\t * @param minPort the minimum port number\n\t\t * @param maxPort the maximum port number\n\t\t * @return a sorted set of available port numbers for this socket type\n\t\t * @throws IllegalStateException if the requested number of available ports could\n\t\t * not be found\n\t\t */\n\t\tSortedSet<Integer> findAvailablePorts(int numRequested, int minPort, int maxPort) {\n\t\t\tAssert.isTrue(minPort > 0, \"'minPort' must be greater than 0\");\n\t\t\tAssert.isTrue(maxPort > minPort, \"'maxPort' must be greater than 'minPort'\");\n\t\t\tAssert.isTrue(maxPort <= PORT_RANGE_MAX, \"'maxPort' must be less than or equal to \" + PORT_RANGE_MAX);\n\t\t\tAssert.isTrue(numRequested > 0, \"'numRequested' must be greater than 0\");\n\t\t\tAssert.isTrue((maxPort - minPort) >= numRequested,\n\t\t\t\t\t\"'numRequested' must not be greater than 'maxPort' - 'minPort'\");\n\n\t\t\tSortedSet<Integer> availablePorts = new TreeSet<>();\n\t\t\tint attemptCount = 0;\n\t\t\twhile ((++attemptCount <= numRequested + 100) && availablePorts.size() < numRequested) {\n\t\t\t\tavailablePorts.add(findAvailablePort(minPort, maxPort));\n\t\t\t}\n\n\t\t\tif (availablePorts.size() != numRequested) {\n\t\t\t\tthrow new IllegalStateException(\n\t\t\t\t\t\tString.format(\"Could not find %d available %s ports in the range [%d, %d]\", numRequested,\n\t\t\t\t\t\t\t\tname(), minPort, maxPort));\n\t\t\t}\n\n\t\t\treturn availablePorts;\n\t\t}\n\t}\n}\n\n"
  },
  {
    "path": "spring-cloud-deployer-local/src/main/java/org/springframework/cloud/deployer/spi/local/DockerCommandBuilder.java",
    "content": "/*\n * Copyright 2016-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.local;\n\nimport java.io.IOException;\nimport java.net.URL;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.concurrent.ThreadLocalRandom;\nimport java.util.function.Supplier;\nimport java.util.stream.Collectors;\nimport java.util.stream.Stream;\n\nimport com.fasterxml.jackson.core.type.TypeReference;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport org.springframework.cloud.deployer.resource.docker.DockerResource;\nimport org.springframework.cloud.deployer.spi.app.AppDeployer;\nimport org.springframework.cloud.deployer.spi.core.AppDeploymentRequest;\nimport org.springframework.util.StringUtils;\n\n/**\n * Command builder used to craft the command used when running apps inside docker containers.\n *\n * @author Ilayaperumal Gopinathan\n * @author Eric Bottard\n * @author Henryk Konsek\n * @author Thomas Risberg\n * @author Michael Minella\n * @author Christian Tzolov\n */\npublic class DockerCommandBuilder implements CommandBuilder {\n\n\tprivate static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();\n\n\t/**\n\t * Name of the deployment property used to specify the container name pattern to use.\n\t */\n\tpublic static final String DOCKER_CONTAINER_NAME_KEY = AppDeployer.PREFIX + \"docker.container.name\";\n\n\tprivate final Logger logger = LoggerFactory.getLogger(getClass());\n\tprivate final String dockerNetwork;\n\n\tpublic DockerCommandBuilder(String dockerNetwork) {\n\t\tthis.dockerNetwork = dockerNetwork;\n\t}\n\n\t@Override\n\tpublic int getPortSuggestion(LocalDeployerProperties localDeployerProperties) {\n\t\treturn ThreadLocalRandom.current().nextInt(localDeployerProperties.getDocker().getPortRange().getLow(),\n\t\t\t\tlocalDeployerProperties.getDocker().getPortRange().getHigh());\n\t}\n\n\t@Override\n\tpublic URL getBaseUrl(String deploymentId, int index, int port) {\n\t\ttry {\n\t\t\treturn new URL(\"http\", String.format(\"%s-%d\", deploymentId, index), port, \"\");\n\t\t}\n\t\tcatch (Exception e) {\n\t\t\tthrow new IllegalArgumentException(e);\n\t\t}\n\t}\n\n\t@Override\n\tpublic ProcessBuilder buildExecutionCommand(AppDeploymentRequest request, Map<String, String> appInstanceEnv, String deployerId,\n\t\t\tOptional<Integer> appInstanceNumber, LocalDeployerProperties localDeployerProperties,\n\t\t\tOptional<DebugAddress> debugAddressOption) {\n\n\t\tappInstanceEnv.put(\"deployerId\", deployerId);\n\t\tList<String> commands = addDockerOptions(request, appInstanceEnv, appInstanceNumber, localDeployerProperties, debugAddressOption);\n\t\tcommands.addAll(request.getCommandlineArguments());\n\t\tlogger.debug(\"Docker Command = \" + commands);\n\t\treturn new ProcessBuilder(Arrays.asList(AbstractLocalDeployerSupport.windowsSupport(commands.toArray(new String[0]))));\n\t}\n\n\tprivate List<String> addDockerOptions(AppDeploymentRequest request, Map<String, String> appInstanceEnv,\n\t\t\tOptional<Integer> appInstanceNumber, LocalDeployerProperties localDeployerProperties,\n\t\t\tOptional<DebugAddress> debugAddressOption) {\n\n\t\tList<String> commands = new ArrayList<>();\n\t\tcommands.add(\"docker\");\n\t\tcommands.add(\"run\");\n\n\t\tif (StringUtils.hasText(this.dockerNetwork)) {\n\t\t\tcommands.add(\"--network\");\n\t\t\tcommands.add(this.dockerNetwork);\n\t\t}\n\n\t\tif (localDeployerProperties.getDocker().isDeleteContainerOnExit()) {\n\t\t\tcommands.add(\"--rm\");\n\t\t}\n\n\t\t// Add env vars\n\t\tfor (String env : appInstanceEnv.keySet()) {\n\t\t\tcommands.add(\"-e\");\n\t\t\tcommands.add(String.format(\"%s=%s\", env, appInstanceEnv.get(env)));\n\t\t}\n\n\t\tdebugAddressOption.ifPresent(debugAddress -> {\n\t\t\tString debugCommand = getJdwpOptions(debugAddress.getSuspend(), debugAddress.getAddress());\n\t\t\tlogger.debug(\"Deploying app with Debug Command = [{}]\", debugCommand);\n\n\t\t\tcommands.add(\"-e\");\n\t\t\tcommands.add(\"JAVA_TOOL_OPTIONS=\" + debugCommand);\n\t\t\tcommands.add(\"-p\");\n\t\t\tcommands.add(String.format(\"%s:%s\", debugAddress.getPort(), debugAddress.getPort()));\n\t\t});\n\n\t\tString port = getPort(appInstanceEnv);\n\n\t\tif (StringUtils.hasText(port)) {\n\t\t\tcommands.add(\"-p\");\n\t\t\tcommands.add(String.format(\"%s:%s\", port, port));\n\t\t}\n\n\t\tapplyPortMappings(commands,localDeployerProperties);\n\t\tapplyVolumeMountings(commands,localDeployerProperties);\n\t\tapplyAdditionalHosts(commands,localDeployerProperties);\n\n\t\tif (request.getDeploymentProperties().containsKey(DOCKER_CONTAINER_NAME_KEY)) {\n\t\t\tif (appInstanceNumber.isPresent()) {\n\t\t\t\tcommands.add(String.format(\"--name=%s-%d\", request.getDeploymentProperties().get(DOCKER_CONTAINER_NAME_KEY), appInstanceNumber.get()));\n\t\t\t}\n\t\t\telse {\n\t\t\t\tcommands.add(String.format(\"--name=%s\", request.getDeploymentProperties().get(DOCKER_CONTAINER_NAME_KEY)));\n\t\t\t}\n\t\t}\n\t\telse {\n\t\t\tString group = request.getDeploymentProperties().get(AppDeployer.GROUP_PROPERTY_KEY);\n\t\t\tif (StringUtils.hasText(group)) {\n\t\t\t\tString deploymentId = String.format(\"%s.%s\", group, request.getDefinition().getName());\n\t\t\t\tint index = appInstanceNumber.orElse(0);\n\t\t\t\tcommands.add(String.format(\"--name=%s-%d\", deploymentId, index));\n\t\t\t}\n\t\t}\n\n\t\tDockerResource dockerResource = (DockerResource) request.getResource();\n\n\t\ttry {\n\t\t\tString dockerImageURI = dockerResource.getURI().toString();\n\t\t\tcommands.add(dockerImageURI.substring(\"docker:\".length()));\n\t\t}\n\t\tcatch (IOException e) {\n\t\t\tthrow new IllegalStateException(e);\n\t\t}\n\n\t\treturn commands;\n\t}\n\n\tprivate void applyVolumeMountings(List<String> commands, LocalDeployerProperties localDeployerProperties) {\n\t\tString volumeMounts = localDeployerProperties.getDocker().getVolumeMounts();\n\t\tif (StringUtils.hasText(volumeMounts)) {\n\t\t\tfor (String v : parseMapping(volumeMounts)) {\n\t\t\t\tcommands.add(\"-v\");\n\t\t\t\tcommands.add(v);\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate void applyAdditionalHosts(List<String> commands, LocalDeployerProperties localDeployerProperties) {\n\t\tString additionalHosts = localDeployerProperties.getDocker().getAdditionalHosts();\n\t\tif (StringUtils.hasText(additionalHosts)) {\n\t\t\tfor (String v : parseMapping(additionalHosts)) {\n\t\t\t\tcommands.add(\"--add-host\");\n\t\t\t\tcommands.add(v);\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate void applyPortMappings(List<String> commands, LocalDeployerProperties properties) {\n\t\tString portMappings = properties.getDocker().getPortMappings();\n\t\tif (StringUtils.hasText(portMappings)) {\n\t\t\tfor (String p : parseMapping(portMappings)) {\n\t\t\t\tcommands.add(\"-p\");\n\t\t\t\tcommands.add(p);\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate String getPort(Map<String, String> appInstanceEnv) {\n\t\tif (appInstanceEnv.containsKey(AbstractLocalDeployerSupport.SPRING_APPLICATION_JSON)) {\n\t\t\ttry {\n\t\t\t\tHashMap<String, String> flatProperties = new HashMap<>((OBJECT_MAPPER.readValue(\n\t\t\t\t\t\tappInstanceEnv.get(AbstractLocalDeployerSupport.SPRING_APPLICATION_JSON),\n\t\t\t\t\t\tnew TypeReference<HashMap<String, String>>() {})));\n\n\t\t\t\tif (flatProperties.containsKey(LocalAppDeployer.SERVER_PORT_KEY)) {\n\t\t\t\t\treturn flatProperties.get(LocalAppDeployer.SERVER_PORT_KEY);\n\t\t\t\t}\n\t\t\t\t// fall back to appInstanceEnv.\n\t\t\t}\n\t\t\tcatch (IOException e) {\n\t\t\t\tthrow new IllegalArgumentException(\"Unable to determine server port from SPRING_APPLICATION_JSON\");\n\t\t\t}\n\t\t}\n\t\treturn appInstanceEnv.get(LocalAppDeployer.SERVER_PORT_KEY);\n\t}\n\n\tprivate List<String> parseMapping(String map) {\n\t\tSupplier<Stream<String>> stream = () -> Arrays.stream(map.split(\",\"));\n\t\tstream.get().filter(s -> !s.contains(\":\")).forEach(s -> logger.warn(\"incomplete mapping {} will be ignored\", s));\n\t\treturn stream.get().filter(s -> s.contains(\":\")).collect(Collectors.toList());\n\t}\n}\n"
  },
  {
    "path": "spring-cloud-deployer-local/src/main/java/org/springframework/cloud/deployer/spi/local/HttpProbeExecutor.java",
    "content": "/*\n * Copyright 2020 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage org.springframework.cloud.deployer.spi.local;\n\nimport java.net.URI;\nimport java.net.URL;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport org.springframework.cloud.deployer.spi.local.LocalDeployerProperties.HttpProbe;\nimport org.springframework.http.HttpStatusCode;\nimport org.springframework.http.ResponseEntity;\nimport org.springframework.web.client.RestTemplate;\nimport org.springframework.web.util.DefaultUriBuilderFactory;\n\n/**\n * Simple probe executor using rest endpoints.\n *\n * @author Janne Valkealahti\n *\n */\npublic class HttpProbeExecutor {\n\n\tprivate static final Logger logger = LoggerFactory.getLogger(HttpProbeExecutor.class);\n    private final RestTemplate restTemplate;\n    private final URI uri;\n\n    public HttpProbeExecutor(RestTemplate restTemplate, URI uri) {\n        this.restTemplate = restTemplate;\n        this.uri = uri;\n    }\n\n    public static HttpProbeExecutor from(URL baseUrl, HttpProbe httpProbe) {\n        URI base = null;\n        try {\n            base = baseUrl.toURI();\n        } catch (Exception e) {\n        }\n        if (httpProbe == null || httpProbe.getPath() == null || base == null) {\n            return null;\n        }\n        DefaultUriBuilderFactory uriBuilderFactory = new DefaultUriBuilderFactory(base.toString());\n        uriBuilderFactory.setEncodingMode(DefaultUriBuilderFactory.EncodingMode.NONE);\n        URI uri = uriBuilderFactory.builder().path(\"{path}\").build(httpProbe.getPath());\n        return new HttpProbeExecutor(new RestTemplate(), uri);\n    }\n\n    public boolean probe() {\n        try {\n            logger.info(\"Probing for {}\", this.uri);\n            ResponseEntity<String> response = restTemplate.getForEntity(uri, String.class);\n            HttpStatusCode statusCode = response.getStatusCode();\n            boolean ok = statusCode.is2xxSuccessful();\n            if (!ok) {\n                logger.info(\"Probe for {} returned {}:{}\", this.uri, statusCode, response.getBody());\n            }\n            return ok;\n        } catch (Exception e) {\n            logger.trace(\"Probe error for {}\", this.uri, e);\n        }\n        return false;\n    }\n}\n"
  },
  {
    "path": "spring-cloud-deployer-local/src/main/java/org/springframework/cloud/deployer/spi/local/JavaCommandBuilder.java",
    "content": "/*\n * Copyright 2016-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.local;\n\nimport java.io.IOException;\nimport java.net.Inet4Address;\nimport java.net.URL;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Iterator;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Map.Entry;\nimport java.util.Optional;\nimport java.util.concurrent.ThreadLocalRandom;\nimport java.util.regex.Pattern;\nimport java.util.stream.Stream;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport org.springframework.boot.context.properties.bind.Bindable;\nimport org.springframework.boot.context.properties.bind.Binder;\nimport org.springframework.boot.context.properties.source.MapConfigurationPropertySource;\nimport org.springframework.cloud.deployer.spi.app.AppDeployer;\nimport org.springframework.cloud.deployer.spi.core.AppDeploymentRequest;\nimport org.springframework.cloud.deployer.spi.util.ByteSizeUtils;\nimport org.springframework.core.io.Resource;\nimport org.springframework.util.Assert;\nimport org.springframework.util.StringUtils;\n\n/**\n * @author Mark Pollack\n * @author Ilayaperumal Gopinathan\n * @author Thomas Risberg\n * @author Michael Minella\n */\npublic class JavaCommandBuilder implements CommandBuilder {\n\n\tprivate final Logger logger = LoggerFactory.getLogger(getClass());\n\n\tprivate final LocalDeployerProperties properties;\n\n\tpublic JavaCommandBuilder(LocalDeployerProperties properties) {\n\t\tthis.properties = properties;\n\t}\n\n\t@Override\n\tpublic int getPortSuggestion(LocalDeployerProperties localDeployerProperties) {\n\t\treturn ThreadLocalRandom.current().nextInt(localDeployerProperties.getPortRange().getLow(),\n\t\t\tlocalDeployerProperties.getPortRange().getHigh());\n\t}\n\n\t@Override\n\tpublic URL getBaseUrl(String deploymentId, int index, int port) {\n\t\ttry {\n\t\t\treturn new URL(\"http\", Inet4Address.getLocalHost().getHostAddress(), port, \"\");\n\t\t} catch (Exception e) {\n\t\t\tthrow new IllegalArgumentException(e);\n\t\t}\n\t}\n\n\t@Override\n\tpublic ProcessBuilder buildExecutionCommand(\n\t\tAppDeploymentRequest request, Map<String, String> appInstanceEnv,\n\t\tString deployerId, Optional<Integer> appInstanceNumber, LocalDeployerProperties localDeployerProperties,\n\t\tOptional<DebugAddress> debugAddressOption\n\t) {\n\t\tArrayList<String> commands = new ArrayList<>();\n\t\tMap<String, String> deploymentProperties = request.getDeploymentProperties();\n\t\tString bootVersion = deploymentProperties.get(\"spring.cloud.deployer.bootVersion\");\n\t\tif (!StringUtils.hasLength(bootVersion)) {\n\t\t\tOptional<String> bootArg = request.getCommandlineArguments()\n\t\t\t\t.stream()\n\t\t\t\t.filter(s ->\n\t\t\t\t\ts.startsWith(\"--spring.cloud.deployer.bootVersion=\") || s.startsWith(\"--spring.cloud.deployer.boot-version=\"))\n\t\t\t\t.findFirst();\n\t\t\tif (bootArg.isPresent()) {\n\t\t\t\tint indexEq = bootArg.get().indexOf('=');\n\t\t\t\tAssert.isTrue(indexEq > 0, () -> \"Expected = in argument:\" + bootArg.get());\n\t\t\t\tbootVersion = bootArg.get().substring(indexEq + 1).trim();\n\t\t\t}\n\t\t}\n\t\tif (bootVersion == null) {\n\t\t\tbootVersion = \"3\"; // safe to launch boot 2 with Java 17\n\t\t}\n\t\tcommands.add(bindDeploymentProperties(deploymentProperties).getJavaCommand(bootVersion));\n\n\t\tdebugAddressOption.ifPresent(debugAddress -> commands.add(getJdwpOptions(debugAddress.getSuspend(), debugAddress.getAddress())));\n\n\t\t// Add Java System Properties (ie -Dmy.prop=val) before main class or -jar\n\t\taddJavaOptions(commands, deploymentProperties, properties);\n\t\taddJavaExecutionOptions(commands, request);\n\t\tcommands.addAll(request.getCommandlineArguments());\n\t\tlogger.debug(\"Java Command = {}\", commands);\n\n\t\tProcessBuilder builder = new ProcessBuilder(AbstractLocalDeployerSupport.windowsSupport(commands.toArray(new String[0])));\n\n\t\t// retain before we put in app related variables.\n\t\tretainEnvVars(builder.environment(), localDeployerProperties);\n\t\tbuilder.environment().putAll(appInstanceEnv);\n\n\t\treturn builder;\n\t}\n\n\t/**\n\t * Retain the environment variable strings in the provided set indicated by\n\t * {@link LocalDeployerProperties#getEnvVarsToInherit}.\n\t * This assumes that the provided set can be modified.\n\t *\n\t * @param vars                    set of environment variable strings\n\t * @param localDeployerProperties local deployer properties\n\t */\n\tprotected void retainEnvVars(Map<String, String> vars, LocalDeployerProperties localDeployerProperties) {\n\t\tList<String> patterns = new ArrayList<>(Arrays.asList(localDeployerProperties.getEnvVarsToInherit()));\n\t\tfor (Iterator<Entry<String, String>> iterator = vars.entrySet().iterator(); iterator.hasNext(); ) {\n\t\t\tEntry<String, String> entry = iterator.next();\n\t\t\tString var = entry.getKey();\n\t\t\tboolean retain = false;\n\t\t\tfor (String pattern : patterns) {\n\t\t\t\tif (Pattern.matches(pattern, var)) {\n\t\t\t\t\tretain = true;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (!retain) {\n\t\t\t\titerator.remove();\n\t\t\t}\n\t\t}\n\t}\n\n\tprotected void addJavaOptions(\n\t\tList<String> commands, Map<String, String> deploymentProperties,\n\t\tLocalDeployerProperties localDeployerProperties\n\t) {\n\t\tString memory = null;\n\t\tif (deploymentProperties.containsKey(AppDeployer.MEMORY_PROPERTY_KEY)) {\n\t\t\tmemory = \"-Xmx\" +\n\t\t\t\tByteSizeUtils.parseToMebibytes(deploymentProperties.get(AppDeployer.MEMORY_PROPERTY_KEY)) + \"m\";\n\t\t}\n\n\t\tString javaOptsString = bindDeploymentProperties(deploymentProperties).getJavaOpts();\n\t\tif (javaOptsString == null && memory != null) {\n\t\t\tcommands.add(memory);\n\t\t}\n\n\t\tif (javaOptsString != null) {\n\t\t\tString[] javaOpts = StringUtils.tokenizeToStringArray(javaOptsString, \" \");\n\t\t\tboolean noJavaMemoryOption = Stream.of(javaOpts).noneMatch(s -> s.startsWith(\"-Xmx\"));\n\t\t\tif (noJavaMemoryOption && memory != null) {\n\t\t\t\tcommands.add(memory);\n\t\t\t}\n\t\t\tcommands.addAll(Arrays.asList(javaOpts));\n\t\t} else {\n\t\t\tif (localDeployerProperties.getJavaOpts() != null) {\n\t\t\t\tString[] javaOpts = StringUtils.tokenizeToStringArray(localDeployerProperties.getJavaOpts(), \" \");\n\t\t\t\tcommands.addAll(Arrays.asList(javaOpts));\n\t\t\t}\n\t\t}\n\t}\n\n\tprotected void addJavaExecutionOptions(List<String> commands, AppDeploymentRequest request) {\n\t\tcommands.add(\"-jar\");\n\t\tResource resource = request.getResource();\n\t\ttry {\n\t\t\tcommands.add(resource.getFile().getAbsolutePath());\n\t\t} catch (IOException e) {\n\t\t\tthrow new IllegalStateException(e);\n\t\t}\n\t}\n\n\t/**\n\t * This will merge the deployment properties that were passed in at runtime with the deployment properties\n\t * of the Deployer instance.\n\t *\n\t * @param runtimeDeploymentProperties deployment properties passed in at runtime\n\t * @return merged deployer properties\n\t */\n\tprotected LocalDeployerProperties bindDeploymentProperties(Map<String, String> runtimeDeploymentProperties) {\n\t\tLocalDeployerProperties copyOfDefaultProperties = new LocalDeployerProperties(this.properties);\n\t\treturn new Binder(new MapConfigurationPropertySource(runtimeDeploymentProperties))\n\t\t\t.bind(LocalDeployerProperties.PREFIX, Bindable.ofInstance(copyOfDefaultProperties))\n\t\t\t.orElse(copyOfDefaultProperties);\n\t}\n\n}\n"
  },
  {
    "path": "spring-cloud-deployer-local/src/main/java/org/springframework/cloud/deployer/spi/local/LocalActuatorTemplate.java",
    "content": "/*\n * Copyright 2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.local;\n\nimport org.springframework.cloud.deployer.spi.app.AbstractActuatorTemplate;\nimport org.springframework.cloud.deployer.spi.app.AppAdmin;\nimport org.springframework.cloud.deployer.spi.app.AppDeployer;\nimport org.springframework.cloud.deployer.spi.app.AppInstanceStatus;\nimport org.springframework.web.client.RestTemplate;\nimport org.springframework.web.util.UriComponentsBuilder;\n\n/**\n * @author David Turanski\n */\npublic class LocalActuatorTemplate extends AbstractActuatorTemplate {\n\n\tpublic LocalActuatorTemplate(RestTemplate restTemplate, AppDeployer appDeployer,\n\t\t\tAppAdmin appAdmin) {\n\t\tsuper(restTemplate, appDeployer, appAdmin);\n\t}\n\n\t@Override\n\tprotected String actuatorUrlForInstance(AppInstanceStatus appInstanceStatus) {\n\t\treturn UriComponentsBuilder.fromUriString(appInstanceStatus.getAttributes().get(\"url\"))\n\t\t\t\t.path(\"/actuator\").toUriString();\n\t}\n}\n"
  },
  {
    "path": "spring-cloud-deployer-local/src/main/java/org/springframework/cloud/deployer/spi/local/LocalAppDeployer.java",
    "content": "/*\n * Copyright 2016-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.local;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.io.InputStreamReader;\nimport java.lang.ProcessBuilder.Redirect;\nimport java.lang.reflect.Field;\nimport java.net.HttpURLConnection;\nimport java.net.URL;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.TreeMap;\nimport java.util.concurrent.ConcurrentHashMap;\n\nimport jakarta.annotation.PreDestroy;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport org.springframework.cloud.deployer.spi.app.AppDeployer;\nimport org.springframework.cloud.deployer.spi.app.AppInstanceStatus;\nimport org.springframework.cloud.deployer.spi.app.AppScaleRequest;\nimport org.springframework.cloud.deployer.spi.app.AppStatus;\nimport org.springframework.cloud.deployer.spi.app.DeploymentState;\nimport org.springframework.cloud.deployer.spi.core.AppDeploymentRequest;\nimport org.springframework.cloud.deployer.spi.core.RuntimeEnvironmentInfo;\nimport org.springframework.cloud.deployer.spi.local.LocalDeployerProperties.HttpProbe;\nimport org.springframework.util.Assert;\nimport org.springframework.util.FileCopyUtils;\nimport org.springframework.util.StringUtils;\n\n/**\n * An {@link AppDeployer} implementation that spins off a new JVM process per app\n * instance.\n *\n * @author Eric Bottard\n * @author Marius Bogoevici\n * @author Mark Fisher\n * @author Ilayaperumal Gopinathan\n * @author Janne Valkealahti\n * @author Patrick Peralta\n * @author Thomas Risberg\n * @author Oleg Zhurakousky\n * @author Michael Minella\n * @author Glenn Renfro\n * @author Christian Tzolov\n * @author David Turanski\n */\npublic class LocalAppDeployer extends AbstractLocalDeployerSupport implements AppDeployer {\n\n\tprivate static final Logger logger = LoggerFactory.getLogger(LocalAppDeployer.class);\n\tprivate static final String JMX_DEFAULT_DOMAIN_KEY = \"spring.jmx.default-domain\";\n\tprivate static final String ENDPOINTS_SHUTDOWN_ENABLED_KEY = \"endpoints.shutdown.enabled\";\n\tprivate final Map<String, AppInstancesHolder> running = new ConcurrentHashMap<>();\n\n\t/**\n\t * Instantiates a new local app deployer.\n\t *\n\t * @param properties the properties\n\t */\n\tpublic LocalAppDeployer(LocalDeployerProperties properties) {\n\t\tsuper(properties);\n\t}\n\n\t/**\n\t * Returns the process exit value. We explicitly use Integer instead of int to indicate\n\t * that if {@code NULL} is returned, the process is still running.\n\t * @param process the process\n\t * @return the process exit value or {@code NULL} if process is still alive\n\t */\n\tprivate static Integer getProcessExitValue(Process process) {\n\t\ttry {\n\t\t\treturn process.exitValue();\n\t\t}\n\t\tcatch (IllegalThreadStateException e) {\n\t\t\t// process is still alive\n\t\t\treturn null;\n\t\t}\n\t}\n\n\t/**\n\t * Gets the local process pid if available. This should be a safe workaround for unix\n\t * systems where reflection can be used to get pid. More reliable way should land with\n\t * jdk9.\n\t *\n\t * @param p the process\n\t * @return the local process pid\n\t */\n\tprivate static synchronized int getLocalProcessPid(Process p) {\n\t\tint pid = 0;\n\t\ttry {\n\t\t\tif (p.getClass().getName().equals(\"java.lang.UNIXProcess\")) {\n\t\t\t\tField f = p.getClass().getDeclaredField(\"pid\");\n\t\t\t\tf.setAccessible(true);\n\t\t\t\tpid = f.getInt(p);\n\t\t\t\tf.setAccessible(false);\n\t\t\t}\n\t\t}\n\t\tcatch (Exception e) {\n\t\t\tpid = 0;\n\t\t}\n\t\treturn pid;\n\t}\n\n\t@Override\n\tpublic String deploy(AppDeploymentRequest request) {\n\t\tString group = request.getDeploymentProperties().get(GROUP_PROPERTY_KEY);\n\t\tString deploymentId = String.format(\"%s.%s\", group, request.getDefinition().getName());\n\t\tvalidateStatus(deploymentId, DeploymentState.unknown);\n\t\tList<AppInstance> processes = new ArrayList<>();\n\t\trunning.put(deploymentId, new AppInstancesHolder(processes, request));\n\n\t\ttry {\n\t\t\tPath workDir = createWorkingDir(request.getDeploymentProperties(), deploymentId);\n\n\t\t\tString countProperty = request.getDeploymentProperties().get(COUNT_PROPERTY_KEY);\n\t\t\tint count = (StringUtils.hasText(countProperty)) ? Integer.parseInt(countProperty) : 1;\n\n\t\t\tfor (int index = 0; index < count; index++) {\n\t\t\t\tprocesses.add(deployApp(request, workDir, group, deploymentId, index, request.getDeploymentProperties()));\n\t\t\t}\n\t\t}\n\t\tcatch (IOException e) {\n\t\t\tthrow new RuntimeException(\"Exception trying to deploy \" + request, e);\n\t\t}\n\t\treturn deploymentId;\n\t}\n\n\t@Override\n\tpublic void scale(AppScaleRequest appScaleRequest) {\n\t\tvalidateStatus(appScaleRequest.getDeploymentId(), DeploymentState.deployed);\n\t\tAppInstancesHolder holder = running.get(appScaleRequest.getDeploymentId());\n\t\tList<AppInstance> instances = holder != null ? holder.instances : null;\n\t\tif (instances == null) {\n\t\t\tthrow new IllegalStateException(\n\t\t\t\t\t\"Can't find existing instances for deploymentId \" + appScaleRequest.getDeploymentId());\n\t\t}\n\t\tAppDeploymentRequest request = holder.request;\n\n\t\tString group = request.getDeploymentProperties().get(GROUP_PROPERTY_KEY);\n\t\tString deploymentId = String.format(\"%s.%s\", group, request.getDefinition().getName());\n\n\t\ttry {\n\t\t\tPath workDir = createWorkingDir(request.getDeploymentProperties(), deploymentId);\n\t\t\tint deltaCount = appScaleRequest.getCount() - instances.size();\n\t\t\tint targetCount = instances.size() + deltaCount;\n\n\t\t\tif (deltaCount > 0) {\n\t\t\t\tfor (int index = instances.size(); index < targetCount; index++) {\n\t\t\t\t\tinstances.add(deployApp(request, workDir, group, deploymentId, index, request.getDeploymentProperties()));\n\t\t\t\t}\n\t\t\t}\n\t\t\telse if (deltaCount < 0) {\n\t\t\t\tList<AppInstance> processes = new ArrayList<>();\n\t\t\t\tfor (int index = instances.size() - 1; index >= targetCount; index--) {\n\t\t\t\t\tprocesses.add(instances.remove(index));\n\t\t\t\t}\n\t\t\t\tfor (AppInstance instance : processes) {\n\t\t\t\t\tif (isAlive(instance.getProcess())) {\n\t\t\t\t\t\tlogger.info(\"Un-deploying app with deploymentId {} instance {}.\", deploymentId, instance.getInstanceNumber());\n\t\t\t\t\t\tshutdownAndWait(instance);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t}\n\t\tcatch (IOException e) {\n\t\t\tthrow new RuntimeException(\"Exception trying to deploy \" + request, e);\n\t\t}\n\t}\n\n\t@Override\n\tpublic void undeploy(String id) {\n\t\tAppInstancesHolder holder = running.get(id);\n\t\tList<AppInstance> processes = holder != null ? holder.instances : null;\n\t\tif (processes != null) {\n\t\t\tfor (AppInstance instance : processes) {\n\t\t\t\tif (isAlive(instance.getProcess())) {\n\t\t\t\t\tlogger.info(\"Un-deploying app with deploymentId {} instance {}.\", id, instance.getInstanceNumber());\n\t\t\t\t\tshutdownAndWait(instance);\n\t\t\t\t}\n\t\t\t}\n\t\t\trunning.remove(id);\n\t\t}\n\t\telse {\n\t\t\tthrow new IllegalStateException(String.format(\"App with deploymentId %s is not in a deployed state.\", id));\n\t\t}\n\t}\n\n\t@Override\n\tpublic AppStatus status(String id) {\n\t\tAppInstancesHolder holder = running.get(id);\n\t\tList<AppInstance> instances = holder != null ? holder.instances : null;\n\t\tAppStatus.Builder builder = AppStatus.of(id);\n\n\t\tif (instances != null) {\n\t\t\tfor (AppInstance instance : instances) {\n\t\t\t\tbuilder.with(instance);\n\t\t\t}\n\t\t}\n\n\t\treturn builder.build();\n\t}\n\n\t@Override\n\tpublic String getLog(String id) {\n\t\tAppInstancesHolder holder = running.get(id);\n\t\tList<AppInstance> instances = holder != null ? holder.instances : null;\n\t\tStringBuilder stringBuilder = new StringBuilder();\n\t\tif (instances != null) {\n\t\t\tfor (AppInstance instance : instances) {\n\t\t\t\tString stderr = instance.getStdErr();\n\t\t\t\tif (StringUtils.hasText(stderr)) {\n\t\t\t\t\tstringBuilder.append(\"stderr:\\n\");\n\t\t\t\t\tstringBuilder.append(stderr);\n\t\t\t\t}\n\t\t\t\tString stdout = instance.getStdOut();\n\t\t\t\tif (StringUtils.hasText(stdout)) {\n\t\t\t\t\tstringBuilder.append(\"stdout:\\n\");\n\t\t\t\t\tstringBuilder.append(stdout);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn stringBuilder.toString();\n\t}\n\n\t@Override\n\tpublic RuntimeEnvironmentInfo environmentInfo() {\n\t\treturn super.createRuntimeEnvironmentInfo(AppDeployer.class, this.getClass());\n\t}\n\n\t@PreDestroy\n\tpublic void shutdown() {\n\t\tfor (String deploymentId : running.keySet()) {\n\t\t\tundeploy(deploymentId);\n\t\t}\n\t}\n\n\tprivate AppInstance deployApp(AppDeploymentRequest request, Path workDir, String group, String deploymentId,\n\t\t\tint index, Map<String, String> deploymentProperties) throws IOException {\n\n\t\tLocalDeployerProperties localDeployerPropertiesToUse = bindDeploymentProperties(deploymentProperties);\n\n\t\t// consolidatedAppProperties is a Map of all application properties to be used by\n\t\t// the app being launched. These values should end up as environment variables\n\t\t// either explicitly or as a SPRING_APPLICATION_JSON value.\n\t\tHashMap<String, String> consolidatedAppProperties = new HashMap<>(request.getDefinition().getProperties());\n\n\t\tconsolidatedAppProperties.put(JMX_DEFAULT_DOMAIN_KEY, deploymentId);\n\n\t\tif (!request.getDefinition().getProperties().containsKey(ENDPOINTS_SHUTDOWN_ENABLED_KEY)) {\n\t\t\tconsolidatedAppProperties.put(ENDPOINTS_SHUTDOWN_ENABLED_KEY, \"true\");\n\t\t}\n\n\t\tconsolidatedAppProperties.put(\"endpoints.jmx.unique-names\", \"true\");\n\n\t\tif (group != null) {\n\t\t\tconsolidatedAppProperties.put(\"spring.cloud.application.group\", group);\n\t\t}\n\n\t\t// This Map is the consolidated application properties *for the instance*\n\t\t// to be deployed in this iteration\n\t\tMap<String, String> appInstanceEnv = new HashMap<>(consolidatedAppProperties);\n\n\t\t// we only set 'normal' style props reflecting what we set for env format\n\t\t// for cross reference to work inside SAJ.\n\t\t// looks like for now we can't remove these env style formats as i.e.\n\t\t// DeployerIntegrationTestProperties in tests really assume 'INSTANCE_INDEX' and\n\t\t// this might be indication that we can't yet fully remove those.\n\t\tString guid = toGuid(deploymentId, index);\n\t\tif (useSpringApplicationJson(request)) {\n\t\t\tappInstanceEnv.put(\"instance.index\", Integer.toString(index));\n\t\t\tappInstanceEnv.put(\"spring.cloud.stream.instanceIndex\", Integer.toString(index));\n\t\t\tappInstanceEnv.put(\"spring.application.index\", Integer.toString(index));\n\t\t\tappInstanceEnv.put(\"spring.cloud.application.guid\", guid);\n\t\t}\n\t\telse {\n\t\t\tappInstanceEnv.put(\"INSTANCE_INDEX\", Integer.toString(index));\n\t\t\tappInstanceEnv.put(\"SPRING_APPLICATION_INDEX\", Integer.toString(index));\n\t\t\tappInstanceEnv.put(\"SPRING_CLOUD_APPLICATION_GUID\", guid);\n\t\t}\n\n\t\tthis.getLocalDeployerProperties().getAppAdmin().addCredentialsToAppEnvironmentAsProperties(appInstanceEnv);\n\n\t\tboolean useDynamicPort = !request.getDefinition().getProperties().containsKey(SERVER_PORT_KEY);\n\t\t// WATCH OUT: The calcServerPort sets the computed port in the appInstanceEnv#SERVER_PORT_KEY.\n\t\t//  Later is implicitly passed to and used inside the command builder. Therefore the calcServerPort() method\n\t\t//  must always be called before the buildProcessBuilder(..)!\n\t\tint port = calcServerPort(request, useDynamicPort, appInstanceEnv);\n\n\t\tProcessBuilder builder = buildProcessBuilder(request, appInstanceEnv, Optional.of(index), deploymentId)\n\t\t\t\t.inheritIO();\n\t\tbuilder.directory(workDir.toFile());\n\n\t\tURL baseUrl = (StringUtils.hasText(localDeployerPropertiesToUse.getHostname())) ?\n\t\t\t\tnew URL(\"http\", localDeployerPropertiesToUse.getHostname(), port, \"\")\n\t\t\t\t: getCommandBuilder(request).getBaseUrl(deploymentId, index, port);\n\n\t\tAppInstance instance = new AppInstance(deploymentId, index, port, baseUrl,\n\t\t\t\tlocalDeployerPropertiesToUse.getStartupProbe(), localDeployerPropertiesToUse.getHealthProbe());\n\t\tif (this.shouldInheritLogging(request)) {\n\t\t\tinstance.start(builder, workDir);\n\t\t\tlogger.info(\"Deploying app with deploymentId {} instance {}.\\n   Logs will be inherited.\",\n\t\t\t\t\tdeploymentId, index);\n\t\t}\n\t\telse {\n\t\t\tinstance.start(builder, workDir, getLocalDeployerProperties().isDeleteFilesOnExit());\n\t\t\tlogger.info(\"Deploying app with deploymentId {} instance {}.\\n   Logs will be in {}\", deploymentId,\n\t\t\t\t\tindex, workDir);\n\t\t}\n\t\treturn instance;\n\t}\n\n\tprivate Path createWorkingDir(Map<String, String> deploymentProperties, String deploymentId) throws IOException {\n\t\tLocalDeployerProperties localDeployerPropertiesToUse = bindDeploymentProperties(deploymentProperties);\n\n\t\tPath workingDirectoryRoot = Files.createDirectories(localDeployerPropertiesToUse.getWorkingDirectoriesRoot());\n\t\tPath workDir = Files.createDirectories(workingDirectoryRoot.resolve(Long.toString(System.currentTimeMillis())).resolve(deploymentId));\n\n\t\tif (getLocalDeployerProperties().isDeleteFilesOnExit()) {\n\t\t\tworkDir.toFile().deleteOnExit();\n\t\t}\n\t\treturn workDir;\n\t}\n\n\tprivate void validateStatus(String deploymentId, DeploymentState expectedState) {\n\t\tDeploymentState state = status(deploymentId).getState();\n\t\tAssert.state(expectedState.equals(state),\n\t\t\t\tString.format(\"App with deploymentId [%s] with state [%s] doesn't match expected state [%s]\",\n\t\t\t\t\t\tdeploymentId, state, expectedState));\n\t}\n\n\tprivate static String toGuid(String deploymentId, int appIndex) {\n\t\treturn String.format(\"%s-%s\", deploymentId, appIndex);\n\t}\n\n\tprivate static class AppInstance implements Instance, AppInstanceStatus {\n\n\t\tprivate final String deploymentId;\n\n\t\tprivate final int instanceNumber;\n\n\t\tprivate final URL baseUrl;\n\t\tprivate final Map<String, String> attributes = new TreeMap<>();\n\t\tprivate int pid;\n\t\tprivate Process process;\n\t\tprivate File workFile;\n\t\tprivate File stdout;\n\t\tprivate File stderr;\n\t\tprivate int port;\n\t\tprivate HttpProbeExecutor startupProbeExecutor;\n\t\tprivate HttpProbeExecutor healthProbeExecutor;\n\t\tprivate boolean startupProbeOk;\n\n\t\tprivate AppInstance(String deploymentId, int instanceNumber, int port, URL baseUrl, HttpProbe startupProbe,\n\t\t\t\tHttpProbe healthProbe) {\n\t\t\tthis.deploymentId = deploymentId;\n\t\t\tthis.instanceNumber = instanceNumber;\n\t\t\tthis.port = port;\n\t\t\tthis.baseUrl = baseUrl;\n\t\t\tthis.attributes.put(\"port\", Integer.toString(port));\n\t\t\tthis.attributes.put(\"guid\", toGuid(deploymentId, instanceNumber));\n\t\t\tthis.attributes.put(\"url\", baseUrl.toString());\n\t\t\tthis.startupProbeExecutor = HttpProbeExecutor.from(baseUrl, startupProbe);\n\t\t\tthis.healthProbeExecutor = HttpProbeExecutor.from(baseUrl, healthProbe);\n\t\t}\n\n\t\t@Override\n\t\tpublic String getId() {\n\t\t\treturn deploymentId + \"-\" + instanceNumber;\n\t\t}\n\n\t\t@Override\n\t\tpublic URL getBaseUrl() {\n\t\t\treturn this.baseUrl;\n\t\t}\n\n\t\t@Override\n\t\tpublic Process getProcess() {\n\t\t\treturn this.process;\n\t\t}\n\n\t\t@Override\n\t\tpublic String toString() {\n\t\t\treturn String.format(\"%s [%s]\", getId(), getState());\n\t\t}\n\n\t\t@Override\n\t\tpublic DeploymentState getState() {\n\t\t\tInteger exit = getProcessExitValue(process);\n\t\t\t// TODO: consider using exit code mapper concept from batch\n\t\t\tif (exit != null) {\n\t\t\t\treturn DeploymentState.failed;\n\t\t\t}\n\t\t\tif (port < 1) {\n\t\t\t\t// Support case where user passed in zero or negative port indicating fully random\n\t\t\t\t// chosen by OS. In this case we simply return deployed if process is up.\n\t\t\t\t// Also we can't even try http check as we would not know port to connect to.\n\t\t\t\treturn DeploymentState.deployed;\n\t\t\t}\n\t\t\t// do startup probe first and only until we're deployed\n\t\t\tif (startupProbeExecutor != null && !startupProbeOk) {\n\t\t\t\tboolean ok = startupProbeExecutor.probe();\n\t\t\t\tif (ok) {\n\t\t\t\t\tstartupProbeOk = true;\n\t\t\t\t\treturn DeploymentState.deployed;\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\treturn DeploymentState.deploying;\n\t\t\t\t}\n\t\t\t}\n\t\t\t// now deployed, checking health probe\n\t\t\tif (healthProbeExecutor != null) {\n\t\t\t\treturn healthProbeExecutor.probe() ? DeploymentState.deployed : DeploymentState.failed;\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tHttpURLConnection urlConnection = (HttpURLConnection) baseUrl.openConnection();\n\t\t\t\turlConnection.setConnectTimeout(100);\n\t\t\t\turlConnection.connect();\n\t\t\t\turlConnection.disconnect();\n\t\t\t\treturn DeploymentState.deployed;\n\t\t\t}\n\t\t\tcatch (IOException e) {\n\t\t\t\treturn DeploymentState.deploying;\n\t\t\t}\n\t\t}\n\n\t\tpublic String getStdOut() {\n\t\t\ttry {\n\t\t\t\treturn FileCopyUtils.copyToString(new InputStreamReader(Files.newInputStream(this.stdout.toPath())));\n\t\t\t}\n\t\t\tcatch (IOException e) {\n\t\t\t\treturn \"Log retrieval returned \" + e.getMessage();\n\t\t\t}\n\t\t}\n\n\t\tpublic String getStdErr() {\n\t\t\ttry {\n\t\t\t\treturn FileCopyUtils.copyToString(new InputStreamReader(Files.newInputStream(this.stderr.toPath())));\n\t\t\t}\n\t\t\tcatch (IOException e) {\n\t\t\t\treturn \"Log retrieval returned \" + e.getMessage();\n\t\t\t}\n\t\t}\n\n\t\tpublic int getInstanceNumber() {\n\t\t\treturn instanceNumber;\n\t\t}\n\n\t\t@Override\n\t\tpublic Map<String, String> getAttributes() {\n\t\t\treturn this.attributes;\n\t\t}\n\n\t\t/**\n\t\t * Will start the process while redirecting 'out' and 'err' streams to the 'out' and 'err'\n\t\t * streams of this process.\n\t\t */\n\t\tprivate void start(ProcessBuilder builder, Path workDir) throws IOException {\n\t\t\tif (logger.isDebugEnabled()) {\n\t\t\t\tlogger.debug(\"Local App Deployer Commands: \" + String.join(\",\", builder.command())\n\t\t\t\t\t\t+ \", Environment: \" + builder.environment());\n\t\t\t}\n\t\t\tthis.workFile = workDir.toFile();\n\t\t\tthis.attributes.put(\"working.dir\", this.workFile.getAbsolutePath());\n\t\t\tthis.process = builder.start();\n\t\t\tthis.pid = getLocalProcessPid(this.process);\n\t\t\tif (pid > 0) {\n\t\t\t\t// add pid if we got it\n\t\t\t\tattributes.put(\"pid\", Integer.toString(pid));\n\t\t\t}\n\t\t}\n\n\t\tprivate void start(ProcessBuilder builder, Path workDir, boolean deleteOnExit) throws IOException {\n\t\t\tString workDirPath = workDir.toFile().getAbsolutePath();\n\n\t\t\tthis.stdout = Files.createFile(Paths.get(workDirPath, \"stdout_\" + instanceNumber + \".log\")).toFile();\n\t\t\tthis.attributes.put(\"stdout\", stdout.getAbsolutePath());\n\n\t\t\tthis.stderr = Files.createFile(Paths.get(workDirPath, \"stderr_\" + instanceNumber + \".log\")).toFile();\n\t\t\tthis.attributes.put(\"stderr\", stderr.getAbsolutePath());\n\n\t\t\tif (deleteOnExit) {\n\t\t\t\tthis.stdout.deleteOnExit();\n\t\t\t\tthis.stderr.deleteOnExit();\n\t\t\t}\n\t\t\tbuilder.redirectOutput(Redirect.to(this.stdout));\n\t\t\tbuilder.redirectError(Redirect.to(this.stderr));\n\n\t\t\tthis.start(builder, workDir);\n\t\t}\n\t}\n\n\tprivate static class AppInstancesHolder {\n\t\tfinal List<AppInstance> instances;\n\t\tfinal AppDeploymentRequest request;\n\n\t\tpublic AppInstancesHolder(List<AppInstance> instances, AppDeploymentRequest request) {\n\t\t\tthis.instances = instances;\n\t\t\tthis.request = request;\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "spring-cloud-deployer-local/src/main/java/org/springframework/cloud/deployer/spi/local/LocalDeployerAutoConfiguration.java",
    "content": "/*\n * Copyright 2016-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.local;\n\nimport org.springframework.boot.autoconfigure.AutoConfigureOrder;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;\nimport org.springframework.boot.context.properties.EnableConfigurationProperties;\nimport org.springframework.cloud.deployer.spi.app.ActuatorOperations;\nimport org.springframework.cloud.deployer.spi.app.AppDeployer;\nimport org.springframework.cloud.deployer.spi.task.TaskLauncher;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.core.Ordered;\nimport org.springframework.web.client.RestTemplate;\n\n/**\n * Creates a {@link LocalAppDeployer} and {@link LocalTaskLauncher}\n *\n * @author Mark Fisher\n * @author David Turanski\n */\n@Configuration\n@EnableConfigurationProperties(LocalDeployerProperties.class)\n@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)\npublic class LocalDeployerAutoConfiguration {\n\n\t@Bean\n\t@ConditionalOnMissingBean(AppDeployer.class)\n\tpublic AppDeployer appDeployer(LocalDeployerProperties properties) {\n\t\treturn new LocalAppDeployer(properties);\n\t}\n\n\t@Bean\n\t@ConditionalOnMissingBean(TaskLauncher.class)\n\tpublic TaskLauncher taskLauncher(LocalDeployerProperties properties) {\n\t\treturn new LocalTaskLauncher(properties);\n\t}\n\n\t@Bean\n\t@ConditionalOnMissingBean\n\tRestTemplate actuatorRestTemplate() {\n\t\treturn new RestTemplate();\n\t}\n\n\t@Bean\n\t@ConditionalOnMissingBean(ActuatorOperations.class)\n\tActuatorOperations actuatorOperations(RestTemplate actuatorRestTemplate, AppDeployer appDeployer,\n\t\t\tLocalDeployerProperties properties) {\n\t\treturn new LocalActuatorTemplate(actuatorRestTemplate, appDeployer, properties.getAppAdmin());\n\t}\n}\n"
  },
  {
    "path": "spring-cloud-deployer-local/src/main/java/org/springframework/cloud/deployer/spi/local/LocalDeployerProperties.java",
    "content": "/*\n * Copyright 2015-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.local;\n\nimport java.io.File;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.Arrays;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.stream.Collectors;\n\nimport jakarta.validation.constraints.Min;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport org.springframework.boot.context.properties.ConfigurationProperties;\nimport org.springframework.cloud.deployer.spi.app.AppAdmin;\nimport org.springframework.core.style.ToStringCreator;\nimport org.springframework.util.Assert;\nimport org.springframework.util.StringUtils;\nimport org.springframework.validation.annotation.Validated;\n\n/**\n * Configuration properties for the local deployer.\n *\n * @author Eric Bottard\n * @author Mark Fisher\n * @author Ilayaperumal Gopinathan\n * @author Oleg Zhurakousky\n * @author Vinicius Carvalho\n * @author David Turanski\n * @author Christian Tzolov\n */\n@Validated\n@ConfigurationProperties(prefix = LocalDeployerProperties.PREFIX)\npublic class LocalDeployerProperties {\n\n\t/**\n\t * Top level prefix for local deployer configuration properties.\n\t */\n\tpublic static final String PREFIX = \"spring.cloud.deployer.local\";\n\n\t/**\n\t * Deployer property allowing logging to be redirected to the output stream of\n\t * the process that triggered child process. Could be set per the entire\n\t * deployment (<em>i.e.</em> {@literal deployer.*.local.inheritLogging=true}) or\n\t * per individual application (<em>i.e.</em>\n\t * {@literal deployer.<app-name>.local.inheritLogging=true}).\n\t */\n\tpublic static final String INHERIT_LOGGING = PREFIX + \".inherit-logging\";\n\n\t/**\n\t * Remote debugging property allowing one to specify port for the remote debug\n\t * session. Must be set per individual application (<em>i.e.</em>\n\t * {@literal deployer.<app-name>.local.debugPort=9999}).\n\t *\n\t * @deprecated This is only JDK 8 compatible. Use the {@link #DEBUG_ADDRESS} instead for supporting all JDKs.\n\t */\n\tpublic static final String DEBUG_PORT = PREFIX + \".debug-port\";\n\n\t/**\n\t * Remote debugging property allowing one to specify the address for the remote debug\n\t * session. On Java versions 1.8 or older use the <em>port</em> format. On Java versions 1.9 or greater use the\n\t * <em>host:port</em> format. The host could default to <em>*</em>. May be set for individual applications (<em>i.e.</em>\n\t * {@literal deployer.<app-name>.local.debugAddress=*:9999}).\n\t */\n\tpublic static final String DEBUG_ADDRESS = PREFIX + \".debug-address\";\n\n\t/**\n\t * Remote debugging property allowing one to specify if the startup of the\n\t * application should be suspended until remote debug session is established.\n\t * Values must be either 'y' or 'n'. Must be set per individual application\n\t * (<em>i.e.</em> {@literal deployer.<app-name>.local.debugSuspend=y}).\n\t */\n\tpublic static final String DEBUG_SUSPEND = PREFIX + \".debug-suspend\";\n\n\tprivate static final Logger logger = LoggerFactory.getLogger(LocalDeployerProperties.class);\n\n\tprivate static final String JAVA_COMMAND = LocalDeployerUtils.isWindows() ? \"java.exe\" : \"java\";\n\n\t// looks like some windows systems uses 'Path' but process builder give it as 'PATH'\n\tprivate static final String[] ENV_VARS_TO_INHERIT_DEFAULTS_WIN = {\"TMP\", \"TEMP\", \"PATH\", \"Path\",\n\t\tAbstractLocalDeployerSupport.SPRING_APPLICATION_JSON};\n\n\tprivate static final String[] ENV_VARS_TO_INHERIT_DEFAULTS_OTHER = {\"TMP\", \"LANG\", \"LANGUAGE\", \"LC_.*\", \"PATH\",\n\t\tAbstractLocalDeployerSupport.SPRING_APPLICATION_JSON};\n\n\t/**\n\t * Directory in which all created processes will run and create log files.\n\t */\n\tprivate Path workingDirectoriesRoot = new File(System.getProperty(\"java.io.tmpdir\")).toPath();\n\n\t/**\n\t * Whether to delete created files and directories on JVM exit.\n\t */\n\tprivate boolean deleteFilesOnExit = true;\n\n\t/**\n\t * Array of regular expression patterns for environment variables that should be\n\t * passed to launched applications.\n\t */\n\tprivate String[] envVarsToInherit = LocalDeployerUtils.isWindows() ? ENV_VARS_TO_INHERIT_DEFAULTS_WIN\n\t\t: ENV_VARS_TO_INHERIT_DEFAULTS_OTHER;\n\n\t/**\n\t * The command to run java.\n\t */\n\tprivate String javaCmd = JAVA_COMMAND;\n\n\t/**\n\t * Maximum number of seconds to wait for application shutdown. via the\n\t * {@code /shutdown} endpoint. A timeout value of 0 specifies an infinite\n\t * timeout. Default is 30 seconds.\n\t */\n\t@Min(-1)\n\tprivate int shutdownTimeout = 30;\n\n\t/**\n\t * The Java Options to pass to the JVM, e.g -Dtest=foo\n\t */\n\tprivate String javaOpts;\n\n\t/**\n\t * Flag to indicate whether application properties are passed as command line\n\t * args or in a SPRING_APPLICATION_JSON environment variable. Default value is\n\t * {@code true}.\n\t */\n\tprivate boolean useSpringApplicationJson = true;\n\n\tprivate final PortRange portRange = new PortRange();\n\n\n\t/**\n\t * The maximum concurrent tasks allowed for this platform instance.\n\t */\n\t@Min(1)\n\tprivate int maximumConcurrentTasks = 20;\n\n\t/**\n\t * Set remote debugging port for JDK 8 runtimes.\n\t *\n\t * @deprecated Use the {@link #debugAddress} instead!\n\t */\n\tprivate Integer debugPort;\n\n\t/**\n\t * Debugging address for the remote clients to attache to. Addresses have the format \"<name>:<port>\" where <name>\n\t * is the host name and <port> is the socket port number at which it attaches or listens.\n\t * For JDK 8 or earlier, the address consists of the port number alone (the host name is implicit to localhost).\n\t * Example addresses for JDK version 9 or higher: <code>*:20075, 192.168.178.10:20075</code>.\n\t * Example addresses for JDK version 8 or earlier: <code>20075</code>.\n\t */\n\tprivate String debugAddress;\n\n\tpublic enum DebugSuspendType {y, n}\n\n\t/**\n\t * Suspend defines whether the JVM should suspend and wait for a debugger to attach or not\n\t */\n\tprivate DebugSuspendType debugSuspend = DebugSuspendType.y;\n\n\tprivate boolean inheritLogging;\n\n\tprivate final Docker docker = new Docker();\n\n\t/**\n\t * (optional) hostname to use when computing the URL of the deployed application.\n\t * By default the {@link CommandBuilder} implementations decide how to build the hostname.\n\t */\n\tprivate String hostname;\n\n\tprivate Map<String, String> javaHomePath = new HashMap<>();\n\n\tprivate AppAdmin appAdmin = new AppAdmin();\n\n\tpublic LocalDeployerProperties() {\n\t\tString javaHome = System.getProperty(\"java.home\");\n\t\tif (javaHome != null) {\n\t\t\tjavaHomePath.put(\"2\", javaHome);\n\t\t\tjavaHomePath.put(\"3\", javaHome);\n\t\t}\n\t}\n\n\tpublic LocalDeployerProperties(LocalDeployerProperties from) {\n\t\tthis.debugPort = from.getDebugPort();\n\t\tthis.debugAddress = from.getDebugAddress();\n\t\tthis.debugSuspend = from.getDebugSuspend();\n\t\tthis.deleteFilesOnExit = from.isDeleteFilesOnExit();\n\t\tthis.docker.network = from.getDocker().getNetwork();\n\t\tthis.docker.deleteContainerOnExit = from.getDocker().isDeleteContainerOnExit();\n\t\tthis.docker.portRange = from.getDocker().getPortRange();\n\t\tthis.envVarsToInherit = new String[from.getEnvVarsToInherit().length];\n\t\tSystem.arraycopy(from.getEnvVarsToInherit(), 0, this.envVarsToInherit, 0, from.getEnvVarsToInherit().length);\n\t\tthis.inheritLogging = from.isInheritLogging();\n\t\tthis.javaCmd = from.getJavaCmd();\n\t\tthis.javaOpts = from.getJavaOpts();\n\t\tthis.maximumConcurrentTasks = from.getMaximumConcurrentTasks();\n\t\tthis.portRange.high = from.getPortRange().getHigh();\n\t\tthis.portRange.low = from.getPortRange().getLow();\n\t\tthis.shutdownTimeout = from.getShutdownTimeout();\n\t\tthis.useSpringApplicationJson = from.isUseSpringApplicationJson();\n\t\tthis.workingDirectoriesRoot = Paths.get(from.getWorkingDirectoriesRoot().toUri());\n\t\tthis.hostname = from.getHostname();\n\t\tthis.appAdmin = from.getAppAdmin();\n\t\tthis.javaHomePath = from.getJavaHomePath().entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));\n\t\tthis.startupProbe = from.getStartupProbe();\n\t\tthis.healthProbe = from.getHealthProbe();\n\t}\n\n\tpublic static class PortRange {\n\n\t\t/**\n\t\t * Lower bound for computing applications's random port.\n\t\t */\n\t\tprivate int low = 20000;\n\n\t\t/**\n\t\t * Upper bound for computing applications's random port.\n\t\t */\n\t\tprivate int high = 61000;\n\n\t\tpublic int getLow() {\n\t\t\treturn low;\n\t\t}\n\n\t\tpublic void setLow(int low) {\n\t\t\tthis.low = low;\n\t\t}\n\n\t\tpublic int getHigh() {\n\t\t\treturn high;\n\t\t}\n\n\t\tpublic void setHigh(int high) {\n\t\t\tthis.high = high;\n\t\t}\n\n\t\t@Override\n\t\tpublic String toString() {\n\t\t\treturn \"{ low=\" + low + \", high=\" + high + '}';\n\t\t}\n\n\t\t@Override\n\t\tpublic int hashCode() {\n\t\t\tfinal int prime = 31;\n\t\t\tint result = 1;\n\t\t\tresult = prime * result + high;\n\t\t\tresult = prime * result + low;\n\t\t\treturn result;\n\t\t}\n\n\t\t@Override\n\t\tpublic boolean equals(Object obj) {\n\t\t\tif (this == obj) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t\tif (obj == null) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tif (getClass() != obj.getClass()) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tPortRange other = (PortRange) obj;\n\t\t\tif (high != other.high) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\treturn low == other.low;\n\t\t}\n\t}\n\n\tpublic static class Docker {\n\t\t/**\n\t\t * Container network\n\t\t */\n\t\tprivate String network = \"bridge\";\n\n\t\t/**\n\t\t * Whether to delete the container on container exit.\n\t\t */\n\t\tprivate boolean deleteContainerOnExit = true;\n\n\t\t/**\n\t\t * Allow the Docker command builder use its own port range.\n\t\t */\n\t\tprivate PortRange portRange = new PortRange();\n\n\t\t/**\n\t\t * Set port mappings for container\n\t\t */\n\t\tprivate String portMappings;\n\n\t\t/**\n\t\t * Set volume mappings\n\t\t */\n\t\tprivate String volumeMounts;\n\n\t\t/**\n\t\t * Set additional hosts\n\t\t */\n\t\tprivate String additionalHosts;\n\n\t\tpublic PortRange getPortRange() {\n\t\t\treturn portRange;\n\t\t}\n\n\t\tpublic String getNetwork() {\n\t\t\treturn network;\n\t\t}\n\n\t\tpublic void setNetwork(String network) {\n\t\t\tthis.network = network;\n\t\t}\n\n\t\tpublic boolean isDeleteContainerOnExit() {\n\t\t\treturn deleteContainerOnExit;\n\t\t}\n\n\t\tpublic void setDeleteContainerOnExit(boolean deleteContainerOnExit) {\n\t\t\tthis.deleteContainerOnExit = deleteContainerOnExit;\n\t\t}\n\n\t\tpublic String getPortMappings() {\n\t\t\treturn portMappings;\n\t\t}\n\n\t\tpublic void setPortMappings(String portMappings) {\n\t\t\tthis.portMappings = portMappings;\n\t\t}\n\n\t\tpublic String getVolumeMounts() {\n\t\t\treturn volumeMounts;\n\t\t}\n\n\t\tpublic void setVolumeMounts(String volumeMounts) {\n\t\t\tthis.volumeMounts = volumeMounts;\n\t\t}\n\n\t\tpublic String getAdditionalHosts() { return additionalHosts; }\n\n\t\tpublic void setAdditionalHosts(String additionalHosts) { this.additionalHosts = additionalHosts; }\n\n\t\t@Override\n\t\tpublic int hashCode() {\n\t\t\tfinal int prime = 31;\n\t\t\tint result = 1;\n\t\t\tresult = prime * result + ((network == null) ? 0 : network.hashCode());\n\t\t\treturn result;\n\t\t}\n\n\t\t@Override\n\t\tpublic boolean equals(Object obj) {\n\t\t\tif (this == obj) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t\tif (obj == null) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tif (getClass() != obj.getClass()) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tDocker other = (Docker) obj;\n\t\t\tif (network == null) {\n\t\t\t\treturn other.network == null;\n\t\t\t} else return network.equals(other.network);\n\t\t}\n\t}\n\n\tpublic Docker getDocker() {\n\t\treturn docker;\n\t}\n\n\tpublic String getHostname() {\n\t\treturn hostname;\n\t}\n\n\tpublic void setHostname(String hostname) {\n\t\tthis.hostname = hostname;\n\t}\n\n\tpublic Integer getDebugPort() {\n\t\treturn debugPort;\n\t}\n\n\tpublic DebugSuspendType getDebugSuspend() {\n\t\treturn debugSuspend;\n\t}\n\n\tpublic void setDebugSuspend(DebugSuspendType debugSuspend) {\n\t\tthis.debugSuspend = debugSuspend;\n\t}\n\n\tpublic void setDebugPort(Integer debugPort) {\n\t\tlogger.warn(\"The debugPort is deprecated! It supports only pre Java 9 environments. \" +\n\t\t\t\"Please use the debugAddress property instead!\");\n\t\tthis.debugPort = debugPort;\n\t}\n\n\tpublic String getDebugAddress() {\n\t\treturn debugAddress;\n\t}\n\n\tpublic void setDebugAddress(String debugAddress) {\n\t\tthis.debugAddress = debugAddress;\n\t}\n\n\tpublic boolean isInheritLogging() {\n\t\treturn inheritLogging;\n\t}\n\n\tpublic void setInheritLogging(boolean inheritLogging) {\n\t\tthis.inheritLogging = inheritLogging;\n\t}\n\n\tpublic String getJavaCommand(String bootVersion) {\n\t\tif (!StringUtils.hasText(javaCmd) || this.javaCmd.equals(JAVA_COMMAND)) {\n\t\t\treturn deduceJavaCommand(bootVersion);\n\t\t}\n\t\treturn javaCmd;\n\t}\n\n\tpublic Map<String, String> getJavaHomePath() {\n\t\treturn javaHomePath;\n\t}\n\n\tpublic void setJavaHomePath(Map<String, String> javaHomePath) {\n\t\tthis.javaHomePath = javaHomePath;\n\t}\n\n\tpublic String getJavaCmd() {\n\t\treturn javaCmd;\n\t}\n\n\tpublic void setJavaCmd(String javaCmd) {\n\t\tthis.javaCmd = javaCmd;\n\t}\n\n\tpublic Path getWorkingDirectoriesRoot() {\n\t\treturn workingDirectoriesRoot;\n\t}\n\n\tpublic void setWorkingDirectoriesRoot(String workingDirectoriesRoot) {\n\t\tthis.workingDirectoriesRoot = Paths.get(workingDirectoriesRoot);\n\t}\n\n\tpublic void setWorkingDirectoriesRoot(Path workingDirectoriesRoot) {\n\t\tthis.workingDirectoriesRoot = workingDirectoriesRoot;\n\t}\n\n\tpublic boolean isDeleteFilesOnExit() {\n\t\treturn deleteFilesOnExit;\n\t}\n\n\tpublic void setDeleteFilesOnExit(boolean deleteFilesOnExit) {\n\t\tthis.deleteFilesOnExit = deleteFilesOnExit;\n\t}\n\n\tpublic String[] getEnvVarsToInherit() {\n\t\treturn envVarsToInherit;\n\t}\n\n\tpublic void setEnvVarsToInherit(String[] envVarsToInherit) {\n\t\tthis.envVarsToInherit = envVarsToInherit;\n\t}\n\n\tpublic int getShutdownTimeout() {\n\t\treturn shutdownTimeout;\n\t}\n\n\tpublic LocalDeployerProperties setShutdownTimeout(int shutdownTimeout) {\n\t\tthis.shutdownTimeout = shutdownTimeout;\n\t\treturn this;\n\t}\n\n\tpublic String getJavaOpts() {\n\t\treturn javaOpts;\n\t}\n\n\tpublic void setJavaOpts(String javaOpts) {\n\t\tthis.javaOpts = javaOpts;\n\t}\n\n\tpublic boolean isUseSpringApplicationJson() {\n\t\treturn useSpringApplicationJson;\n\t}\n\n\tpublic void setUseSpringApplicationJson(boolean useSpringApplicationJson) {\n\t\tthis.useSpringApplicationJson = useSpringApplicationJson;\n\t}\n\n\tpublic PortRange getPortRange() {\n\t\treturn portRange;\n\t}\n\n\tpublic int getMaximumConcurrentTasks() {\n\t\treturn maximumConcurrentTasks;\n\t}\n\n\tpublic void setMaximumConcurrentTasks(int maximumConcurrentTasks) {\n\t\tthis.maximumConcurrentTasks = maximumConcurrentTasks;\n\t}\n\n\tprivate HttpProbe startupProbe = new HttpProbe();\n\n\tprivate HttpProbe healthProbe = new HttpProbe();\n\n\tpublic HttpProbe getStartupProbe() {\n\t\treturn startupProbe;\n\t}\n\n\tpublic void setStartupProbe(HttpProbe startupProbe) {\n\t\tthis.startupProbe = startupProbe;\n\t}\n\n\tpublic HttpProbe getHealthProbe() {\n\t\treturn healthProbe;\n\t}\n\n\tpublic void setHealthProbe(HttpProbe healthProbe) {\n\t\tthis.healthProbe = healthProbe;\n\t}\n\n\tpublic AppAdmin getAppAdmin() {\n\t\treturn appAdmin;\n\t}\n\n\tpublic void setAppAdmin(AppAdmin appAdmin) {\n\t\tthis.appAdmin = appAdmin;\n\t}\n\n\tpublic static class HttpProbe {\n\n\t\t/**\n\t\t * Path to check as a probe\n\t\t */\n\t\tprivate String path;\n\n\t\tpublic String getPath() {\n\t\t\treturn path;\n\t\t}\n\n\t\tpublic void setPath(String path) {\n\t\t\tthis.path = path;\n\t\t}\n\t}\n\n\tprivate String deduceJavaCommand(String bootVersion) {\n\t\tString javaExecutablePath = JAVA_COMMAND;\n\n\t\tString javaHome = getJavaHome(bootVersion);\n\t\tif (StringUtils.hasText(javaHome)) {\n\t\t\tFile javaExecutable = new File(javaHome, \"bin\" + File.separator + javaExecutablePath);\n\t\t\tAssert.isTrue(javaExecutable.exists(), () ->\n\t\t\t\t\"Java executable'\" + javaExecutable + \"'discovered via 'java.home' system property '\" + this.javaHomePath\n\t\t\t\t\t+ \"' does not exist.\");\n\t\t\tAssert.isTrue(javaExecutable.canExecute(), () ->\n\t\t\t\t\"Java executable'\" + javaExecutable + \"'discovered via 'java.home' system property '\" + this.javaHomePath\n\t\t\t\t\t+ \"' is not executable.\");\n\t\t\tjavaExecutablePath = javaExecutable.getAbsolutePath();\n\t\t} else {\n\t\t\tlogger.warn(\"System property 'java.home' and 'spring.cloud.deployer.local.{}.javaHomePath' is not set. \" +\n\t\t\t\t\"Defaulting to the java executable path as \" + JAVA_COMMAND + \" assuming it's in PATH.\", bootVersion);\n\t\t}\n\t\treturn javaExecutablePath;\n\t}\n\n\tprivate String getJavaHome(String bootVersion) {\n\t\tString path = javaHomePath.get(bootVersion);\n\t\treturn path == null ? System.getProperty(\"java.home\") : path;\n\t}\n\n\t@Override\n\tpublic String toString() {\n\t\treturn new ToStringCreator(this).append(\"workingDirectoriesRoot\", this.workingDirectoriesRoot)\n\t\t\t.append(\"javaOpts\", this.javaOpts).append(\"envVarsToInherit\", this.envVarsToInherit).toString();\n\t}\n\n\t@Override\n\tpublic boolean equals(Object o) {\n\t\tif (this == o) return true;\n\t\tif (o == null || getClass() != o.getClass()) return false;\n\n\t\tLocalDeployerProperties that = (LocalDeployerProperties) o;\n\n\t\tif (deleteFilesOnExit != that.deleteFilesOnExit) return false;\n\t\tif (shutdownTimeout != that.shutdownTimeout) return false;\n\t\tif (useSpringApplicationJson != that.useSpringApplicationJson) return false;\n\t\tif (maximumConcurrentTasks != that.maximumConcurrentTasks) return false;\n\t\tif (inheritLogging != that.inheritLogging) return false;\n\t\tif (!Objects.equals(workingDirectoriesRoot, that.workingDirectoriesRoot)) return false;\n\t\t// Probably incorrect - comparing Object[] arrays with Arrays.equals\n\t\tif (!Arrays.equals(envVarsToInherit, that.envVarsToInherit)) return false;\n\t\tif (!Objects.equals(javaCmd, that.javaCmd)) return false;\n\t\tif (!Objects.equals(javaOpts, that.javaOpts)) return false;\n\t\tif (!portRange.equals(that.portRange)) return false;\n\t\tif (!Objects.equals(debugPort, that.debugPort)) return false;\n\t\tif (!Objects.equals(debugAddress, that.debugAddress)) return false;\n\t\tif (debugSuspend != that.debugSuspend) return false;\n\t\tif (!docker.equals(that.docker)) return false;\n\t\tif (!Objects.equals(hostname, that.hostname)) return false;\n\t\tif (!Objects.equals(javaHomePath, that.javaHomePath)) return false;\n\t\tif (!Objects.equals(appAdmin, that.appAdmin)) return false;\n\t\tif (!Objects.equals(startupProbe, that.startupProbe)) return false;\n\t\treturn Objects.equals(healthProbe, that.healthProbe);\n\t}\n\n\t@Override\n\tpublic int hashCode() {\n\t\tint result = workingDirectoriesRoot != null ? workingDirectoriesRoot.hashCode() : 0;\n\t\tresult = 31 * result + (deleteFilesOnExit ? 1 : 0);\n\t\tresult = 31 * result + Arrays.hashCode(envVarsToInherit);\n\t\tresult = 31 * result + (javaCmd != null ? javaCmd.hashCode() : 0);\n\t\tresult = 31 * result + shutdownTimeout;\n\t\tresult = 31 * result + (javaOpts != null ? javaOpts.hashCode() : 0);\n\t\tresult = 31 * result + (useSpringApplicationJson ? 1 : 0);\n\t\tresult = 31 * result + portRange.hashCode();\n\t\tresult = 31 * result + maximumConcurrentTasks;\n\t\tresult = 31 * result + (debugPort != null ? debugPort.hashCode() : 0);\n\t\tresult = 31 * result + (debugAddress != null ? debugAddress.hashCode() : 0);\n\t\tresult = 31 * result + (debugSuspend != null ? debugSuspend.hashCode() : 0);\n\t\tresult = 31 * result + (inheritLogging ? 1 : 0);\n\t\tresult = 31 * result + docker.hashCode();\n\t\tresult = 31 * result + (hostname != null ? hostname.hashCode() : 0);\n\t\tresult = 31 * result + (javaHomePath != null ? javaHomePath.hashCode() : 0);\n\t\tresult = 31 * result + (appAdmin != null ? appAdmin.hashCode() : 0);\n\t\tresult = 31 * result + (startupProbe != null ? startupProbe.hashCode() : 0);\n\t\tresult = 31 * result + (healthProbe != null ? healthProbe.hashCode() : 0);\n\t\treturn result;\n\t}\n}\n"
  },
  {
    "path": "spring-cloud-deployer-local/src/main/java/org/springframework/cloud/deployer/spi/local/LocalDeployerUtils.java",
    "content": "/*\n * Copyright 2017-2018 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.local;\n\nimport java.util.Locale;\n\n/**\n * Deployer utility functions.\n *\n * @author Janne Valkealahti\n * @author Michael Minella\n *\n */\npublic class LocalDeployerUtils {\n\n\t/**\n\t * Checks if jvm is running on windows.\n\t *\n\t * @return true if windows detected\n\t */\n\tprotected static boolean isWindows() {\n\t\tString osName = System.getProperty(\"os.name\");\n\n\t\treturn osName != null && osName.toLowerCase(Locale.ROOT).startsWith(\"windows\");\n\t}\n}\n"
  },
  {
    "path": "spring-cloud-deployer-local/src/main/java/org/springframework/cloud/deployer/spi/local/LocalTaskLauncher.java",
    "content": "/*\n * Copyright 2016-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.local;\n\nimport java.io.File;\nimport java.io.FileInputStream;\nimport java.io.IOException;\nimport java.io.InputStreamReader;\nimport java.net.HttpURLConnection;\nimport java.net.Inet4Address;\nimport java.net.URL;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.UUID;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.concurrent.CopyOnWriteArrayList;\n\nimport jakarta.annotation.PreDestroy;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport org.springframework.cloud.deployer.spi.core.AppDeploymentRequest;\nimport org.springframework.cloud.deployer.spi.core.RuntimeEnvironmentInfo;\nimport org.springframework.cloud.deployer.spi.task.LaunchState;\nimport org.springframework.cloud.deployer.spi.task.TaskLauncher;\nimport org.springframework.cloud.deployer.spi.task.TaskStatus;\nimport org.springframework.util.FileCopyUtils;\nimport org.springframework.util.StringUtils;\n\n/**\n * A {@link TaskLauncher} implementation that spins off a new JVM process per task launch.\n *\n * @author Eric Bottard\n * @author Marius Bogoevici\n * @author Mark Fisher\n * @author Janne Valkealahti\n * @author Thomas Risberg\n * @author Oleg Zhurakousky\n * @author Michael Minella\n * @author Christian Tzolov\n * @author David Turanski\n * @author Glenn Renfro\n * @author Ben Blinebury\n */\npublic class LocalTaskLauncher extends AbstractLocalDeployerSupport implements TaskLauncher {\n\n\tprivate static final Logger logger = LoggerFactory.getLogger(LocalTaskLauncher.class);\n\n\tprivate static final String JMX_DEFAULT_DOMAIN_KEY = \"spring.jmx.default-domain\";\n\n\tprivate final Map<String, TaskInstance> running = new ConcurrentHashMap<>();\n\n\tprivate final Map<String, CopyOnWriteArrayList<String>> taskInstanceHistory = new ConcurrentHashMap<>();\n\n\t/**\n\t * Instantiates a new local task launcher.\n\t *\n\t * @param properties the properties\n\t */\n\tpublic LocalTaskLauncher(LocalDeployerProperties properties) {\n\t\tsuper(properties);\n\t}\n\n\t@Override\n\tpublic String launch(AppDeploymentRequest request) {\n\n\t\tif (this.maxConcurrentExecutionsReached()) {\n\t\t\tthrow new IllegalStateException(\n\t\t\t\tString.format(\"Cannot launch task %s. The maximum concurrent task executions is at its limit [%d].\",\n\t\t\t\t\trequest.getDefinition().getName(), this.getMaximumConcurrentTasks())\n\t\t\t);\n\t\t}\n\n\t\tString taskLaunchId = request.getDefinition().getName() + \"-\" + UUID.randomUUID().toString();\n\n\t\tpruneTaskInstanceHistory(request.getDefinition().getName(), taskLaunchId);\n\n\t\tHashMap<String, String> args = new HashMap<>();\n\t\targs.putAll(request.getDefinition().getProperties());\n\t\targs.put(JMX_DEFAULT_DOMAIN_KEY, taskLaunchId);\n\t\targs.put(\"endpoints.shutdown.enabled\", \"true\");\n\t\targs.put(\"endpoints.jmx.unique-names\", \"true\");\n\n\t\ttry {\n\t\t\tPath workingDirectory = createWorkingDirectory(request.getDeploymentProperties(), taskLaunchId);\n\n\t\t\tboolean useDynamicPort = isDynamicPort(request);\n\n\t\t\tint port = calcServerPort(request, useDynamicPort, args);\n\n\t\t\tProcessBuilder builder = buildProcessBuilder(request, args, Optional.empty(), taskLaunchId).inheritIO();\n\n\t\t\tTaskInstance instance = new TaskInstance(builder, workingDirectory, port);\n\t\t\tif (this.shouldInheritLogging(request)) {\n\t\t\t\tinstance.start(builder);\n\t\t\t\tlogger.info(\"launching task {}\\n    Logs will be inherited.\", taskLaunchId);\n\n\t\t\t}\n\t\t\telse {\n\t\t\t\tinstance.start(builder, getLocalDeployerProperties().isDeleteFilesOnExit());\n\t\t\t\tlogger.info(\"launching task {}\\n   Logs will be in {}\", taskLaunchId, workingDirectory);\n\t\t\t}\n\t\t\trunning.put(taskLaunchId, instance);\n\t\t}\n\t\tcatch (IOException e) {\n\t\t\tthrow new RuntimeException(\"Exception trying to launch \" + request, e);\n\t\t}\n\n\t\treturn taskLaunchId;\n\t}\n\n\tprivate void pruneTaskInstanceHistory(String taskDefinitionName, String taskLaunchId) {\n\t\tCopyOnWriteArrayList<String> oldTaskInstanceIds = taskInstanceHistory.get(taskDefinitionName);\n\t\tif (oldTaskInstanceIds == null) {\n\t\t\toldTaskInstanceIds = new CopyOnWriteArrayList<>();\n\t\t\ttaskInstanceHistory.put(taskDefinitionName, oldTaskInstanceIds);\n\t\t}\n\n\t\tfor (String oldTaskInstanceId : oldTaskInstanceIds) {\n\t\t\tTaskInstance oldTaskInstance = running.get(oldTaskInstanceId);\n\t\t\tif (oldTaskInstance != null && oldTaskInstance.getState() != LaunchState.running\n\t\t\t\t\t&& oldTaskInstance.getState() != LaunchState.launching) {\n\t\t\t\trunning.remove(oldTaskInstanceId);\n\t\t\t\toldTaskInstanceIds.remove(oldTaskInstanceId);\n\t\t\t} else {\n\t\t\t\toldTaskInstanceIds.remove(oldTaskInstanceId);\n\t\t\t}\n\t\t}\n\t\toldTaskInstanceIds.add(taskLaunchId);\n\t}\n\n\tprivate boolean isDynamicPort(AppDeploymentRequest request) {\n\t\tboolean isServerPortKeyonArgs = isServerPortKeyPresentOnArgs(request) != null;\n\t\treturn !request.getDefinition().getProperties().containsKey(SERVER_PORT_KEY)\n\t\t\t\t&& !isServerPortKeyonArgs;\n\t}\n\n\t@Override\n\tpublic void cancel(String id) {\n\t\tTaskInstance instance = running.get(id);\n\t\tif (instance != null) {\n\t\t\tinstance.cancelled = true;\n\t\t\tif (isAlive(instance.getProcess())) {\n\t\t\t\tshutdownAndWait(instance);\n\t\t\t}\n\t\t}\n\t}\n\n\t@Override\n\tpublic TaskStatus status(String id) {\n\t\tTaskInstance instance = running.get(id);\n\t\tif (instance != null) {\n\t\t\treturn new TaskStatus(id, instance.getState(), instance.getAttributes());\n\t\t}\n\t\treturn new TaskStatus(id, LaunchState.unknown, null);\n\t}\n\n\t@Override\n\tpublic String getLog(String id) {\n\t\tTaskInstance instance = running.get(id);\n\t\tif (instance != null) {\n\t\t\tStringBuilder stringBuilder = new StringBuilder();\n\t\t\tString stderr = instance.getStdErr();\n\t\t\tif (StringUtils.hasText(stderr)) {\n\t\t\t\tstringBuilder.append(\"stderr:\\n\");\n\t\t\t\tstringBuilder.append(stderr);\n\t\t\t}\n\t\t\tString stdout = instance.getStdOut();\n\t\t\tif (StringUtils.hasText(stdout)) {\n\t\t\t\tstringBuilder.append(\"stdout:\\n\");\n\t\t\t\tstringBuilder.append(stdout);\n\t\t\t}\n\t\t\treturn stringBuilder.toString();\n\t\t}\n\t\telse {\n\t\t\treturn \"Log could not be retrieved as the task instance is not running.\";\n\t\t}\n\t}\n\n\t@Override\n\tpublic void cleanup(String id) {\n\t}\n\n\t@Override\n\tpublic void destroy(String appName) {\n\t}\n\n\t@Override\n\tpublic RuntimeEnvironmentInfo environmentInfo() {\n\t\treturn super.createRuntimeEnvironmentInfo(TaskLauncher.class, this.getClass());\n\t}\n\n\t@Override\n\tpublic int getMaximumConcurrentTasks() {\n\t\treturn getLocalDeployerProperties().getMaximumConcurrentTasks();\n\t}\n\n\n\t@Override\n\tpublic int getRunningTaskExecutionCount() {\n\t\tint runningExecutionCount = 0;\n\n\t\tfor (TaskInstance taskInstance: running.values()) {\n\t\t\tif (taskInstance.getProcess().isAlive()) {\n\t\t\t\trunningExecutionCount++;\n\t\t\t}\n\t\t}\n\t\treturn runningExecutionCount;\n\t}\n\n\tprivate synchronized boolean maxConcurrentExecutionsReached() {\n\t\treturn getRunningTaskExecutionCount() >= getMaximumConcurrentTasks();\n\t}\n\n\t@PreDestroy\n\tpublic void shutdown() throws Exception {\n\t\tfor (String taskLaunchId : running.keySet()) {\n\t\t\tcancel(taskLaunchId);\n\t\t}\n\t\ttaskInstanceHistory.clear();\n\t}\n\n\tprivate Path createWorkingDirectory(Map<String, String> deploymentProperties, String taskLaunchId) throws IOException {\n\t\tLocalDeployerProperties localDeployerPropertiesToUse = bindDeploymentProperties(deploymentProperties);\n\n\t\tPath workingDirectoryRoot = Files.isSymbolicLink(localDeployerPropertiesToUse.getWorkingDirectoriesRoot())\n\t\t\t\t? Files.readSymbolicLink(localDeployerPropertiesToUse.getWorkingDirectoriesRoot())\n\t\t\t\t: Files.createDirectories(localDeployerPropertiesToUse.getWorkingDirectoriesRoot());\n\n\t\tPath workingDirectory = Files.createDirectories(workingDirectoryRoot.resolve(Long.toString(System.nanoTime())).resolve(taskLaunchId));\n\n\t\tif (localDeployerPropertiesToUse.isDeleteFilesOnExit()) {\n\t\t\tworkingDirectory.toFile().deleteOnExit();\n\t\t}\n\n\t\treturn workingDirectory;\n\t}\n\n\tprivate static class TaskInstance implements Instance {\n\n\t\tprivate Process process;\n\n\t\tprivate final Path workDir;\n\n\t\tprivate File stdout;\n\n\t\tprivate File stderr;\n\n\t\tprivate final URL baseUrl;\n\n\t\tprivate boolean cancelled;\n\n\t\tprivate TaskInstance(ProcessBuilder builder, Path workDir, int port) throws IOException {\n\t\t\tbuilder.directory(workDir.toFile());\n\t\t\tthis.workDir = workDir;\n\t\t\tthis.baseUrl = new URL(\"http\", Inet4Address.getLocalHost().getHostAddress(), port, \"\");\n\t\t\tif (logger.isDebugEnabled()) {\n\t\t\t\tlogger.debug(\"Local Task Launcher Commands: \" + String.join(\",\", builder.command())\n\t\t\t\t\t\t+ \", Environment: \" + builder.environment());\n\t\t\t}\n\t\t}\n\n\t\t@Override\n\t\tpublic URL getBaseUrl() {\n\t\t\treturn this.baseUrl;\n\t\t}\n\n\t\t@Override\n\t\tpublic Process getProcess() {\n\t\t\treturn this.process;\n\t\t}\n\n\t\tpublic LaunchState getState() {\n\t\t\tif (cancelled) {\n\t\t\t\treturn LaunchState.cancelled;\n\t\t\t}\n\t\t\tInteger exit = getProcessExitValue(process);\n\t\t\t// TODO: consider using exit code mapper concept from batch\n\t\t\tif (exit != null) {\n\t\t\t\tif (exit == 0) {\n\t\t\t\t\treturn LaunchState.complete;\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\treturn LaunchState.failed;\n\t\t\t\t}\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tHttpURLConnection urlConnection = (HttpURLConnection) baseUrl.openConnection();\n\t\t\t\turlConnection.setConnectTimeout(100);\n\t\t\t\turlConnection.connect();\n\t\t\t\turlConnection.disconnect();\n\t\t\t\treturn LaunchState.running;\n\t\t\t}\n\t\t\tcatch (IOException e) {\n\t\t\t\treturn LaunchState.launching;\n\t\t\t}\n\t\t}\n\n\t\tpublic String getStdOut() {\n\t\t\ttry {\n\t\t\t\treturn FileCopyUtils.copyToString(new InputStreamReader(new FileInputStream(this.stdout)));\n\t\t\t}\n\t\t\tcatch (IOException e) {\n\t\t\t\treturn \"Log retrieval returned \" + e.getMessage();\n\t\t\t}\n\t\t}\n\n\t\tpublic String getStdErr() {\n\t\t\ttry {\n\t\t\t\treturn FileCopyUtils.copyToString(new InputStreamReader(new FileInputStream(this.stderr)));\n\t\t\t}\n\t\t\tcatch (IOException e) {\n\t\t\t\treturn \"Log retrieval returned \" + e.getMessage();\n\t\t\t}\n\t\t}\n\n\t\t/**\n\t\t * Will start the process while redirecting 'out' and 'err' streams to the 'out' and 'err'\n\t\t * streams of this process.\n\t\t */\n\t\tprivate void start(ProcessBuilder builder) throws IOException {\n\t\t\tif (logger.isDebugEnabled()) {\n\t\t\t\tlogger.debug(\"Local Task Launcher Commands: \" + String.join(\",\", builder.command())\n\t\t\t\t\t\t+ \", Environment: \" + builder.environment());\n\t\t\t}\n\t\t\tthis.process = builder.start();\n\t\t}\n\n\t\tprivate void start(ProcessBuilder builder, boolean deleteOnExit) throws IOException {\n\t\t\tString workDirPath = workDir.toFile().getAbsolutePath();\n\t\t\tthis.stdout = Files.createFile(Paths.get(workDirPath, \"stdout.log\")).toFile();\n\t\t\tthis.stderr = Files.createFile(Paths.get(workDirPath, \"stderr.log\")).toFile();\n\t\t\tbuilder.redirectOutput(this.stdout);\n\t\t\tbuilder.redirectError(this.stderr);\n\t\t\tthis.process = builder.start();\n\t\t\tif(deleteOnExit) {\n\t\t\t\tthis.stdout.deleteOnExit();\n\t\t\t\tthis.stderr.deleteOnExit();\n\t\t\t}\n\t\t}\n\n\t\tprivate Map<String, String> getAttributes() {\n\t\t\tHashMap<String, String> result = new HashMap<>();\n\t\t\tresult.put(\"working.dir\", workDir.toFile().getAbsolutePath());\n\t\t\tif(this.stdout != null) {\n\t\t\t\tresult.put(\"stdout\", stdout.getAbsolutePath());\n\t\t\t}\n\t\t\tif(this.stderr != null) {\n\t\t\t\tresult.put(\"stderr\", stderr.getAbsolutePath());\n\t\t\t}\n\t\t\tresult.put(\"url\", baseUrl.toString());\n\t\t\treturn result;\n\t\t}\n\t}\n\n\t/**\n\t * Returns the process exit value. We explicitly use Integer instead of int\n\t * to indicate that if {@code NULL} is returned, the process is still running.\n\t *\n\t * @param process the process\n\t * @return the process exit value or {@code NULL} if process is still alive\n\t */\n\tprivate static Integer getProcessExitValue(Process process) {\n\t\ttry {\n\t\t\treturn process.exitValue();\n\t\t}\n\t\tcatch (IllegalThreadStateException e) {\n\t\t\t// process is still alive\n\t\t\treturn null;\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "spring-cloud-deployer-local/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports",
    "content": "org.springframework.cloud.deployer.spi.local.LocalDeployerAutoConfiguration\n"
  },
  {
    "path": "spring-cloud-deployer-local/src/test/java/org/springframework/cloud/deployer/spi/local/DebugAddressTests.java",
    "content": "/*\n * Copyright 2020-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.local;\n\nimport java.util.Optional;\n\nimport org.junit.jupiter.api.Test;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * @author Christian Tzolov\n */\npublic class DebugAddressTests {\n\n\t@Test\n\tpublic void testDebugEmptyConfiguration() {\n\t\tOptional<DebugAddress> debugAddress =\n\t\t\t\tDebugAddress.from(new LocalDeployerProperties(), 0);\n\t\tassertThat(debugAddress.isPresent()).isFalse();\n\t}\n\n\t@Test\n\tpublic void testDebugPort() {\n\t\tLocalDeployerProperties properties = new LocalDeployerProperties();\n\t\tproperties.setDebugPort(20075);\n\t\tOptional<DebugAddress> debugAddress =\n\t\t\t\tDebugAddress.from(properties, 0);\n\t\tassertThat(debugAddress.isPresent()).isTrue();\n\t\tassertThat(debugAddress.get().getHost()).isNull();\n\t\tassertThat(debugAddress.get().getPort()).isEqualTo(\"20075\");\n\t\tassertThat(debugAddress.get().getAddress()).isEqualTo(\"20075\");\n\t}\n\n\t@Test\n\tpublic void testDebugPortWithInstance() {\n\t\tLocalDeployerProperties properties = new LocalDeployerProperties();\n\t\tproperties.setDebugPort(20075);\n\t\tOptional<DebugAddress> debugAddress =\n\t\t\t\tDebugAddress.from(properties, 100);\n\t\tassertThat(debugAddress.isPresent()).isTrue();\n\t\tassertThat(debugAddress.get().getHost()).isNull();\n\t\tassertThat(debugAddress.get().getPort()).isEqualTo(\"20175\");\n\t\tassertThat(debugAddress.get().getAddress()).isEqualTo(\"20175\");\n\t}\n\n\t@Test\n\tpublic void testDebugPortInvalidValue() {\n\t\tLocalDeployerProperties properties = new LocalDeployerProperties();\n\t\tproperties.setDebugPort(-666);\n\t\tOptional<DebugAddress> debugAddress =\n\t\t\t\tDebugAddress.from(properties, 0);\n\t\tassertThat(debugAddress.isPresent()).isFalse();\n\t}\n\n\t@Test\n\tpublic void testDebugAddressPortOnly() {\n\t\tLocalDeployerProperties properties = new LocalDeployerProperties();\n\t\tproperties.setDebugAddress(\"20075\");\n\t\tOptional<DebugAddress> debugAddress =\n\t\t\t\tDebugAddress.from(properties, 100);\n\t\tassertThat(debugAddress.isPresent()).isTrue();\n\t\tassertThat(debugAddress.get().getHost()).isNull();\n\t\tassertThat(debugAddress.get().getPort()).isEqualTo(\"20175\");\n\t\tassertThat(debugAddress.get().getAddress()).isEqualTo(\"20175\");\n\t}\n\n\t@Test\n\tpublic void testDebugAddressWildcardHost() {\n\t\tLocalDeployerProperties properties = new LocalDeployerProperties();\n\t\tproperties.setDebugAddress(\"*:20075\");\n\t\tOptional<DebugAddress> debugAddress =\n\t\t\t\tDebugAddress.from(properties, 100);\n\t\tassertThat(debugAddress.isPresent()).isTrue();\n\t\tassertThat(debugAddress.get().getHost()).isEqualTo(\"*\");\n\t\tassertThat(debugAddress.get().getPort()).isEqualTo(\"20175\");\n\t\tassertThat(debugAddress.get().getAddress()).isEqualTo(\"*:20175\");\n\t}\n\n\n\t@Test\n\tpublic void testDebugAddressWithIP() {\n\t\tLocalDeployerProperties properties = new LocalDeployerProperties();\n\t\tproperties.setDebugAddress(\"127.0.0.1:20075\");\n\t\tOptional<DebugAddress> debugAddress =\n\t\t\t\tDebugAddress.from(properties, 100);\n\t\tassertThat(debugAddress.isPresent()).isTrue();\n\t\tassertThat(debugAddress.get().getHost()).isEqualTo(\"127.0.0.1\");\n\t\tassertThat(debugAddress.get().getPort()).isEqualTo(\"20175\");\n\t\tassertThat(debugAddress.get().getAddress()).isEqualTo(\"127.0.0.1:20175\");\n\t}\n\n\t@Test\n\tpublic void testDebugAddressWithHostname() {\n\t\tLocalDeployerProperties properties = new LocalDeployerProperties();\n\t\tproperties.setDebugAddress(\"localhost:20075\");\n\t\tOptional<DebugAddress> debugAddress =\n\t\t\t\tDebugAddress.from(properties, 100);\n\t\tassertThat(debugAddress.isPresent()).isTrue();\n\t\tassertThat(debugAddress.get().getHost()).isEqualTo(\"localhost\");\n\t\tassertThat(debugAddress.get().getPort()).isEqualTo(\"20175\");\n\t\tassertThat(debugAddress.get().getAddress()).isEqualTo(\"localhost:20175\");\n\t}\n\n\t@Test\n\tpublic void testDebugAddressWithInvalidIP() {\n\t\tLocalDeployerProperties properties = new LocalDeployerProperties();\n\t\tproperties.setDebugAddress(\"127.0.:20075\");\n\t\tOptional<DebugAddress> debugAddress =\n\t\t\t\tDebugAddress.from(properties, 100);\n\t\tassertThat(debugAddress.isPresent()).isFalse();\n\t}\n\n}\n"
  },
  {
    "path": "spring-cloud-deployer-local/src/test/java/org/springframework/cloud/deployer/spi/local/DeployerSocketUtilsTests.java",
    "content": "/*\n * Copyright 2016-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.local;\n\nimport java.util.SortedSet;\n\nimport org.junit.jupiter.api.Test;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;\n\n/**\n * Unit tests for {@link DeployerSocketUtils}.\n *\n * @author Sam Brannen\n * @author Gary Russell\n * @author Glenn Renfro\n */\nclass DeployerSocketUtilsTests {\n\n\t@Test\n\tvoid findAvailableTcpPortWithZeroMinPort() {\n\t\tassertThatIllegalArgumentException().isThrownBy(() -> DeployerSocketUtils.findAvailableTcpPort(0));\n\t}\n\n\t@Test\n\tvoid findAvailableTcpPortWithNegativeMinPort() {\n\t\tassertThatIllegalArgumentException().isThrownBy(() -> DeployerSocketUtils.findAvailableTcpPort(-500));\n\t}\n\n\t@Test\n\tvoid findAvailableTcpPortWithMin() {\n\t\tint port = DeployerSocketUtils.findAvailableTcpPort(50000);\n\t\tassertPortInRange(port, 50000, DeployerSocketUtils.PORT_RANGE_MAX);\n\t}\n\n\t@Test\n\tvoid find4AvailableTcpPortsInRange() {\n\t\tfindAvailableTcpPorts(4, 30000, 35000);\n\t}\n\n\t@Test\n\tvoid find50AvailableTcpPortsInRange() {\n\t\tfindAvailableTcpPorts(50, 40000, 45000);\n\t}\n\n\t@Test\n\tvoid findAvailableTcpPortsWithRequestedNumberGreaterThanSizeOfRange() {\n\t\tassertThatIllegalArgumentException().isThrownBy(() -> findAvailableTcpPorts(50, 45000, 45010));\n\t}\n\n\t// Helpers\n\n\tprivate void findAvailableTcpPorts(int numRequested, int minPort, int maxPort) {\n\t\tSortedSet<Integer> ports = DeployerSocketUtils.findAvailableTcpPorts(numRequested, minPort, maxPort);\n\t\tassertAvailablePorts(ports, numRequested, minPort, maxPort);\n\t}\n\n\tprivate void assertPortInRange(int port, int minPort, int maxPort) {\n\t\tassertThat(port >= minPort).as(\"port [\" + port + \"] >= \" + minPort).isTrue();\n\t\tassertThat(port <= maxPort).as(\"port [\" + port + \"] <= \" + maxPort).isTrue();\n\t}\n\n\tprivate void assertAvailablePorts(SortedSet<Integer> ports, int numRequested, int minPort, int maxPort) {\n\t\tassertThat(ports.size()).as(\"number of ports requested\").isEqualTo(numRequested);\n\t\tfor (int port : ports) {\n\t\t\tassertPortInRange(port, minPort, maxPort);\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "spring-cloud-deployer-local/src/test/java/org/springframework/cloud/deployer/spi/local/DockerCommandBuilderTests.java",
    "content": "/*\n * Copyright 2017-2020 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.local;\n\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.Optional;\n\nimport org.junit.jupiter.api.Test;\n\nimport org.springframework.cloud.deployer.resource.docker.DockerResource;\nimport org.springframework.cloud.deployer.spi.core.AppDefinition;\nimport org.springframework.cloud.deployer.spi.core.AppDeploymentRequest;\nimport org.springframework.core.io.Resource;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * Unit tests for {@link DockerCommandBuilder}.\n *\n * @author Eric Bottard\n * @author Christian Tzolov\n */\npublic class DockerCommandBuilderTests {\n\n\t@Test\n\tpublic void testContainerName() {\n\t\tAppDefinition appDefinition = new AppDefinition(\"foo\", null);\n\t\tResource resource = new DockerResource(\"foo/bar\");\n\t\tMap<String, String> deploymentProperties = Collections.singletonMap(DockerCommandBuilder.DOCKER_CONTAINER_NAME_KEY, \"gogo\");\n\t\tAppDeploymentRequest request = new AppDeploymentRequest(appDefinition, resource, deploymentProperties);\n\n\t\tProcessBuilder builder = new DockerCommandBuilder(null)\n\t\t\t\t.buildExecutionCommand(request, new HashMap<>(), \"deployerId\", Optional.of(1),\n\t\t\t\t\t\tnew LocalDeployerProperties(), Optional.empty());\n\t\tassertThat(builder.command()).containsAnyElementsOf(Arrays.asList(\"docker\", \"run\", \"--rm\", \"--name=gogo-1\",\n\t\t\t\t\"foo/bar\", \"deployerId=deployerId\"));\n\t}\n\n\t@Test\n\tpublic void testContainerNameWithDockerNetwork() {\n\t\tAppDefinition appDefinition = new AppDefinition(\"foo\", null);\n\t\tResource resource = new DockerResource(\"foo/bar\");\n\t\tMap<String, String> deploymentProperties = Collections.singletonMap(DockerCommandBuilder.DOCKER_CONTAINER_NAME_KEY, \"gogo\");\n\t\tAppDeploymentRequest request = new AppDeploymentRequest(appDefinition, resource, deploymentProperties);\n\t\tProcessBuilder builder = new DockerCommandBuilder(\"scdf_default\")\n\t\t\t\t.buildExecutionCommand(request, new HashMap<>(), \"deployerId\", Optional.of(1),\n\t\t\t\t\t\tnew LocalDeployerProperties(), Optional.empty());\n\t\tassertThat(builder.command()).containsAnyElementsOf(Arrays.asList(\"docker\", \"run\", \"--network\", \"scdf_default\",\n\t\t\t\t\"--rm\", \"--name=gogo-1\", \"foo/bar\"));\n\t}\n\n\t@Test\n\tpublic void testContainerNameWithDockerNetworkAndKeepContainers() {\n\t\tAppDefinition appDefinition = new AppDefinition(\"foo\", null);\n\t\tResource resource = new DockerResource(\"foo/bar\");\n\t\tMap<String, String> deploymentProperties = Collections.singletonMap(DockerCommandBuilder.DOCKER_CONTAINER_NAME_KEY, \"gogo\");\n\t\tAppDeploymentRequest request = new AppDeploymentRequest(appDefinition, resource, deploymentProperties);\n\n\t\tLocalDeployerProperties localDeployerProperties = new LocalDeployerProperties();\n\t\tlocalDeployerProperties.getDocker().setDeleteContainerOnExit(false);\n\n\t\tProcessBuilder builder = new DockerCommandBuilder(\"scdf_default\")\n\t\t\t\t.buildExecutionCommand(request, new HashMap<>(), \"deployerId\", Optional.of(1),\n\t\t\t\t\t\tlocalDeployerProperties, Optional.empty());\n\t\tassertThat(builder.command()).containsAnyElementsOf(Arrays.asList(\"docker\", \"run\", \"--network\", \"scdf_default\",\n\t\t\t\t\"--name=gogo-1\", \"foo/bar\"));\n\t\tassertThat(builder.command()).doesNotContain(\"--rm\");\n\t}\n\n\t@Test\n\tpublic void testUseLocalDeployerPropertiesToKeepStoppedContainer() {\n\t\tAppDefinition appDefinition = new AppDefinition(\"foo\", null);\n\t\tResource resource = new DockerResource(\"foo/bar\");\n\t\tMap<String, String> deploymentProperties = Collections.singletonMap(DockerCommandBuilder.DOCKER_CONTAINER_NAME_KEY, \"gogo\");\n\t\tAppDeploymentRequest request = new AppDeploymentRequest(appDefinition, resource, deploymentProperties);\n\n\t\tLocalDeployerProperties localDeployerProperties = new LocalDeployerProperties();\n\t\tlocalDeployerProperties.getDocker().setDeleteContainerOnExit(false);\n\n\t\tProcessBuilder builder = new DockerCommandBuilder(\"scdf_default\")\n\t\t\t\t.buildExecutionCommand(request, new HashMap<>(), \"deployerId\", Optional.of(1),\n\t\t\t\t\t\tlocalDeployerProperties, Optional.empty());\n\t\tassertThat(builder.command()).containsAnyElementsOf(Arrays.asList(\"docker\", \"run\", \"--network\", \"scdf_default\",\n\t\t\t\t\"--name=gogo-1\", \"foo/bar\"));\n\t\tassertThat(builder.command()).doesNotContain(\"--rm\");\n\t}\n\n\t@Test\n\tpublic void testSpringApplicationJSON() {\n\t\tLocalDeployerProperties properties = new LocalDeployerProperties();\n\t\tLocalAppDeployer deployer = new LocalAppDeployer(properties);\n\t\tAppDefinition definition = new AppDefinition(\"foo\", Collections.singletonMap(\"foo\", \"bar\"));\n\t\tResource resource = new DockerResource(\"foo/bar\");\n\t\tMap<String, String> deploymentProperties = new HashMap<>();\n\t\tdeploymentProperties.put(LocalDeployerProperties.DEBUG_ADDRESS, \"*:9999\");\n\t\tdeploymentProperties.put(LocalDeployerProperties.DEBUG_SUSPEND, \"y\");\n\t\tdeploymentProperties.put(LocalDeployerProperties.INHERIT_LOGGING, \"true\");\n\t\tAppDeploymentRequest request = new AppDeploymentRequest(definition, resource, deploymentProperties);\n\t\tProcessBuilder builder = deployer.buildProcessBuilder(request, request.getDefinition().getProperties(), Optional.of(1), \"foo\");\n\n\t\tString SAJ = LocalDeployerUtils.isWindows() ? \"SPRING_APPLICATION_JSON={\\\\\\\"foo\\\\\\\":\\\\\\\"bar\\\\\\\"}\" : \"SPRING_APPLICATION_JSON={\\\"foo\\\":\\\"bar\\\"}\";\n\t\tassertThat(builder.command()).contains(\"-e\", SAJ);\n\t}\n\n\t@Test\n\tpublic void  testContainerPortMappings(){\n\t\tAppDefinition appDefinition = new AppDefinition(\"foo\", null);\n\t\tResource resource = new DockerResource(\"foo/bar\");\n\t\tMap<String, String> deploymentProperties = Collections.emptyMap();\n\t\tAppDeploymentRequest request = new AppDeploymentRequest(appDefinition, resource, deploymentProperties);\n\n\t\tString goodMapping1 = \"9090:9090\";\n\t\tString goodMapping2 = \"6090:7090\";\n\t\tString incompleteMapping = \"8888\";\n\t\tLocalDeployerProperties localDeployerProperties = new LocalDeployerProperties();\n\t\tlocalDeployerProperties.getDocker().setPortMappings(goodMapping1 + \",\" + goodMapping2 + \",\" + incompleteMapping);\n\n\t\tProcessBuilder builder = new DockerCommandBuilder(\"scdf_default\")\n\t\t\t\t.buildExecutionCommand(request, new HashMap<>(), \"deployerId\", Optional.of(1),\n\t\t\t\t\t\tlocalDeployerProperties, Optional.empty());\n\n\t\tassertThat(builder.command()).contains(goodMapping1, goodMapping2);\n\t\tassertThat(builder.command()).doesNotContain(incompleteMapping);\n\t}\n\n\t@Test\n\tpublic void  testContainerVolumeMount(){\n\t\tAppDefinition appDefinition = new AppDefinition(\"foo\", null);\n\t\tResource resource = new DockerResource(\"foo/bar\");\n\t\tMap<String, String> deploymentProperties = Collections.emptyMap();\n\t\tAppDeploymentRequest request = new AppDeploymentRequest(appDefinition, resource, deploymentProperties);\n\n\t\tString goodMapping1 = \"/tmp:/tmp\";\n\t\tString goodMapping2 = \"/opt:/opt\";\n\t\tString incompleteMapping = \"/dev/null\";\n\t\tLocalDeployerProperties localDeployerProperties = new LocalDeployerProperties();\n\t\tlocalDeployerProperties.getDocker().setVolumeMounts(goodMapping1 + \",\" + goodMapping2 + \",\" + incompleteMapping);\n\n\t\tProcessBuilder builder = new DockerCommandBuilder(\"scdf_default\")\n\t\t\t\t.buildExecutionCommand(request, new HashMap<>(), \"deployerId\", Optional.of(1),\n\t\t\t\t\t\tlocalDeployerProperties, Optional.empty());\n\n\t\tassertThat(builder.command()).contains(goodMapping1, goodMapping2);\n\t\tassertThat(builder.command()).doesNotContain(incompleteMapping);\n\t}\n\n\t@Test\n\tpublic void  testContainerAdditionalHost(){\n\t\tAppDefinition appDefinition = new AppDefinition(\"foo\", null);\n\t\tResource resource = new DockerResource(\"foo/bar\");\n\t\tMap<String, String> deploymentProperties = Collections.emptyMap();\n\t\tAppDeploymentRequest request = new AppDeploymentRequest(appDefinition, resource, deploymentProperties);\n\n\t\tString goodMapping1 = \"host.docker.internal:host-gateway\";\n\t\tString incompleteMapping = \"host.docker.internal\";\n\t\tLocalDeployerProperties localDeployerProperties = new LocalDeployerProperties();\n\t\tlocalDeployerProperties.getDocker().setAdditionalHosts(goodMapping1 + \",\" + incompleteMapping);\n\n\t\tProcessBuilder builder = new DockerCommandBuilder(\"scdf_default\")\n\t\t\t\t.buildExecutionCommand(request, new HashMap<>(), \"deployerId\", Optional.of(1),\n\t\t\t\t\t\tlocalDeployerProperties, Optional.empty());\n\n\t\tassertThat(builder.command()).contains(goodMapping1);\n\t\tassertThat(builder.command()).doesNotContain(incompleteMapping);\n\t}\n}\n"
  },
  {
    "path": "spring-cloud-deployer-local/src/test/java/org/springframework/cloud/deployer/spi/local/JavaExecutionCommandBuilderTests.java",
    "content": "/*\n * Copyright 2015-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.local;\n\nimport java.io.File;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.stream.Collectors;\n\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\n\nimport org.springframework.cloud.deployer.spi.app.AppDeployer;\nimport org.springframework.cloud.deployer.spi.core.AppDefinition;\nimport org.springframework.cloud.deployer.spi.core.AppDeploymentRequest;\nimport org.springframework.core.io.ClassPathResource;\nimport org.springframework.core.io.Resource;\nimport org.springframework.core.io.UrlResource;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.AssertionsForClassTypes.assertThatExceptionOfType;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\npublic class JavaExecutionCommandBuilderTests {\n\n\tprivate JavaCommandBuilder commandBuilder;\n\tprivate List<String> args;\n\tprivate Map<String, String> deploymentProperties;\n\tprivate LocalDeployerProperties localDeployerProperties;\n\n\t@BeforeEach\n\tpublic void setUp() {\n\t\targs = new ArrayList<>();\n\t\tdeploymentProperties = new HashMap<>();\n\t\tlocalDeployerProperties = new LocalDeployerProperties();\n\t\tcommandBuilder = new JavaCommandBuilder(localDeployerProperties);\n\t}\n\n\t@Test\n\tpublic void testDirectJavaMemoryOption() {\n\t\tdeploymentProperties.put(AppDeployer.MEMORY_PROPERTY_KEY, \"1024m\");\n\t\tcommandBuilder.addJavaOptions(args, deploymentProperties, localDeployerProperties);\n\t\tassertThat(args).hasSize(1);\n\t\tassertThat(args.get(0)).isEqualTo(\"-Xmx1024m\");\n\t}\n\n\t@Test\n\tpublic void testDirectJavaMemoryOptionWithG() {\n\t\tdeploymentProperties.put(AppDeployer.MEMORY_PROPERTY_KEY, \"1g\");\n\t\tcommandBuilder.addJavaOptions(args, deploymentProperties, localDeployerProperties);\n\t\tassertThat(args).hasSize(1);\n\t\tassertThat(args.get(0)).isEqualTo(\"-Xmx1024m\");\n\t}\n\n\t@Test\n\tpublic void testJavaMemoryOption() {\n\t\tdeploymentProperties.put(LocalDeployerProperties.PREFIX + \".javaOpts\", \"-Xmx1024m\");\n\t\tcommandBuilder.addJavaOptions(args, deploymentProperties, localDeployerProperties);\n\t\tassertThat(args).hasSize(1);\n\t\tassertThat(args.get(0)).isEqualTo(\"-Xmx1024m\");\n\t}\n\n\t@Test\n\tpublic void testJavaMemoryOptionWithKebabCase() {\n\t\tdeploymentProperties.put(LocalDeployerProperties.PREFIX + \".java-opts\", \"-Xmx1024m\");\n\t\tcommandBuilder.addJavaOptions(args, deploymentProperties, localDeployerProperties);\n\t\tassertThat(args).hasSize(1);\n\t\tassertThat(args.get(0)).isEqualTo(\"-Xmx1024m\");\n\t}\n\n\t@Test\n\tpublic void testJavaCmdOption() throws Exception {\n\t\tMap<String, String> properties = new HashMap<>();\n\t\tproperties.put(LocalDeployerProperties.PREFIX + \".javaCmd\", \"/test/java\");\n\t\tResource resource = mock(Resource.class);\n\t\twhen(resource.getFile()).thenReturn(new File(\"/\"));\n\t\tAppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(mock(AppDefinition.class), resource, properties);\n\t\tProcessBuilder builder = commandBuilder.buildExecutionCommand(appDeploymentRequest, new HashMap<>(),\n\t\t\t\t\"deployerId\", Optional.of(1), new LocalDeployerProperties(), Optional.empty());\n\t\tassertThat(builder.command().get(0)).isEqualTo(\"/test/java\");\n\t}\n\n\t@Test\n\tpublic void testJavaCmdOptionWithKebabCase() throws Exception {\n\t\tMap<String, String> properties = new HashMap<>();\n\t\tproperties.put(LocalDeployerProperties.PREFIX + \".java-cmd\", \"/test/java\");\n\t\tResource resource = mock(Resource.class);\n\t\twhen(resource.getFile()).thenReturn(new File(\"/\"));\n\t\tAppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(mock(AppDefinition.class), resource, properties);\n\t\tProcessBuilder builder = commandBuilder.buildExecutionCommand(appDeploymentRequest, new HashMap<>(),\n\t\t\t\t\"deployerId\", Optional.of(1), new LocalDeployerProperties(), Optional.empty());\n\t\tassertThat(builder.command().get(0)).isEqualTo(\"/test/java\");\n\t}\n\n\t@Test\n\tpublic void testOverrideMemoryOptions() {\n\t\tdeploymentProperties.put(AppDeployer.MEMORY_PROPERTY_KEY, \"1024m\");\n\t\tdeploymentProperties.put(LocalDeployerProperties.PREFIX + \".javaOpts\", \"-Xmx2048m\");\n\t\tcommandBuilder.addJavaOptions(args, deploymentProperties, localDeployerProperties);\n\t\tassertThat(args).hasSize(1);\n\t\tassertThat(args.get(0)).isEqualTo(\"-Xmx2048m\");\n\t}\n\n\t@Test\n\tpublic void testDirectMemoryOptionsWithOtherOptions() {\n\t\tdeploymentProperties.put(AppDeployer.MEMORY_PROPERTY_KEY, \"1024m\");\n\t\tdeploymentProperties.put(LocalDeployerProperties.PREFIX + \".javaOpts\", \"-Dtest=foo\");\n\t\tcommandBuilder.addJavaOptions(args, deploymentProperties, localDeployerProperties);\n\t\tassertThat(args).hasSize(2);\n\t\tassertThat(args.get(0)).isEqualTo(\"-Xmx1024m\");\n\t\tassertThat(args.get(1)).isEqualTo(\"-Dtest=foo\");\n\t}\n\n\t@Test\n\tpublic void testMultipleOptions() {\n\t\tdeploymentProperties.put(LocalDeployerProperties.PREFIX + \".javaOpts\", \"-Dtest=foo -Dbar=baz\");\n\t\tcommandBuilder.addJavaOptions(args, deploymentProperties, localDeployerProperties);\n\t\tassertThat(args).hasSize(2);\n\t\tassertThat(args.get(0)).isEqualTo(\"-Dtest=foo\");\n\t\tassertThat(args.get(1)).isEqualTo(\"-Dbar=baz\");\n\t}\n\n\t@Test\n\tpublic void testConfigurationPropertiesOverride() {\n\t\tlocalDeployerProperties.setJavaOpts(\"-Dfoo=test -Dbaz=bar\");\n\t\tcommandBuilder.addJavaOptions(args, deploymentProperties, localDeployerProperties);\n\t\tassertThat(args).hasSize(2);\n\t\tassertThat(args.get(0)).isEqualTo(\"-Dfoo=test\");\n\t\tassertThat(args.get(1)).isEqualTo(\"-Dbaz=bar\");\n\t}\n\n\t@Test\n\tpublic void testJarExecution() {\n\t\tAppDefinition definition = new AppDefinition(\"randomApp\", new HashMap<>());\n\t\tdeploymentProperties.put(LocalDeployerProperties.PREFIX + \".javaOpts\", \"-Dtest=foo -Dbar=baz\");\n\t\tAppDeploymentRequest appDeploymentRequest =\n\t\t\t\tnew AppDeploymentRequest(definition, testResource(), deploymentProperties);\n\t\tcommandBuilder.addJavaExecutionOptions(args, appDeploymentRequest);\n\t\tassertThat(args).hasSize(2);\n\t\tassertThat(args.get(0)).isEqualTo(\"-jar\");\n\t\tassertThat(args.get(1)).contains(\"testResource.txt\");\n\t}\n\n\t@Test\n\tpublic void testBadResourceExecution() {\n\t\tassertThatExceptionOfType(IllegalStateException.class).isThrownBy(() -> {\n\t\t\tAppDefinition definition = new AppDefinition(\"randomApp\", new HashMap<>());\n\t\t\tdeploymentProperties.put(LocalDeployerProperties.PREFIX + \".javaOpts\", \"-Dtest=foo -Dbar=baz\");\n\t\t\tAppDeploymentRequest appDeploymentRequest =\n\t\t\t\t\tnew AppDeploymentRequest(definition, new UrlResource(\"https://spring.io\"), deploymentProperties);\n\t\t\tcommandBuilder.addJavaExecutionOptions(args, appDeploymentRequest);\n\t\t});\n\t}\n\n\t@Test\n\tpublic void testCommandBuilderSpringApplicationJson() {\n\t\tLocalDeployerProperties properties = new LocalDeployerProperties();\n\t\tLocalAppDeployer deployer = new LocalAppDeployer(properties);\n\t\tAppDefinition definition = new AppDefinition(\"foo\", Collections.singletonMap(\"foo\", \"bar\"));\n\n\t\tdeploymentProperties.put(LocalDeployerProperties.DEBUG_PORT, \"9999\");\n\t\tdeploymentProperties.put(LocalDeployerProperties.DEBUG_SUSPEND, \"y\");\n\t\tdeploymentProperties.put(LocalDeployerProperties.INHERIT_LOGGING, \"true\");\n\t\tAppDeploymentRequest request = new AppDeploymentRequest(definition, testResource(), deploymentProperties);\n\n\t\tProcessBuilder builder = deployer.buildProcessBuilder(request, definition.getProperties(), Optional.of(1), \"foo\");\n\t\tassertThat(builder.environment().keySet()).contains(AbstractLocalDeployerSupport.SPRING_APPLICATION_JSON);\n\t\tassertThat(builder.environment().get(AbstractLocalDeployerSupport.SPRING_APPLICATION_JSON)).isEqualTo(\"{\\\"foo\\\":\\\"bar\\\"}\");\n\t}\n\n\t@Test\n\tpublic void testCommandBuilderWithSpringApplicationJson() {\n\t\tLocalDeployerProperties properties = new LocalDeployerProperties();\n\t\tLocalAppDeployer deployer = new LocalAppDeployer(properties);\n\t\tMap<String, String> applicationProperties = new HashMap<>();\n\t\tapplicationProperties.put(\"foo\", \"bar\");\n\t\tString SAJ = \"{\\\"debug\\\":\\\"true\\\"}\";\n\t\tapplicationProperties.put(AbstractLocalDeployerSupport.SPRING_APPLICATION_JSON, SAJ);\n\t\tAppDefinition definition = new AppDefinition(\"foo\", applicationProperties);\n\n\t\tdeploymentProperties.put(LocalDeployerProperties.DEBUG_PORT, \"9999\");\n\t\tdeploymentProperties.put(LocalDeployerProperties.DEBUG_SUSPEND, \"y\");\n\t\tdeploymentProperties.put(LocalDeployerProperties.INHERIT_LOGGING, \"true\");\n\t\tAppDeploymentRequest request = new AppDeploymentRequest(definition, testResource(), deploymentProperties);\n\n\n\t\tProcessBuilder builder = deployer.buildProcessBuilder(request, definition.getProperties(), Optional.of(1), \"foo\");\n\t\tassertThat(builder.environment().keySet()).contains(AbstractLocalDeployerSupport.SPRING_APPLICATION_JSON);\n\t\tassertThat(builder.environment().get(AbstractLocalDeployerSupport.SPRING_APPLICATION_JSON)).isEqualTo(\"{\\\"foo\\\":\\\"bar\\\",\\\"debug\\\":\\\"true\\\"}\");\n\n\t}\n\n\t@Test\n\tpublic void testRetainEnv() {\n\t\tLocalDeployerProperties properties1 = new LocalDeployerProperties();\n\t\tLocalAppDeployer deployer1 = new LocalAppDeployer(properties1);\n\t\tAppDefinition definition1 = new AppDefinition(\"foo\", null);\n\t\tAppDeploymentRequest request1 = new AppDeploymentRequest(definition1, testResource(), deploymentProperties);\n\t\tProcessBuilder builder1 = deployer1.buildProcessBuilder(request1, definition1.getProperties(), Optional.of(1), \"foo\");\n\t\tList<String> env1 = builder1.environment().keySet().stream().map(String::toLowerCase).collect(Collectors.toList());\n\n\t\tLocalDeployerProperties properties2 = new LocalDeployerProperties();\n\t\tproperties2.setEnvVarsToInherit(new String[0]);\n\t\tLocalAppDeployer deployer2 = new LocalAppDeployer(properties2);\n\t\tAppDefinition definition2 = new AppDefinition(\"foo\", null);\n\t\tAppDeploymentRequest request2 = new AppDeploymentRequest(definition2, testResource(), deploymentProperties);\n\t\tProcessBuilder builder2 = deployer2.buildProcessBuilder(request2, definition2.getProperties(), Optional.of(1), \"foo\");\n\t\tList<String> env2 = builder2.environment().keySet().stream().map(String::toLowerCase).collect(Collectors.toList());\n\n\t\tif (env1.contains(\"path\")) {\n\t\t\t// path should be there, and it was check that something were removed\n\t\t\tassertThat(builder1.environment().keySet().size()).isGreaterThan(builder2.environment().keySet().size());\n\t\t}\n\t\tassertThat(env2).doesNotContain(\"path\");\n\t}\n\n\tprotected Resource testResource() {\n\t\treturn new ClassPathResource(\"testResource.txt\");\n\t}\n\n}\n"
  },
  {
    "path": "spring-cloud-deployer-local/src/test/java/org/springframework/cloud/deployer/spi/local/LocalAppDeployerEnvironmentIntegrationTests.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.local;\n\nimport java.io.BufferedReader;\nimport java.io.IOException;\nimport java.io.InputStreamReader;\nimport java.nio.ByteBuffer;\nimport java.time.Duration;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.Locale;\nimport java.util.Map;\nimport java.util.Map.Entry;\nimport java.util.UUID;\n\nimport org.junit.jupiter.api.Test;\n\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.boot.context.properties.EnableConfigurationProperties;\nimport org.springframework.boot.test.context.SpringBootTest;\nimport org.springframework.cloud.deployer.resource.docker.DockerResource;\nimport org.springframework.cloud.deployer.spi.app.ActuatorOperations;\nimport org.springframework.cloud.deployer.spi.app.AppDeployer;\nimport org.springframework.cloud.deployer.spi.app.AppInstanceStatus;\nimport org.springframework.cloud.deployer.spi.app.AppStatus;\nimport org.springframework.cloud.deployer.spi.app.DeploymentState;\nimport org.springframework.cloud.deployer.spi.core.AppDefinition;\nimport org.springframework.cloud.deployer.spi.core.AppDeploymentRequest;\nimport org.springframework.cloud.deployer.spi.local.LocalAppDeployerEnvironmentIntegrationTests.Config;\nimport org.springframework.cloud.deployer.spi.test.AbstractAppDeployerIntegrationJUnit5Tests;\nimport org.springframework.cloud.deployer.spi.test.AbstractIntegrationJUnit5Tests;\nimport org.springframework.cloud.deployer.spi.test.Timeout;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.core.io.Resource;\nimport org.springframework.web.client.RestTemplate;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.awaitility.Awaitility.await;\n\n/**\n * Integration tests for {@link LocalAppDeployer} not using SAJ.\n *\n * Now supports running with Docker images for tests, just set this env var:\n *\n *   SPRING_CLOUD_DEPLOYER_SPI_TEST_USE_DOCKER=true\n *\n * @author Eric Bottard\n * @author Mark Fisher\n * @author Oleg Zhurakousky\n * @author Janne Valkealahti\n * @author Ilayaperumal Gopinathan\n */\n@SpringBootTest(classes = { Config.class, AbstractIntegrationJUnit5Tests.Config.class }, value = {\n\t\t\"maven.remoteRepositories.springRepo.url=https://repo.spring.io/snapshot\",\n\t\t\"spring.cloud.deployer.local.use-spring-application-json=false\"\n})\npublic class LocalAppDeployerEnvironmentIntegrationTests extends AbstractAppDeployerIntegrationJUnit5Tests {\n\n\tprivate static final String TESTAPP_DOCKER_IMAGE_NAME = \"springcloud/spring-cloud-deployer-spi-test-app:latest\";\n\n\t@Autowired\n\tprivate AppDeployer appDeployer;\n\n\t@Autowired\n\tprivate ActuatorOperations actuatorOperations;\n\n\t@Value(\"${spring-cloud-deployer-spi-test-use-docker:false}\")\n\tprivate boolean useDocker;\n\n\t@Override\n\tprotected AppDeployer provideAppDeployer() {\n\t\treturn appDeployer;\n\t}\n\n\t@Override\n\tprotected Resource testApplication() {\n\t\tif (useDocker) {\n\t\t\tlog.info(\"Using Docker image for tests\");\n\t\t\treturn new DockerResource(TESTAPP_DOCKER_IMAGE_NAME);\n\t\t}\n\t\treturn super.testApplication();\n\t}\n\n\t@Override\n\tprotected String randomName() {\n\t\tif (LocalDeployerUtils.isWindows()) {\n\t\t\t// tweak random dir name on win to be shorter\n\t\t\tString uuid = UUID.randomUUID().toString();\n\t\t\tlong l = ByteBuffer.wrap(uuid.toString().getBytes()).getLong();\n\t\t\treturn testName + Long.toString(l, Character.MAX_RADIX);\n\t\t}\n\t\telse {\n\t\t\treturn super.randomName();\n\t\t}\n\t}\n\n\t@Test\n\tpublic void testEnvVariablesInheritedViaEnvEndpointNoSaj() {\n\t\tif (useDocker) {\n\t\t\t// would not expect to be able to check anything on docker\n\t\t\treturn;\n\t\t}\n\t\tMap<String, String> properties = new HashMap<>();\n\t\tAppDefinition definition = new AppDefinition(randomName(), properties);\n\t\tResource resource = testApplication();\n\t\tAppDeploymentRequest request = new AppDeploymentRequest(definition, resource);\n\n\t\tlog.info(\"Deploying {}...\", request.getDefinition().getName());\n\n\t\tString deploymentId = appDeployer().deploy(request);\n\t\tTimeout timeout = deploymentTimeout();\n\t\tawait().pollInterval(Duration.ofMillis(timeout.pause))\n\t\t\t\t.atMost(Duration.ofMillis(timeout.totalTime))\n\t\t\t\t.untilAsserted(() -> {\n\t\t\tassertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.deployed);\n\t\t});\n\n\t\tMap<String, AppInstanceStatus> instances = appDeployer().status(deploymentId).getInstances();\n\t\tString url = null;\n\t\tif (instances.size() == 1) {\n\t\t\turl = instances.entrySet().iterator().next().getValue().getAttributes().get(\"url\");\n\t\t}\n\t\tString env = null;\n\t\tif (url != null) {\n\t\t\tRestTemplate template = new RestTemplate();\n\t\t\tenv = template.getForObject(url + \"/actuator/env\", String.class);\n\t\t}\n\n\t\tlog.info(\"Undeploying {}...\", deploymentId);\n\n\t\ttimeout = undeploymentTimeout();\n\t\tappDeployer().undeploy(deploymentId);\n\t\tawait().pollInterval(Duration.ofMillis(timeout.pause))\n\t\t\t\t.atMost(Duration.ofMillis(timeout.totalTime))\n\t\t\t\t.untilAsserted(() -> {\n\t\t\tassertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.unknown);\n\t\t});\n\n\t\tassertThat(url).isNotNull();\n\t\tif (LocalDeployerUtils.isWindows()) {\n\t\t\t// windows is weird, we may still get Path or PATH\n\t\t\tassertThat(env).containsIgnoringCase(\"path\");\n\t\t}\n\t\telse {\n\t\t\tassertThat(env).contains(\"\\\"PATH\\\"\");\n\t\t\t// we're not using SAJ so it's i.e.\n\t\t\t// INSTANCE_INDEX not instance.index\n\t\t\tassertThat(env).contains(\"\\\"INSTANCE_INDEX\\\"\");\n\t\t\tassertThat(env).contains(\"\\\"SPRING_APPLICATION_INDEX\\\"\");\n\t\t\tassertThat(env).contains(\"\\\"SPRING_CLOUD_APPLICATION_GUID\\\"\");\n\t\t}\n\t}\n\n\t@Test\n\tpublic void testFailureToCallShutdownOnUndeploy() {\n\t\tMap<String, String> properties = new HashMap<>();\n\t\t// disable shutdown endpoint\n\t\tproperties.put(\"endpoints.shutdown.enabled\", \"false\");\n\t\tAppDefinition definition = new AppDefinition(randomName(), properties);\n\t\tResource resource = testApplication();\n\t\tAppDeploymentRequest request = new AppDeploymentRequest(definition, resource);\n\n\t\tlog.info(\"Deploying {}...\", request.getDefinition().getName());\n\n\t\tString deploymentId = appDeployer().deploy(request);\n\t\tTimeout timeout = deploymentTimeout();\n\t\tawait().pollInterval(Duration.ofMillis(timeout.pause))\n\t\t\t\t.atMost(Duration.ofMillis(timeout.totalTime))\n\t\t\t\t.untilAsserted(() -> {\n\t\t\tassertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.deployed);\n\t\t});\n\n\t\tlog.info(\"Undeploying {}...\", deploymentId);\n\n\t\ttimeout = undeploymentTimeout();\n\t\tappDeployer().undeploy(deploymentId);\n\t\tawait().pollInterval(Duration.ofMillis(timeout.pause))\n\t\t\t\t.atMost(Duration.ofMillis(timeout.totalTime))\n\t\t\t\t.untilAsserted(() -> {\n\t\t\tassertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.unknown);\n\t\t});\n\t}\n\n\t@Test\n\t// was triggered by GH-50 and subsequently GH-55\n\tpublic void testNoStdoutStderrOnInheritLoggingAndNoNPEOnGetAttributes() {\n\t\tMap<String, String> properties = new HashMap<>();\n\t\tAppDefinition definition = new AppDefinition(randomName(), properties);\n\t\tResource resource = testApplication();\n\t\tAppDeploymentRequest request = new AppDeploymentRequest(definition, resource, Collections.singletonMap(LocalDeployerProperties.INHERIT_LOGGING, \"true\"));\n\n\t\tAppDeployer deployer = appDeployer();\n\t\tString deploymentId = deployer.deploy(request);\n\t\tAppStatus appStatus = deployer.status(deploymentId);\n\t\tassertThat(appStatus.getInstances()).hasSizeGreaterThan(0);\n\t\tfor (Entry<String, AppInstanceStatus> instanceStatusEntry : appStatus.getInstances().entrySet()) {\n\t\t\tMap<String, String> attributes = instanceStatusEntry.getValue().getAttributes();\n\t\t\tassertThat(attributes).doesNotContainKey(\"stdout\");\n\t\t\tassertThat(attributes).doesNotContainKey(\"stderr\");\n\t\t}\n\t\tdeployer.undeploy(deploymentId);\n\t}\n\n\t@Test\n\tpublic void testInDebugModeWithSuspended() throws Exception {\n\t\tMap<String, String> properties = new HashMap<>();\n\t\tAppDefinition definition = new AppDefinition(randomName(), properties);\n\t\tResource resource = testApplication();\n\t\tMap<String, String> deploymentProperties = new HashMap<>();\n\t\tdeploymentProperties.put(LocalDeployerProperties.DEBUG_PORT, \"9999\");\n\t\tdeploymentProperties.put(LocalDeployerProperties.DEBUG_SUSPEND, \"y\");\n\t\tdeploymentProperties.put(LocalDeployerProperties.INHERIT_LOGGING, \"true\");\n\t\tAppDeploymentRequest request = new AppDeploymentRequest(definition, resource, deploymentProperties);\n\n\t\tAppDeployer deployer = appDeployer();\n\t\tString deploymentId = deployer.deploy(request);\n\t\tThread.sleep(5000);\n\t\tAppStatus appStatus = deployer.status(deploymentId);\n\t\tif (resource instanceof DockerResource) {\n\t\t\ttry {\n\t\t\t\tString containerId = getCommandOutput(\"docker ps -q --filter ancestor=\"+ TESTAPP_DOCKER_IMAGE_NAME);\n\t\t\t\tString logOutput = getCommandOutput(\"docker logs \"+ containerId);\n\t\t\t\tassertThat(logOutput).contains(\"Listening for transport dt_socket at address: 9999\");\n\t\t\t} catch (IOException e) {\n\t\t\t}\n\t\t}\n\t\telse {\n\t\t\tassertThat(appStatus.toString()).contains(\"deploying\");\n\t\t}\n\n\t\tdeployer.undeploy(deploymentId);\n\t}\n\n\t@Test\n\tpublic void testActuatorOperations() {\n\t\tif (useDocker) {\n\t\t\t// would not expect to be able to check anything on docker\n\t\t\treturn;\n\t\t}\n\t\tMap<String, String> properties = new HashMap<>();\n\t\tAppDefinition definition = new AppDefinition(randomName(), properties);\n\t\tResource resource = testApplication();\n\t\tAppDeploymentRequest request = new AppDeploymentRequest(definition, resource);\n\n\t\tlog.info(\"Deploying {}...\", request.getDefinition().getName());\n\n\t\tString deploymentId = appDeployer().deploy(request);\n\t\tTimeout timeout = deploymentTimeout();\n\t\tawait().pollInterval(Duration.ofMillis(timeout.pause))\n\t\t\t\t.atMost(Duration.ofMillis(timeout.totalTime))\n\t\t\t\t.untilAsserted(() -> {\n\t\t\t\t\tassertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.deployed);\n\t\t\t\t});\n\t\tString id = deploymentId + \"-0\";\n\t\tMap<String, Object> env = actuatorOperations\n\t\t\t\t.getFromActuator(deploymentId, id, \"/env\", Map.class);\n\t\tassertThat(env).containsKeys(\"activeProfiles\", \"propertySources\");\n\t\tMap<String, Object> status = actuatorOperations\n\t\t\t\t.getFromActuator(deploymentId, id, \"/health\", Map.class);\n\t\tassertThat(status.get(\"status\")).isEqualTo(\"UP\");\n\n\t\tMap<String, Object> loggers = actuatorOperations\n\t\t\t\t.getFromActuator(deploymentId, id, \"/loggers/org.springframework\", Map.class);\n\t\tassertThat(loggers).isNotNull();\n\t\tassertThat(loggers.get(\"configuredLevel\")).isNull();\n\t\tactuatorOperations.postToActuator(deploymentId, id,\"/loggers/org.springframework\",\n\t\t\t\tCollections.singletonMap(\"configuredLevel\", \"debug\"),  Object.class);\n\t\tloggers = actuatorOperations\n\t\t\t\t.getFromActuator(deploymentId, id, \"/loggers/org.springframework\", Map.class);\n\t\tassertThat(((String)loggers.get(\"configuredLevel\")).toLowerCase(Locale.ROOT)).isEqualTo(\"debug\");\n\n\t}\n\n\tprivate String getCommandOutput(String cmd) throws IOException {\n\t\tProcess process = Runtime.getRuntime().exec(cmd);\n\t\tBufferedReader stdInput = new BufferedReader(new InputStreamReader(process.getInputStream()));\n\t\treturn stdInput.lines().findFirst().get();\n\t}\n\n\t@Configuration\n\t@EnableConfigurationProperties(LocalDeployerProperties.class)\n\tpublic static class Config {\n\n\t\t@Bean\n\t\tpublic AppDeployer appDeployer(LocalDeployerProperties properties) {\n\t\t\treturn new LocalAppDeployer(properties);\n\t\t}\n\n\t\t@Bean\n\t\tActuatorOperations actuatorOperations(AppDeployer appDeployer, LocalDeployerProperties properties) {\n\t\t\treturn new LocalActuatorTemplate(new RestTemplate(), appDeployer, properties.getAppAdmin());\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "spring-cloud-deployer-local/src/test/java/org/springframework/cloud/deployer/spi/local/LocalAppDeployerIntegrationTests.java",
    "content": "/*\n * Copyright 2016-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.local;\n\nimport java.io.BufferedReader;\nimport java.io.File;\nimport java.io.IOException;\nimport java.io.InputStreamReader;\nimport java.nio.ByteBuffer;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.time.Duration;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Map.Entry;\nimport java.util.UUID;\nimport java.util.stream.Collectors;\n\nimport org.junit.jupiter.api.Test;\n\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.boot.context.properties.EnableConfigurationProperties;\nimport org.springframework.boot.test.context.SpringBootTest;\nimport org.springframework.cloud.deployer.resource.docker.DockerResource;\nimport org.springframework.cloud.deployer.spi.app.AppDeployer;\nimport org.springframework.cloud.deployer.spi.app.AppInstanceStatus;\nimport org.springframework.cloud.deployer.spi.app.AppStatus;\nimport org.springframework.cloud.deployer.spi.app.DeploymentState;\nimport org.springframework.cloud.deployer.spi.core.AppDefinition;\nimport org.springframework.cloud.deployer.spi.core.AppDeploymentRequest;\nimport org.springframework.cloud.deployer.spi.local.LocalAppDeployerIntegrationTests.Config;\nimport org.springframework.cloud.deployer.spi.test.AbstractAppDeployerIntegrationJUnit5Tests;\nimport org.springframework.cloud.deployer.spi.test.AbstractIntegrationJUnit5Tests;\nimport org.springframework.cloud.deployer.spi.test.Timeout;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.core.io.Resource;\nimport org.springframework.test.annotation.DirtiesContext;\nimport org.springframework.web.client.RestTemplate;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.awaitility.Awaitility.await;\n\n/**\n * Integration tests for {@link LocalAppDeployer}.\n * <p>\n * Now supports running with Docker images for tests, just set this env var:\n * <p>\n * SPRING_CLOUD_DEPLOYER_SPI_TEST_USE_DOCKER=true\n *\n * @author Eric Bottard\n * @author Mark Fisher\n * @author Oleg Zhurakousky\n * @author Janne Valkealahti\n * @author Ilayaperumal Gopinathan\n */\n@SpringBootTest(classes = {\n\tConfig.class,\n\tAbstractIntegrationJUnit5Tests.Config.class\n}, properties = {\n\t\"maven.remoteRepositories.springRepo.url=https://repo.spring.io/snapshot\"\n})\n@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)\npublic class LocalAppDeployerIntegrationTests extends AbstractAppDeployerIntegrationJUnit5Tests {\n\n\tprivate static final String TESTAPP_DOCKER_IMAGE_NAME = \"springcloud/spring-cloud-deployer-spi-test-app:latest\";\n\n\t@Autowired\n\tprivate AppDeployer appDeployer;\n\n\t@Value(\"${spring-cloud-deployer-spi-test-use-docker:false}\")\n\tprivate boolean useDocker;\n\n\t@Override\n\tprotected AppDeployer provideAppDeployer() {\n\t\treturn appDeployer;\n\t}\n\n\t@Override\n\tprotected Resource testApplication() {\n\t\tif (useDocker) {\n\t\t\tlog.info(\"Using Docker image for tests\");\n\t\t\treturn new DockerResource(TESTAPP_DOCKER_IMAGE_NAME);\n\t\t}\n\t\treturn super.testApplication();\n\t}\n\n\t@Override\n\tprotected String randomName() {\n\t\tif (LocalDeployerUtils.isWindows()) {\n\t\t\t// tweak random dir name on win to be shorter\n\t\t\tString uuid = UUID.randomUUID().toString();\n\t\t\tlong l = ByteBuffer.wrap(uuid.getBytes()).getLong();\n\t\t\treturn testName + Long.toString(l, Character.MAX_RADIX);\n\t\t} else {\n\t\t\treturn super.randomName();\n\t\t}\n\t}\n\n\t@Test\n\tpublic void testEnvVariablesInheritedViaEnvEndpoint() {\n\t\tif (useDocker) {\n\t\t\t// would not expect to be able to check anything on docker\n\t\t\treturn;\n\t\t}\n\t\tMap<String, String> properties = new HashMap<>();\n\t\tAppDefinition definition = new AppDefinition(randomName(), properties);\n\t\tResource resource = testApplication();\n\t\tAppDeploymentRequest request = new AppDeploymentRequest(definition, resource);\n\n\t\tlog.info(\"Deploying {}...\", request.getDefinition().getName());\n\n\t\tAppDeployer appDeployer = appDeployer();\n\t\tString deploymentId = appDeployer.deploy(request);\n\t\tTimeout timeout = deploymentTimeout();\n\t\tawait().pollInterval(Duration.ofMillis(timeout.pause))\n\t\t\t.atMost(Duration.ofMillis(timeout.totalTime))\n\t\t\t.untilAsserted(() -> {\n\t\t\t\tassertThat(appDeployer.status(deploymentId).getState()).isEqualTo(DeploymentState.deployed);\n\t\t\t});\n\n\t\tMap<String, AppInstanceStatus> instances = appDeployer.status(deploymentId).getInstances();\n\t\tString url = null;\n\t\tif (instances.size() == 1) {\n\t\t\turl = instances.entrySet().iterator().next().getValue().getAttributes().get(\"url\");\n\t\t}\n\t\tString env = null;\n\t\tif (url != null) {\n\t\t\tRestTemplate template = new RestTemplate();\n\t\t\tenv = template.getForObject(url + \"/actuator/env\", String.class);\n\t\t}\n\n\t\tlog.info(\"Undeploying {}...\", deploymentId);\n\n\t\ttimeout = undeploymentTimeout();\n\t\tappDeployer.undeploy(deploymentId);\n\t\tawait().pollInterval(Duration.ofMillis(timeout.pause))\n\t\t\t.atMost(Duration.ofMillis(timeout.totalTime))\n\t\t\t.untilAsserted(() -> {\n\t\t\t\tassertThat(appDeployer.status(deploymentId).getState()).isEqualTo(DeploymentState.unknown);\n\t\t\t});\n\n\t\tassertThat(url).isNotNull();\n\t\tif (LocalDeployerUtils.isWindows()) {\n\t\t\t// windows is weird, we may still get Path or PATH\n\t\t\tassertThat(env).containsIgnoringCase(\"path\");\n\t\t} else {\n\t\t\tassertThat(env).contains(\"\\\"PATH\\\"\");\n\t\t\t// we're defaulting to SAJ so it's i.e.\n\t\t\t// instance.index not INSTANCE_INDEX\n\t\t\tassertThat(env).contains(\"\\\"instance.index\\\"\");\n\t\t\tassertThat(env).contains(\"\\\"spring.application.index\\\"\");\n\t\t\tassertThat(env).contains(\"\\\"spring.cloud.application.guid\\\"\");\n\t\t\tassertThat(env).contains(\"\\\"spring.cloud.stream.instanceIndex\\\"\");\n\t\t}\n\t}\n\n\t@Test\n\tpublic void testAppLogRetrieval() {\n\t\tMap<String, String> properties = new HashMap<>();\n\t\tAppDefinition definition = new AppDefinition(randomName(), properties);\n\t\tResource resource = testApplication();\n\t\tAppDeploymentRequest request = new AppDeploymentRequest(definition, resource);\n\n\t\tlog.info(\"Deploying {}...\", request.getDefinition().getName());\n\t\tString deploymentId = appDeployer().deploy(request);\n\t\tTimeout timeout = deploymentTimeout();\n\t\tawait().pollInterval(Duration.ofMillis(timeout.pause))\n\t\t\t.atMost(Duration.ofMillis(timeout.totalTime))\n\t\t\t.untilAsserted(() -> {\n\t\t\t\tassertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.deployed);\n\t\t\t});\n\t\tString logContent = appDeployer().getLog(deploymentId);\n\t\tassertThat(logContent).contains(\"Starting DeployerIntegrationTestApplication\");\n\t}\n\n\t// TODO: remove when these two are forced in tck tests\n\t@Test\n\tpublic void testScale() {\n\t\tdoTestScale(false);\n\t}\n\n\t@Test\n\tpublic void testScaleWithIndex() {\n\t\tdoTestScale(true);\n\t}\n\n\t@Test\n\tpublic void testFailureToCallShutdownOnUndeploy() {\n\t\tMap<String, String> properties = new HashMap<>();\n\t\t// disable shutdown endpoint\n\t\tproperties.put(\"endpoints.shutdown.enabled\", \"false\");\n\t\tAppDefinition definition = new AppDefinition(randomName(), properties);\n\t\tResource resource = testApplication();\n\t\tAppDeploymentRequest request = new AppDeploymentRequest(definition, resource);\n\n\t\tlog.info(\"Deploying {}...\", request.getDefinition().getName());\n\n\t\tAppDeployer appDeployer = appDeployer();\n\t\tString deploymentId = appDeployer.deploy(request);\n\t\tTimeout timeout = deploymentTimeout();\n\t\tawait().pollInterval(Duration.ofMillis(timeout.pause))\n\t\t\t.atMost(Duration.ofMillis(timeout.totalTime))\n\t\t\t.untilAsserted(() ->\n\t\t\t\tassertThat(appDeployer.status(deploymentId).getState()).isEqualTo(DeploymentState.deployed)\n\t\t\t);\n\n\t\tlog.info(\"Undeploying {}...\", deploymentId);\n\n\t\ttimeout = undeploymentTimeout();\n\t\tappDeployer.undeploy(deploymentId);\n\t\tawait().pollInterval(Duration.ofMillis(timeout.pause))\n\t\t\t.atMost(Duration.ofMillis(timeout.totalTime))\n\t\t\t.untilAsserted(() ->\n\t\t\t\tassertThat(appDeployer.status(deploymentId).getState()).isEqualTo(DeploymentState.unknown)\n\t\t\t);\n\t}\n\n\t@Test\n\t// was triggered by GH-50 and subsequently GH-55\n\tpublic void testNoStdoutStderrOnInheritLoggingAndNoNPEOnGetAttributes() {\n\t\tMap<String, String> properties = new HashMap<>();\n\t\tAppDefinition definition = new AppDefinition(randomName(), properties);\n\t\tResource resource = testApplication();\n\t\tAppDeploymentRequest request = new AppDeploymentRequest(definition,\n\t\t\tresource,\n\t\t\tCollections.singletonMap(LocalDeployerProperties.INHERIT_LOGGING, \"true\"));\n\n\t\tAppDeployer deployer = appDeployer();\n\t\tString deploymentId = deployer.deploy(request);\n\t\tAppStatus appStatus = deployer.status(deploymentId);\n\t\tassertThat(appStatus.getInstances()).hasSizeGreaterThan(0);\n\t\tfor (Entry<String, AppInstanceStatus> instanceStatusEntry : appStatus.getInstances().entrySet()) {\n\t\t\tMap<String, String> attributes = instanceStatusEntry.getValue().getAttributes();\n\t\t\tassertThat(attributes).doesNotContainKey(\"stdout\");\n\t\t\tassertThat(attributes).doesNotContainKey(\"stderr\");\n\t\t}\n\t\tdeployer.undeploy(deploymentId);\n\t}\n\n\t@Test\n\tpublic void testInDebugModeWithSuspended() throws Exception {\n\t\tMap<String, String> properties = new HashMap<>();\n\t\tAppDefinition definition = new AppDefinition(randomName(), properties);\n\t\tResource resource = testApplication();\n\t\tMap<String, String> deploymentProperties = new HashMap<>();\n\t\tdeploymentProperties.put(LocalDeployerProperties.DEBUG_PORT, \"9999\");\n\t\tdeploymentProperties.put(LocalDeployerProperties.DEBUG_SUSPEND, \"y\");\n\t\tdeploymentProperties.put(LocalDeployerProperties.INHERIT_LOGGING, \"true\");\n\t\tAppDeploymentRequest request = new AppDeploymentRequest(definition, resource, deploymentProperties);\n\n\t\tAppDeployer deployer = appDeployer();\n\t\tString deploymentId = deployer.deploy(request);\n\t\tThread.sleep(5000);\n\t\tAppStatus appStatus = deployer.status(deploymentId);\n\t\tif (resource instanceof DockerResource) {\n\t\t\ttry {\n\t\t\t\tString containerId = getCommandOutput(\"docker ps -q --filter ancestor=\" + TESTAPP_DOCKER_IMAGE_NAME);\n\t\t\t\tString logOutput = getCommandOutput(\"docker logs \" + containerId);\n\t\t\t\tassertThat(logOutput).contains(\"Listening for transport dt_socket at address: 9999\");\n\t\t\t} catch (IOException e) {\n\t\t\t\tlog.debug(\"Exception:ignored:\" + e);\n\t\t\t}\n\t\t} else {\n\t\t\tassertThat(appStatus.toString()).contains(\"deploying\");\n\t\t}\n\n\t\tdeployer.undeploy(deploymentId);\n\t}\n\n\t@Test\n\tpublic void testInDebugModeWithSuspendedUseCamelCase() throws Exception {\n\t\tMap<String, String> properties = new HashMap<>();\n\t\tAppDefinition definition = new AppDefinition(randomName(), properties);\n\t\tResource resource = testApplication();\n\t\tMap<String, String> deploymentProperties = new HashMap<>();\n\t\tdeploymentProperties.put(LocalDeployerProperties.PREFIX + \".debugPort\", \"8888\");\n\t\tdeploymentProperties.put(LocalDeployerProperties.PREFIX + \".debugSuspend\", \"y\");\n\t\tdeploymentProperties.put(LocalDeployerProperties.PREFIX + \".inheritLogging\", \"true\");\n\t\tAppDeploymentRequest request = new AppDeploymentRequest(definition, resource, deploymentProperties);\n\n\t\tAppDeployer deployer = appDeployer();\n\t\tString deploymentId = deployer.deploy(request);\n\t\tThread.sleep(5000);\n\t\tAppStatus appStatus = deployer.status(deploymentId);\n\t\tif (resource instanceof DockerResource) {\n\t\t\ttry {\n\t\t\t\tString containerId = getCommandOutput(\"docker ps -q --filter ancestor=\" + TESTAPP_DOCKER_IMAGE_NAME);\n\t\t\t\tString logOutput = getCommandOutput(\"docker logs \" + containerId);\n\t\t\t\tassertThat(logOutput).contains(\"Listening for transport dt_socket at address: 8888\");\n\t\t\t\tString containerPorts = getCommandOutputAll(\"docker port \" + containerId);\n\t\t\t\tassertThat(containerPorts).contains(\"8888/tcp -> 0.0.0.0:8888\");\n\t\t\t} catch (IOException e) {\n\t\t\t\tlog.debug(\"Exception:ignored:\" + e);\n\t\t\t}\n\t\t} else {\n\t\t\tassertThat(appStatus.toString()).contains(\"deploying\");\n\t\t}\n\n\t\tdeployer.undeploy(deploymentId);\n\t}\n\n\t@Test\n\tpublic void testUseDefaultDeployerProperties() throws IOException {\n\n\t\tLocalDeployerProperties localDeployerProperties = new LocalDeployerProperties();\n\t\tPath tmpPath = new File(System.getProperty(\"java.io.tmpdir\")).toPath();\n\t\tPath customWorkDirRoot = tmpPath.resolve(\"test-default-directory\");\n\t\tlocalDeployerProperties.setWorkingDirectoriesRoot(customWorkDirRoot.toFile().getAbsolutePath());\n\n\t\t// Create a new LocalAppDeployer using a working directory that is different from the default value.\n\t\tAppDeployer appDeployer = new LocalAppDeployer(localDeployerProperties);\n\n\t\tList<Path> beforeDirs = getBeforePaths(customWorkDirRoot);\n\n\t\tMap<String, String> properties = new HashMap<>();\n\t\tproperties.put(\"server.port\", \"0\");\n\t\tAppDefinition definition = new AppDefinition(randomName(), properties);\n\t\tResource resource = testApplication();\n\t\tAppDeploymentRequest request = new AppDeploymentRequest(definition, resource);\n\n\n\t\t// Deploy\n\t\tString deploymentId = appDeployer.deploy(request);\n\t\tTimeout timeout = deploymentTimeout();\n\t\tawait().pollInterval(Duration.ofMillis(timeout.pause))\n\t\t\t.atMost(Duration.ofMillis(timeout.totalTime))\n\t\t\t.untilAsserted(() -> assertThat(appDeployer.status(deploymentId).getState()).isEqualTo(DeploymentState.deployed));\n\n\t\ttimeout = undeploymentTimeout();\n\t\t// Undeploy\n\t\tappDeployer.undeploy(deploymentId);\n\t\tawait().pollInterval(Duration.ofMillis(timeout.pause))\n\t\t\t.atMost(Duration.ofMillis(timeout.totalTime))\n\t\t\t.untilAsserted(() -> assertThat(appDeployer.status(deploymentId).getState()).isEqualTo(DeploymentState.unknown));\n\n\t\tList<Path> afterDirs = getAfterPaths(customWorkDirRoot);\n\t\tassertThat(afterDirs).as(\"Additional working directory not created\").hasSize(beforeDirs.size() + 1);\n\t}\n\n\t@Test\n\tpublic void testZeroPortReportsDeployed() throws IOException {\n\t\tMap<String, String> properties = new HashMap<>();\n\t\tproperties.put(\"server.port\", \"0\");\n\t\tPath tmpPath = new File(System.getProperty(\"java.io.tmpdir\")).toPath();\n\t\tPath customWorkDirRoot = tmpPath.resolve(\"spring-cloud-deployer-app-workdir\");\n\t\tMap<String, String> deploymentProperties = new HashMap<>();\n\t\tdeploymentProperties.put(LocalDeployerProperties.PREFIX + \".working-directories-root\", customWorkDirRoot.toFile().getAbsolutePath());\n\n\t\tAppDefinition definition = new AppDefinition(randomName(), properties);\n\t\tResource resource = testApplication();\n\t\tAppDeploymentRequest request = new AppDeploymentRequest(definition, resource, deploymentProperties);\n\n\t\tList<Path> beforeDirs = getBeforePaths(customWorkDirRoot);\n\n\t\tlog.info(\"Deploying {}...\", request.getDefinition().getName());\n\t\tString deploymentId = appDeployer().deploy(request);\n\t\tTimeout timeout = deploymentTimeout();\n\t\tawait().pollInterval(Duration.ofMillis(timeout.pause))\n\t\t\t.atMost(Duration.ofMillis(timeout.totalTime))\n\t\t\t.untilAsserted(() -> assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.deployed));\n\n\t\tlog.info(\"Undeploying {}...\", deploymentId);\n\n\t\ttimeout = undeploymentTimeout();\n\t\tappDeployer().undeploy(deploymentId);\n\t\tawait().pollInterval(Duration.ofMillis(timeout.pause))\n\t\t\t.atMost(Duration.ofMillis(timeout.totalTime))\n\t\t\t.untilAsserted(() -> assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.unknown));\n\n\n\t\tList<Path> afterDirs = getAfterPaths(customWorkDirRoot);\n\t\tassertThat(afterDirs.size()).as(\"Additional working directory not created\").isEqualTo(beforeDirs.size() + 1);\n\t}\n\n\t@Test\n\tpublic void testStartupProbeFail() {\n\t\tMap<String, String> deploymentProperties = new HashMap<>();\n\t\tdeploymentProperties.put(LocalDeployerProperties.PREFIX + \".startup-probe.path\", \"/fake\");\n\n\t\tAppDefinition definition = new AppDefinition(randomName(), null);\n\t\tResource resource = testApplication();\n\t\tAppDeploymentRequest request = new AppDeploymentRequest(definition, resource, deploymentProperties);\n\n\t\tlog.info(\"Deploying {}...\", request.getDefinition().getName());\n\n\t\tAppDeployer appDeployer = appDeployer();\n\t\tString deploymentId = appDeployer.deploy(request);\n\n\t\tawait().atMost(Duration.ofSeconds(30)).until(() ->\n\t\t\tappDeployer.status(deploymentId).getState().equals(DeploymentState.deploying)\n\t\t);\n\t\tawait().during(Duration.ofSeconds(10)).atMost(Duration.ofSeconds(12)).until(() ->\n\t\t\tappDeployer.status(deploymentId).getState().equals(DeploymentState.deploying)\n\t\t);\n\n\t\tlog.info(\"Undeploying {}...\", deploymentId);\n\n\t\tTimeout timeout = undeploymentTimeout();\n\t\tappDeployer.undeploy(deploymentId);\n\t\tawait().pollInterval(Duration.ofMillis(timeout.pause))\n\t\t\t.atMost(Duration.ofMillis(timeout.totalTime))\n\t\t\t.untilAsserted(() ->\n\t\t\t\tassertThat(appDeployer.status(deploymentId).getState()).isEqualTo(DeploymentState.unknown)\n\t\t\t);\n\t}\n\n\t@Test\n\tpublic void testStartupProbeSucceed() {\n\t\tMap<String, String> deploymentProperties = new HashMap<>();\n\t\tdeploymentProperties.put(LocalDeployerProperties.PREFIX + \".startup-probe.path\", \"/actuator/info\");\n\n\t\tAppDefinition definition = new AppDefinition(randomName(), null);\n\t\tResource resource = testApplication();\n\t\tAppDeploymentRequest request = new AppDeploymentRequest(definition, resource, deploymentProperties);\n\n\t\tlog.info(\"Deploying {}...\", request.getDefinition().getName());\n\n\t\tAppDeployer appDeployer = appDeployer();\n\t\tString deploymentId = appDeployer.deploy(request);\n\n\t\tawait().atMost(Duration.ofSeconds(30)).until(() ->\n\t\t\tappDeployer.status(deploymentId).getState().equals(DeploymentState.deploying)\n\t\t);\n\t\tawait().atMost(Duration.ofSeconds(12)).until(() ->\n\t\t\tappDeployer.status(deploymentId).getState().equals(DeploymentState.deployed)\n\t\t);\n\n\t\tlog.info(\"Undeploying {}...\", deploymentId);\n\n\t\tTimeout timeout = undeploymentTimeout();\n\t\tappDeployer.undeploy(deploymentId);\n\t\tawait().pollInterval(Duration.ofMillis(timeout.pause))\n\t\t\t.atMost(Duration.ofMillis(timeout.totalTime))\n\t\t\t.untilAsserted(() ->\n\t\t\t\tassertThat(appDeployer.status(deploymentId).getState()).isEqualTo(DeploymentState.unknown)\n\t\t\t);\n\t}\n\n\t@Test\n\tpublic void testHealthProbeFail() {\n\t\tMap<String, String> deploymentProperties = new HashMap<>();\n\t\tdeploymentProperties.put(LocalDeployerProperties.PREFIX + \".health-probe.path\", \"/fake\");\n\n\t\tAppDefinition definition = new AppDefinition(randomName(), null);\n\t\tResource resource = testApplication();\n\t\tAppDeploymentRequest request = new AppDeploymentRequest(definition, resource, deploymentProperties);\n\n\t\tlog.info(\"Deploying {}...\", request.getDefinition().getName());\n\n\t\tAppDeployer appDeployer = appDeployer();\n\t\tString deploymentId = appDeployer.deploy(request);\n\n\t\tawait()\n\t\t\t.atMost(Duration.ofSeconds(30))\n\t\t\t.until(() -> {\n\t\t\t\tDeploymentState state = appDeployer.status(deploymentId).getState();\n\t\t\t\tlog.info(\"Awaiting failed. State={}\", state);\n\t\t\t\treturn DeploymentState.failed.equals(state);\n\t\t\t});\n\n\t\tlog.info(\"Undeploying {}...\", deploymentId);\n\n\t\tTimeout timeout = undeploymentTimeout();\n\t\tappDeployer.undeploy(deploymentId);\n\t\tawait()\n\t\t\t.pollDelay(Duration.ofMillis(timeout.pause))\n\t\t\t.pollInterval(Duration.ofMillis(timeout.pause))\n\t\t\t.atMost(Duration.ofMillis(timeout.totalTime))\n\t\t\t.untilAsserted(() ->\n\t\t\t\tassertThat(appDeployer.status(deploymentId).getState()).isEqualTo(DeploymentState.unknown)\n\t\t\t);\n\t}\n\n\t@Test\n\tpublic void testHealthProbeSucceed() {\n\t\tMap<String, String> deploymentProperties = new HashMap<>();\n\t\tdeploymentProperties.put(LocalDeployerProperties.PREFIX + \".health-probe.path\", \"/actuator/info\");\n\n\t\tAppDefinition definition = new AppDefinition(randomName(), null);\n\t\tResource resource = testApplication();\n\t\tAppDeploymentRequest request = new AppDeploymentRequest(definition, resource, deploymentProperties);\n\n\t\tlog.info(\"Deploying {}...\", request.getDefinition().getName());\n\n\t\tAppDeployer appDeployer = appDeployer();\n\t\tString deploymentId = appDeployer.deploy(request);\n\n\t\tawait()\n\t\t\t.atMost(Duration.ofSeconds(30))\n\t\t\t.until(() -> {\n\t\t\t\t\tDeploymentState state = appDeployer.status(deploymentId).getState();\n\t\t\t\t\tlog.info(\"Awaiting deployed. State={}\", state);\n\t\t\t\t\treturn DeploymentState.deployed.equals(state);\n\t\t\t\t}\n\t\t\t);\n\n\t\tlog.info(\"Undeploying {}...\", deploymentId);\n\n\t\tTimeout timeout = undeploymentTimeout();\n\t\tappDeployer.undeploy(deploymentId);\n\t\tawait()\n\t\t\t.pollDelay(Duration.ofMillis(timeout.pause))\n\t\t\t.pollInterval(Duration.ofMillis(timeout.pause))\n\t\t\t.atMost(Duration.ofMillis(timeout.totalTime))\n\t\t\t.untilAsserted(() -> {\n\t\t\t\tDeploymentState state = appDeployer.status(deploymentId).getState();\n\t\t\t\tlog.info(\"Awaiting unknown. State={}\", state);\n\t\t\t\tassertThat(state).isEqualTo(DeploymentState.unknown);\n\t\t\t});\n\t}\n\n\t@SuppressWarnings(\"resource\")\n\tprivate List<Path> getAfterPaths(Path customWorkDirRoot) throws IOException {\n\t\tif (!Files.exists(customWorkDirRoot)) {\n\t\t\treturn new ArrayList<>();\n\t\t}\n\t\treturn Files.walk(customWorkDirRoot, 1)\n\t\t\t.filter(Files::isDirectory)\n\t\t\t.filter(path -> !path.getFileName().toString().startsWith(\".\"))\n\t\t\t.collect(Collectors.toList());\n\t}\n\n\tprivate List<Path> getBeforePaths(Path customWorkDirRoot) throws IOException {\n\t\tList<Path> beforeDirs = new ArrayList<>();\n\t\tbeforeDirs.add(customWorkDirRoot);\n\t\tif (Files.exists(customWorkDirRoot)) {\n\t\t\tbeforeDirs = Files.walk(customWorkDirRoot, 1)\n\t\t\t\t.filter(Files::isDirectory)\n\t\t\t\t.collect(Collectors.toList());\n\t\t}\n\t\treturn beforeDirs;\n\t}\n\n\tprivate String getCommandOutput(String cmd) throws IOException {\n\t\tProcess process = Runtime.getRuntime().exec(cmd);\n\t\tBufferedReader stdInput = new BufferedReader(new InputStreamReader(process.getInputStream()));\n\t\treturn stdInput.lines().findFirst().orElse(null);\n\t}\n\n\tprivate String getCommandOutputAll(String cmd) throws IOException {\n\t\tProcess process = Runtime.getRuntime().exec(cmd);\n\t\tBufferedReader stdInput = new BufferedReader(new InputStreamReader(process.getInputStream()));\n\t\treturn stdInput.lines().collect(Collectors.joining());\n\t}\n\n\t@Configuration\n\t@EnableConfigurationProperties(LocalDeployerProperties.class)\n\tpublic static class Config {\n\n\t\t@Bean\n\t\tpublic AppDeployer appDeployer(LocalDeployerProperties properties) {\n\t\t\treturn new LocalAppDeployer(properties);\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "spring-cloud-deployer-local/src/test/java/org/springframework/cloud/deployer/spi/local/LocalDeployerPropertiesTests.java",
    "content": "/*\n * Copyright 2019-2020 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage org.springframework.cloud.deployer.spi.local;\n\nimport java.nio.file.Paths;\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.condition.EnabledOnOs;\nimport org.junit.jupiter.api.condition.OS;\n\nimport org.springframework.boot.context.properties.EnableConfigurationProperties;\nimport org.springframework.boot.test.context.runner.ApplicationContextRunner;\nimport org.springframework.core.env.StandardEnvironment;\nimport org.springframework.core.env.SystemEnvironmentPropertySource;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\npublic class LocalDeployerPropertiesTests {\n\n\tprivate final ApplicationContextRunner contextRunner = new ApplicationContextRunner();\n\n\t@Test\n\t@EnabledOnOs(OS.LINUX)\n\tpublic void defaultNoPropertiesSet() {\n\t\tthis.contextRunner\n\t\t\t.withUserConfiguration(Config1.class)\n\t\t\t.run((context) -> {\n\t\t\t\tLocalDeployerProperties properties = context.getBean(LocalDeployerProperties.class);\n\t\t\t\tassertThat(properties.getDebugPort()).isNull();\n\t\t\t\tassertThat(properties.getDebugSuspend()).isEqualTo(LocalDeployerProperties.DebugSuspendType.y);\n\t\t\t\tassertThat(properties.isDeleteFilesOnExit()).isTrue();\n\t\t\t\tassertThat(properties.getEnvVarsToInherit()).containsExactlyInAnyOrder(\"TMP\", \"LANG\", \"LANGUAGE\", \"LC_.*\",\n\t\t\t\t\t\"PATH\", \"SPRING_APPLICATION_JSON\");\n\t\t\t\tassertThat(properties.isInheritLogging()).isFalse();\n\t\t\t\tassertThat(properties.getJavaCommand(\"2\")).contains(\"java\");\n\t\t\t\tassertThat(properties.getJavaOpts()).isNull();\n\t\t\t\tassertThat(properties.getMaximumConcurrentTasks()).isEqualTo(20);\n\t\t\t\tassertThat(properties.getPortRange()).isNotNull();\n\t\t\t\tassertThat(properties.getPortRange().getLow()).isEqualTo(20000);\n\t\t\t\tassertThat(properties.getPortRange().getHigh()).isEqualTo(61000);\n\t\t\t\tassertThat(properties.getShutdownTimeout()).isEqualTo(30);\n\t\t\t\tassertThat(properties.isUseSpringApplicationJson()).isTrue();\n\t\t\t\tassertThat(properties.getDocker().getNetwork()).isEqualTo(\"bridge\");\n\t\t\t\tassertThat(properties.getStartupProbe()).isNotNull();\n\t\t\t\tassertThat(properties.getStartupProbe().getPath()).isNull();\n\t\t\t\tassertThat(properties.getHealthProbe()).isNotNull();\n\t\t\t\tassertThat(properties.getHealthProbe().getPath()).isNull();\n\t\t\t});\n\t}\n\n\t@Test\n\tpublic void setAllProperties() {\n\t\tthis.contextRunner\n\t\t\t.withInitializer(context -> {\n\t\t\t\tMap<String, Object> map = new HashMap<>();\n\t\t\t\tmap.put(\"spring.cloud.deployer.local.debug-port\", \"8888\");\n\t\t\t\tmap.put(\"spring.cloud.deployer.local.debug-suspend\", \"n\");\n\t\t\t\tmap.put(\"spring.cloud.deployer.local.delete-files-on-exit\", false);\n\t\t\t\tmap.put(\"spring.cloud.deployer.local.env-vars-to-inherit\", \"FOO,BAR\");\n\t\t\t\tmap.put(\"spring.cloud.deployer.local.inherit-logging\", true);\n\t\t\t\tmap.put(\"spring.cloud.deployer.local.java-home-path.2\", \"foobar1\");\n\t\t\t\tmap.put(\"spring.cloud.deployer.local.java-opts\", \"foobar2\");\n\t\t\t\tmap.put(\"spring.cloud.deployer.local.maximum-concurrent-tasks\", 1234);\n\t\t\t\tmap.put(\"spring.cloud.deployer.local.port-range.low\", 2345);\n\t\t\t\tmap.put(\"spring.cloud.deployer.local.port-range.high\", 2346);\n\t\t\t\tmap.put(\"spring.cloud.deployer.local.shutdown-timeout\", 3456);\n\t\t\t\tmap.put(\"spring.cloud.deployer.local.use-spring-application-json\", false);\n\t\t\t\tmap.put(\"spring.cloud.deployer.local.docker.network\", \"spring-cloud-dataflow-server_default\");\n\t\t\t\tmap.put(\"spring.cloud.deployer.local.docker.port-mappings\", \"9091:5678\");\n\t\t\t\tmap.put(\"spring.cloud.deployer.local.docker.volume-mounts\", \"/tmp:/opt\");\n\t\t\t\tmap.put(\"spring.cloud.deployer.local.startup-probe.path\", \"/path1\");\n\t\t\t\tmap.put(\"spring.cloud.deployer.local.health-probe.path\", \"/path2\");\n\t\t\t\tmap.put(\"spring.cloud.deployer.local.app-admin.user\", \"user\");\n\t\t\t\tmap.put(\"spring.cloud.deployer.local.app-admin.password\", \"password\");\n\n\t\t\t\tcontext.getEnvironment().getPropertySources().addLast(new SystemEnvironmentPropertySource(\n\t\t\t\t\tStandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, map));\n\t\t\t})\n\t\t\t.withUserConfiguration(Config1.class)\n\t\t\t.run((context) -> {\n\t\t\t\tLocalDeployerProperties properties = context.getBean(LocalDeployerProperties.class);\n\t\t\t\tassertThat(properties.getDebugPort()).isEqualTo(8888);\n\t\t\t\tassertThat(properties.getDebugSuspend()).isEqualTo(LocalDeployerProperties.DebugSuspendType.n);\n\t\t\t\tassertThat(properties.isDeleteFilesOnExit()).isFalse();\n\t\t\t\tassertThat(properties.getEnvVarsToInherit()).containsExactly(\"FOO\", \"BAR\");\n\t\t\t\tassertThat(properties.isInheritLogging()).isTrue();\n\t\t\t\tassertThat(properties.getJavaHomePath().get(\"2\")).contains(\"foobar1\");\n\t\t\t\tassertThat(properties.getJavaOpts()).contains(\"foobar2\");\n\t\t\t\tassertThat(properties.getMaximumConcurrentTasks()).isEqualTo(1234);\n\t\t\t\tassertThat(properties.getPortRange()).isNotNull();\n\t\t\t\tassertThat(properties.getPortRange().getLow()).isEqualTo(2345);\n\t\t\t\tassertThat(properties.getPortRange().getHigh()).isEqualTo(2346);\n\t\t\t\tassertThat(properties.getShutdownTimeout()).isEqualTo(3456);\n\t\t\t\tassertThat(properties.isUseSpringApplicationJson()).isFalse();\n\t\t\t\tassertThat(properties.getDocker().getNetwork()).isEqualTo(\"spring-cloud-dataflow-server_default\");\n\t\t\t\tassertThat(properties.getDocker().getPortMappings()).isEqualTo(\"9091:5678\");\n\t\t\t\tassertThat(properties.getDocker().getVolumeMounts()).isEqualTo(\"/tmp:/opt\");\n\t\t\t\tassertThat(properties.getStartupProbe().getPath()).isEqualTo(\"/path1\");\n\t\t\t\tassertThat(properties.getHealthProbe().getPath()).isEqualTo(\"/path2\");\n\t\t\t\tassertThat(properties.getAppAdmin().getUser()).isEqualTo(\"user\");\n\t\t\t\tassertThat(properties.getAppAdmin().getPassword()).isEqualTo(\"password\");\n\t\t\t});\n\t}\n\n\n\t@Test\n\tpublic void setAllPropertiesCamelCase() {\n\t\tthis.contextRunner\n\t\t\t.withInitializer(context -> {\n\n\t\t\t\tMap<String, Object> map = new HashMap<>();\n\t\t\t\tmap.put(\"spring.cloud.deployer.local.debugPort\", \"8888\");\n\t\t\t\tmap.put(\"spring.cloud.deployer.local.debugSuspend\", \"n\");\n\t\t\t\tmap.put(\"spring.cloud.deployer.local.deleteFilesOnExit\", false);\n\t\t\t\tmap.put(\"spring.cloud.deployer.local.envVarsToInherit\", \"FOO,BAR\");\n\t\t\t\tmap.put(\"spring.cloud.deployer.local.inheritLogging\", true);\n\t\t\t\tmap.put(\"spring.cloud.deployer.local.javaHomePath.2\", \"foobar1\");\n\t\t\t\tmap.put(\"spring.cloud.deployer.local.javaOpts\", \"foobar2\");\n\t\t\t\tmap.put(\"spring.cloud.deployer.local.maximumConcurrentTasks\", 1234);\n\t\t\t\tmap.put(\"spring.cloud.deployer.local.portRange.low\", 2345);\n\t\t\t\tmap.put(\"spring.cloud.deployer.local.portRange.high\", 2346);\n\t\t\t\tmap.put(\"spring.cloud.deployer.local.shutdownTimeout\", 3456);\n\t\t\t\tmap.put(\"spring.cloud.deployer.local.useSpringApplicationJson\", false);\n\t\t\t\tmap.put(\"spring.cloud.deployer.local.docker.network\", \"spring-cloud-dataflow-server_default\");\n\t\t\t\tmap.put(\"spring.cloud.deployer.local.docker.portMappings\", \"9091:5678\");\n\t\t\t\tmap.put(\"spring.cloud.deployer.local.docker.volumeMounts\", \"/tmp:/opt\");\n\t\t\t\tmap.put(\"spring.cloud.deployer.local.appAdmin.user\", \"user\");\n\t\t\t\tmap.put(\"spring.cloud.deployer.local.appAdmin.password\", \"password\");\n\n\t\t\t\tcontext.getEnvironment().getPropertySources().addLast(new SystemEnvironmentPropertySource(\n\t\t\t\t\tStandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, map));\n\t\t\t})\n\t\t\t.withUserConfiguration(Config1.class)\n\t\t\t.run((context) -> {\n\t\t\t\tLocalDeployerProperties properties = context.getBean(LocalDeployerProperties.class);\n\t\t\t\tassertThat(properties.getDebugPort()).isEqualTo(8888);\n\t\t\t\tassertThat(properties.getDebugSuspend()).isEqualTo(LocalDeployerProperties.DebugSuspendType.n);\n\t\t\t\tassertThat(properties.isDeleteFilesOnExit()).isFalse();\n\t\t\t\tassertThat(properties.getEnvVarsToInherit()).containsExactly(\"FOO\", \"BAR\");\n\t\t\t\tassertThat(properties.isInheritLogging()).isTrue();\n\t\t\t\tassertThat(properties.getJavaHomePath().get(\"2\")).contains(\"foobar1\");\n\t\t\t\tassertThat(properties.getJavaOpts()).contains(\"foobar2\");\n\t\t\t\tassertThat(properties.getMaximumConcurrentTasks()).isEqualTo(1234);\n\t\t\t\tassertThat(properties.getPortRange()).isNotNull();\n\t\t\t\tassertThat(properties.getPortRange().getLow()).isEqualTo(2345);\n\t\t\t\tassertThat(properties.getPortRange().getHigh()).isEqualTo(2346);\n\t\t\t\tassertThat(properties.getShutdownTimeout()).isEqualTo(3456);\n\t\t\t\tassertThat(properties.isUseSpringApplicationJson()).isFalse();\n\t\t\t\tassertThat(properties.getDocker().getNetwork()).isEqualTo(\"spring-cloud-dataflow-server_default\");\n\t\t\t\tassertThat(properties.getDocker().getPortMappings()).isEqualTo(\"9091:5678\");\n\t\t\t\tassertThat(properties.getDocker().getVolumeMounts()).isEqualTo(\"/tmp:/opt\");\n\t\t\t\tassertThat(properties.getAppAdmin().getUser()).isEqualTo(\"user\");\n\t\t\t\tassertThat(properties.getAppAdmin().getPassword()).isEqualTo(\"password\");\n\t\t\t});\n\t}\n\n\t@Test\n\t@EnabledOnOs(OS.WINDOWS)\n\tpublic void testOnWindows() {\n\t\tthis.contextRunner\n\t\t\t.withInitializer(context -> {\n\t\t\t\tMap<String, Object> map = new HashMap<>();\n\t\t\t\tmap.put(\"spring.cloud.deployer.local.working-directories-root\", \"file:/C:/tmp\");\n\t\t\t\tcontext.getEnvironment().getPropertySources().addLast(new SystemEnvironmentPropertySource(\n\t\t\t\t\tStandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, map));\n\t\t\t})\n\n\t\t\t.withUserConfiguration(Config1.class)\n\t\t\t.run((context) -> {\n\t\t\t\tLocalDeployerProperties properties = context.getBean(LocalDeployerProperties.class);\n\t\t\t\tassertThat(properties.getWorkingDirectoriesRoot()).isNotNull();\n\t\t\t\tassertThat(properties.getWorkingDirectoriesRoot().toString()).isEqualTo(\"C:\\\\tmp\");\n\t\t\t});\n\t}\n\n\t@Test\n\t@EnabledOnOs(OS.LINUX)\n\tpublic void testOnLinux() {\n\t\tthis.contextRunner\n\t\t\t.withInitializer(context -> {\n\t\t\t\tMap<String, Object> map = new HashMap<>();\n\t\t\t\tmap.put(\"spring.cloud.deployer.local.working-directories-root\", \"/tmp\");\n\n\t\t\t\tcontext.getEnvironment().getPropertySources().addLast(new SystemEnvironmentPropertySource(\n\t\t\t\t\tStandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, map));\n\t\t\t})\n\t\t\t.withUserConfiguration(Config1.class)\n\t\t\t.run((context) -> {\n\t\t\t\tLocalDeployerProperties properties = context.getBean(LocalDeployerProperties.class);\n\t\t\t\tassertThat(properties.getWorkingDirectoriesRoot()).isNotNull();\n\t\t\t\tassertThat(properties.getWorkingDirectoriesRoot().toString()).isEqualTo(\"/tmp\");\n\t\t\t});\n\t}\n\n\t@Test\n\t@EnabledOnOs(OS.LINUX)\n\tpublic void testCopyProperties() {\n\t\tLocalDeployerProperties from = new LocalDeployerProperties();\n\t\tLocalDeployerProperties to = new LocalDeployerProperties(from);\n\t\tassertThat(from).isEqualTo(to);\n\n\t\tfrom = new LocalDeployerProperties();\n\t\tfrom.setDebugPort(1234);\n\t\tfrom.setDebugSuspend(LocalDeployerProperties.DebugSuspendType.y);\n\t\tfrom.getDocker().setNetwork(\"network\");\n\t\tfrom.setDeleteFilesOnExit(true);\n\t\tfrom.setEnvVarsToInherit(new String[]{\"foobar\"});\n\t\tfrom.setInheritLogging(false);\n\t\tfrom.getJavaHomePath().put(\"2\", \"javaCmd\");\n\t\tfrom.setJavaOpts(\"javaOpts\");\n\t\tfrom.setMaximumConcurrentTasks(234);\n\t\tfrom.getPortRange().setHigh(2345);\n\t\tfrom.getPortRange().setLow(2344);\n\t\tfrom.setShutdownTimeout(345);\n\t\tfrom.setUseSpringApplicationJson(false);\n\t\tfrom.setWorkingDirectoriesRoot(Paths.get(\"/first\"));\n\t\tto = new LocalDeployerProperties(from);\n\t\tassertThat(from).isEqualTo(to);\n\t}\n\n\t@EnableConfigurationProperties({LocalDeployerProperties.class})\n\tprivate static class Config1 {\n\t}\n}\n"
  },
  {
    "path": "spring-cloud-deployer-local/src/test/java/org/springframework/cloud/deployer/spi/local/LocalDeployerSupportTests.java",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.local;\n\nimport java.net.MalformedURLException;\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\n\nimport org.springframework.cloud.deployer.spi.core.AppDefinition;\nimport org.springframework.cloud.deployer.spi.core.AppDeploymentRequest;\nimport org.springframework.core.io.ClassPathResource;\nimport org.springframework.core.io.Resource;\nimport org.springframework.test.util.ReflectionTestUtils;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * Tests for the AbstractLocalDeployerSupport\n *\n * @author Thomas Risberg\n */\npublic class LocalDeployerSupportTests {\n\n\tprivate LocalDeployerProperties localDeployerProperties;\n\tprivate AbstractLocalDeployerSupport localDeployerSupport;\n\n\t@BeforeEach\n\tpublic void setUp() {\n\t\tlocalDeployerProperties = new LocalDeployerProperties();\n\t\tlocalDeployerSupport = new AbstractLocalDeployerSupport(this.localDeployerProperties) {};\n\t}\n\n\t@Test\n\tpublic void testAppPropsAsSAJ() throws MalformedURLException {\n\t\tAppDeploymentRequest appDeploymentRequest = createAppDeploymentRequest();\n\n\t\tHashMap<String, String> envVarsToUse = new HashMap<>(appDeploymentRequest.getDefinition().getProperties());\n\t\tMap<String, String> environmentVariables = localDeployerSupport.formatApplicationProperties(appDeploymentRequest,\n\t\t\t\tenvVarsToUse);\n\n\t\tassertThat(environmentVariables).hasSize(1);\n\t\tassertThat(environmentVariables).containsEntry(AbstractLocalDeployerSupport.SPRING_APPLICATION_JSON,\n\t\t\t\t\"{\\\"test.foo\\\":\\\"foo\\\",\\\"test.bar\\\":\\\"bar\\\"}\");\n\t}\n\n\t@Test\n\tpublic void testCalcServerPort() throws MalformedURLException {\n\t\tMap<String, String> applicationProperties = new HashMap<>();\n\t\tMap<String, String> deploymentPropertites = new HashMap<>();\n\t\tList<String> commandLineArgs = new ArrayList<>();\n\n\t\t// test adding to application properties\n\t\tapplicationProperties.put(\"server.port\", \"9292\");\n\t\tAppDefinition definition = new AppDefinition(\"randomApp\", applicationProperties);\n\n\t\tAppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, testResource(),\n\t\t\t\tdeploymentPropertites, commandLineArgs);\n\n\t\tint portToUse = localDeployerSupport.calcServerPort(appDeploymentRequest, false, new HashMap<>());\n\t\tassertThat(portToUse).isEqualTo(9292);\n\n\t\t// test adding to command line args, which has higher precedence than application properties\n\t\tcommandLineArgs.add(LocalTaskLauncher.SERVER_PORT_KEY_COMMAND_LINE_ARG  + 9191);\n\t\tappDeploymentRequest = new AppDeploymentRequest(definition, testResource(),\n\t\t\t\tdeploymentPropertites, commandLineArgs);\n\n\t\tportToUse = localDeployerSupport.calcServerPort(appDeploymentRequest, false, new HashMap<>());\n\t\tassertThat(portToUse).isEqualTo(9191);\n\n\t\t// test using dynamic port assignment\n\t\tportToUse = localDeployerSupport.calcServerPort(appDeploymentRequest, true, new HashMap<>());\n\t\tassertThat(portToUse).isNotEqualTo(9191);\n\t\tassertThat(portToUse).isNotEqualTo(9292);\n\t}\n\n\t@Test\n\tpublic void testShutdownPropertyConfiguresRequestFactory() throws Exception {\n\t\tLocalDeployerProperties properties = new LocalDeployerProperties();\n\t\tproperties.setShutdownTimeout(1);\n\t\tAbstractLocalDeployerSupport abstractLocalDeployerSupport = new AbstractLocalDeployerSupport(properties) {};\n\t\tObject restTemplate = ReflectionTestUtils.getField(abstractLocalDeployerSupport, \"restTemplate\");\n\t\tObject requestFactory = ReflectionTestUtils.getField(restTemplate, \"requestFactory\");\n\t\tObject connectTimeout = ReflectionTestUtils.getField(requestFactory, \"connectTimeout\");\n\t\tObject readTimeout = ReflectionTestUtils.getField(requestFactory, \"readTimeout\");\n\t\tassertThat(connectTimeout).isEqualTo(1000);\n\t\tassertThat(readTimeout).isEqualTo(1000);\n\t}\n\n\t@Test\n\tpublic void testShutdownPropertyNotConfiguresRequestFactory() throws Exception {\n\t\tLocalDeployerProperties properties = new LocalDeployerProperties();\n\t\tproperties.setShutdownTimeout(-1);\n\t\tAbstractLocalDeployerSupport abstractLocalDeployerSupport = new AbstractLocalDeployerSupport(properties) {};\n\t\tObject restTemplate = ReflectionTestUtils.getField(abstractLocalDeployerSupport, \"restTemplate\");\n\t\tObject requestFactory = ReflectionTestUtils.getField(restTemplate, \"requestFactory\");\n\t\tObject connectTimeout =  ReflectionTestUtils.getField(requestFactory,\"connectTimeout\");\n\t\tObject readTimeout = ReflectionTestUtils.getField(requestFactory, \"readTimeout\");\n\t\tassertThat(connectTimeout).isEqualTo(-1);\n\t\tassertThat(readTimeout).isEqualTo(-1);\n\t}\n\n\tprotected AppDeploymentRequest createAppDeploymentRequest() throws MalformedURLException {\n\t\treturn createAppDeploymentRequest(new HashMap<>());\n\t}\n\n\tprotected AppDeploymentRequest createAppDeploymentRequest(Map<String, String> depProps) throws MalformedURLException {\n\t\tMap<String, String> appProperties = new HashMap<>();\n\t\tappProperties.put(\"test.foo\", \"foo\");\n\t\tappProperties.put(\"test.bar\", \"bar\");\n\t\tAppDefinition definition = new AppDefinition(\"randomApp\", appProperties);\n\t\treturn new AppDeploymentRequest(definition, testResource(), depProps);\n\t}\n\n\n\tprotected Resource testResource() {\n\t\treturn new ClassPathResource(\"testResource.txt\");\n\t}\n\n}\n"
  },
  {
    "path": "spring-cloud-deployer-local/src/test/java/org/springframework/cloud/deployer/spi/local/LocalTaskLauncherIntegrationTests.java",
    "content": "/*\n * Copyright 2016-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.local;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.nio.ByteBuffer;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.time.Duration;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.UUID;\nimport java.util.stream.Collectors;\n\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.junit.jupiter.api.io.TempDir;\n\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.boot.context.properties.EnableConfigurationProperties;\nimport org.springframework.boot.test.context.SpringBootTest;\nimport org.springframework.boot.test.system.CapturedOutput;\nimport org.springframework.boot.test.system.OutputCaptureExtension;\nimport org.springframework.cloud.deployer.resource.docker.DockerResource;\nimport org.springframework.cloud.deployer.spi.core.AppDefinition;\nimport org.springframework.cloud.deployer.spi.core.AppDeploymentRequest;\nimport org.springframework.cloud.deployer.spi.local.LocalTaskLauncherIntegrationTests.Config;\nimport org.springframework.cloud.deployer.spi.task.LaunchState;\nimport org.springframework.cloud.deployer.spi.task.TaskLauncher;\nimport org.springframework.cloud.deployer.spi.test.AbstractIntegrationJUnit5Tests;\nimport org.springframework.cloud.deployer.spi.test.AbstractTaskLauncherIntegrationJUnit5Tests;\nimport org.springframework.cloud.deployer.spi.test.Timeout;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.core.io.Resource;\nimport org.springframework.util.FileSystemUtils;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.awaitility.Awaitility.await;\n\n/**\n * Integration tests for {@link LocalTaskLauncher}.\n *\n * Now supports running with Docker images for tests, just set this env var:\n *\n *   SPRING_CLOUD_DEPLOYER_SPI_TEST_USE_DOCKER=true\n *\n * @author Eric Bottard\n * @author Janne Valkealahti\n * @author David Turanski\n * @author Glenn Renfro\n * @author Ilayaperumal Gopinathan\n * @author Ben Blinebury\n *\n */\n@SpringBootTest(classes = {Config.class, AbstractIntegrationJUnit5Tests.Config.class}, value = {\n\t\t\"maven.remoteRepositories.springRepo.url=https://repo.spring.io/snapshot\" })\n@ExtendWith(OutputCaptureExtension.class)\npublic class LocalTaskLauncherIntegrationTests extends AbstractTaskLauncherIntegrationJUnit5Tests {\n\tprivate static final String SYMBOLIC_LINK = \"symbolic_link.txt\";\n\n\t@Autowired\n\tprivate TaskLauncher taskLauncher;\n\n\t@Value(\"${spring-cloud-deployer-spi-test-use-docker:false}\")\n\tprivate boolean useDocker;\n\n\t@Override\n\tprotected TaskLauncher provideTaskLauncher() {\n\t\treturn taskLauncher;\n\t}\n\n\t@Override\n\tprotected Resource testApplication() {\n\t\tif (useDocker) {\n\t\t\tlog.info(\"Using Docker image for tests\");\n\t\t\treturn new DockerResource(\"springcloud/spring-cloud-deployer-spi-test-app:latest\");\n\t\t}\n\t\treturn super.testApplication();\n\t}\n\n\t@Override\n\tprotected String randomName() {\n\t\tif (LocalDeployerUtils.isWindows()) {\n\t\t\t// tweak random dir name on win to be shorter\n\t\t\tString uuid = UUID.randomUUID().toString();\n\t\t\tlong l = ByteBuffer.wrap(uuid.toString().getBytes()).getLong();\n\t\t\treturn testName + Long.toString(l, Character.MAX_RADIX);\n\t\t}\n\t\telse {\n\t\t\treturn super.randomName();\n\t\t}\n\t}\n\n\t@Test\n\tpublic void testPassingServerPortViaCommandLineArgs(CapturedOutput output){\n\t\tMap<String, String> appProperties = new HashMap<>();\n\t\tappProperties.put(\"killDelay\", \"0\");\n\t\tappProperties.put(\"exitCode\", \"0\");\n\n\t\tAppDefinition definition = new AppDefinition(this.randomName(), appProperties);\n\n\t\tbasicLaunchAndValidation(definition, null);\n\t\tassertThat(output).contains(\"Logs will be in\");\n\t}\n\n\t@Test\n\tpublic void testBasicLaunchWithSymbolicLink(CapturedOutput output) throws Exception {\n\t\tMap<String, String> appProperties = new HashMap<>();\n\t\tappProperties.put(\"killDelay\", \"0\");\n\t\tappProperties.put(\"exitCode\", \"0\");\n\n\t\tPath symlink = createSymbolicLink();\n\n\t\tAppDefinition definition = new AppDefinition(this.randomName(), appProperties);\n\t\tMap<String, String> deploymentProperties = Collections.singletonMap(\"spring.cloud.deployer.local.workingDirectoriesRoot\", SYMBOLIC_LINK);\n\n\t\tbasicLaunchAndValidation(definition, deploymentProperties);\n\n\t\tassertThat(output).contains(\"Logs will be in\");\n\n\t\tFiles.delete(Paths.get(symlink.toString()));\n\t}\n\n\t@TempDir\n\tprivate File tempDirectory;\n\n\tprivate Path createSymbolicLink() throws IOException {\n\t\tFile testFile = new File(tempDirectory, \"testFile.txt\");\n\n\t\tPath target = testFile.toPath();\n\n\t\tPath link = Paths.get(SYMBOLIC_LINK);\n\n\t\tif (Files.exists(link)) {\n\t\t\tFiles.delete(link);\n\t\t}\n\n\t\treturn Files.createSymbolicLink(link, target);\n\t}\n\n\t@Test\n\tpublic void testInheritLoggingAndWorkDir(CapturedOutput output) throws IOException {\n\n\t\tMap<String, String> appProperties = new HashMap<>();\n\t\tappProperties.put(\"killDelay\", \"0\");\n\t\tappProperties.put(\"exitCode\", \"0\");\n\n\t\tMap<String, String> deploymentProperties = new HashMap<>();\n\t\tdeploymentProperties.put(LocalDeployerProperties.INHERIT_LOGGING, \"true\");\n\t\tPath tmpPath = new File(System.getProperty(\"java.io.tmpdir\")).toPath();\n\t\tPath customWorkDirRoot = tmpPath.resolve(\"spring-cloud-deployer-task-workdir\");\n\t\tdeploymentProperties.put(LocalDeployerProperties.PREFIX + \".working-directories-root\", customWorkDirRoot.toFile().getAbsolutePath());\n\n\t\tAppDefinition definition = new AppDefinition(this.randomName(), appProperties);\n\n\t\tList<Path> beforeDirs = new ArrayList<>();\n\t\tbeforeDirs.add(customWorkDirRoot);\n\t\tif (Files.exists(customWorkDirRoot)) {\n\t\t\tbeforeDirs = Files.walk(customWorkDirRoot, 1)\n\t\t\t\t\t.filter(path -> Files.isDirectory(path))\n\t\t\t\t\t.collect(Collectors.toList());\n\t\t}\n\n\t\tbasicLaunchAndValidation(definition, deploymentProperties);\n\t\tassertThat(output).contains(\"Logs will be inherited.\");\n\n\t\tList<Path> afterDirs = Files.walk(customWorkDirRoot, 1)\n\t\t\t\t.filter(path -> Files.isDirectory(path))\n\t\t\t\t.collect(Collectors.toList());\n\t\tassertThat(afterDirs).as(\"Additional working directory not created\").hasSize(beforeDirs.size() + 1);\n\n\t\t// clean up if test passed\n\t\tFileSystemUtils.deleteRecursively(customWorkDirRoot);\n\t}\n\n\t@Test\n\tpublic void testAppLogRetrieval() {\n\t\tMap<String, String> appProperties = new HashMap<>();\n\t\tappProperties.put(\"killDelay\", \"0\");\n\t\tappProperties.put(\"exitCode\", \"0\");\n\t\tAppDefinition definition = new AppDefinition(randomName(), appProperties);\n\t\tResource resource = testApplication();\n\t\tAppDeploymentRequest request = new AppDeploymentRequest(definition, resource);\n\n\t\tString launchId1 = taskLauncher().launch(request);\n\n\t\tTimeout timeout = deploymentTimeout();\n\n\t\tawait().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> {\n\t\t\tassertThat(taskLauncher().status(launchId1).getState()).isEqualTo(LaunchState.complete);\n        });\n\n\t\tString logContent = taskLauncher().getLog(launchId1);\n\t\tassertThat(logContent).contains(\"Starting DeployerIntegrationTestApplication\");\n\t}\n\n\t@Test\n\tpublic void testDeleteHistoryOnReLaunch() {\n\t\tMap<String, String> appProperties = new HashMap<>();\n\t\tappProperties.put(\"killDelay\", \"0\");\n\t\tappProperties.put(\"exitCode\", \"0\");\n\t\tAppDefinition definition = new AppDefinition(randomName(), appProperties);\n\t\tResource resource = testApplication();\n\t\tAppDeploymentRequest request = new AppDeploymentRequest(definition, resource);\n\n\t\tString launchId1 = taskLauncher().launch(request);\n\n\t\tTimeout timeout = deploymentTimeout();\n\n\t\tawait().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> {\n\t\t\tassertThat(taskLauncher().status(launchId1).getState()).isEqualTo(LaunchState.complete);\n        });\n\n\t\tString launchId2 = taskLauncher().launch(request);\n\n\t\tassertThat(launchId2).isNotEqualTo(launchId1);\n\n\t\ttimeout = deploymentTimeout();\n\n\t\tawait().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> {\n\t\t\tassertThat(taskLauncher().status(launchId2).getState()).isEqualTo(LaunchState.complete);\n        });\n\n\t\tawait().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> {\n\t\t\tassertThat(taskLauncher().status(launchId1).getState()).isEqualTo(LaunchState.unknown);\n        });\n\n\t\tString launchId3 = taskLauncher().launch(request);\n\n\t\tassertThat(launchId3).isNotEqualTo(launchId1);\n\t\tassertThat(launchId3).isNotEqualTo(launchId2);\n\n\t\ttimeout = deploymentTimeout();\n\n\t\tawait().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> {\n\t\t\tassertThat(taskLauncher().status(launchId3).getState()).isEqualTo(LaunchState.complete);\n        });\n\n\t\tawait().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> {\n\t\t\tassertThat(taskLauncher().status(launchId1).getState()).isEqualTo(LaunchState.unknown);\n        });\n\n\t\tawait().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> {\n\t\t\tassertThat(taskLauncher().status(launchId2).getState()).isEqualTo(LaunchState.unknown);\n        });\n\n\t\ttaskLauncher().destroy(definition.getName());\n\t}\n\n\tprivate void basicLaunchAndValidation(AppDefinition definition, Map<String, String> deploymentProperties) {\n\t\tList<String> commandLineArgs = new ArrayList<>(1);\n\t\t// Test to ensure no issues parsing server.port command line arg.\n\t\tcommandLineArgs.add(LocalTaskLauncher.SERVER_PORT_KEY_COMMAND_LINE_ARG + DeployerSocketUtils.findAvailableTcpPort(LocalTaskLauncher.DEFAULT_SERVER_PORT));\n\n\t\tAppDeploymentRequest request = new AppDeploymentRequest(definition, this.testApplication(), deploymentProperties, commandLineArgs);\n\n\n\t\tthis.log.info(\"Launching {}...\", request.getDefinition().getName());\n\n\t\tString launchId = this.taskLauncher().launch(request);\n\t\tTimeout timeout = this.deploymentTimeout();\n\t\tawait().pollInterval(Duration.ofMillis(1))\n                .atMost(Duration.ofSeconds(30))\n                .untilAsserted(() -> {\n\t\t\tassertThat(taskLauncher.getRunningTaskExecutionCount()).isEqualTo(1);\n        });\n\n\t\tawait().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> {\n\t\t\tassertThat(taskLauncher().status(launchId).getState()).isEqualTo(LaunchState.complete);\n        });\n\n\t\tthis.taskLauncher().destroy(definition.getName());\n\n\t\tawait().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> {\n\t\t\tassertThat(taskLauncher.getRunningTaskExecutionCount()).isEqualTo(0);\n        });\n\t}\n\n\t@Configuration\n\t@EnableConfigurationProperties(LocalDeployerProperties.class)\n\tpublic static class Config {\n\n\t\t@Bean\n\t\tpublic TaskLauncher taskLauncher(LocalDeployerProperties properties) {\n\t\t\treturn new LocalTaskLauncher(properties);\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "spring-cloud-deployer-local/src/test/java/org/springframework/cloud/deployer/spi/local/RandomPortRangeContextTests.java",
    "content": "/*\n * Copyright 2018-2025 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage org.springframework.cloud.deployer.spi.local;\n\nimport org.junit.jupiter.api.Test;\n\nimport org.springframework.boot.autoconfigure.AutoConfigurations;\nimport org.springframework.boot.test.context.runner.ApplicationContextRunner;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * @author Christian Tzolov\n */\npublic class RandomPortRangeContextTests {\n\n\tprivate final ApplicationContextRunner contextRunner = new ApplicationContextRunner()\n\t\t\t.withConfiguration(AutoConfigurations.of(LocalDeployerAutoConfiguration.class));\n\n\t@Test\n\tpublic void defaultProtRangeProperties() {\n\t\tthis.contextRunner\n\t\t\t\t.withUserConfiguration(LocalDeployerAutoConfiguration.class)\n\t\t\t\t.run((context) -> {\n\t\t\t\t\tassertThat(context).hasSingleBean(LocalDeployerProperties.class);\n\t\t\t\t\tassertThat(context).hasBean(\"localDeployerAutoConfiguration\");\n\t\t\t\t\tassertThat(context).getBean(LocalDeployerProperties.class)\n\t\t\t\t\t\t\t.hasFieldOrPropertyWithValue(\"portRange.low\", 20000);\n\t\t\t\t\tassertThat(context).getBean(LocalDeployerProperties.class)\n\t\t\t\t\t\t\t.hasFieldOrPropertyWithValue(\"portRange.high\", 61000);\n\t\t\t\t});\n\t}\n\n\t@Test\n\tpublic void presetProtRangeProperties() {\n\t\tthis.contextRunner\n\t\t\t\t.withUserConfiguration(LocalDeployerAutoConfiguration.class)\n\t\t\t\t.withPropertyValues(\"spring.cloud.deployer.local.portRange.low=20001\", \"spring.cloud.deployer.local.portRange.high=20003\")\n\t\t\t\t.run((context) -> {\n\t\t\t\t\tassertThat(context).hasSingleBean(LocalDeployerProperties.class);\n\t\t\t\t\tassertThat(context).hasBean(\"localDeployerAutoConfiguration\");\n\t\t\t\t\tassertThat(context).getBean(LocalDeployerProperties.class)\n\t\t\t\t\t\t\t.hasFieldOrPropertyWithValue(\"portRange.low\", 20001);\n\t\t\t\t\tassertThat(context).getBean(LocalDeployerProperties.class)\n\t\t\t\t\t\t\t.hasFieldOrPropertyWithValue(\"portRange.high\", 20003);\n\t\t\t\t});\n\t}\n}\n"
  },
  {
    "path": "spring-cloud-deployer-local/src/test/java/org/springframework/cloud/deployer/spi/local/RandomPortRangeTests.java",
    "content": "/*\n * Copyright 2018 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage org.springframework.cloud.deployer.spi.local;\n\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.mockito.Mock;\nimport org.mockito.junit.jupiter.MockitoExtension;\n\nimport org.springframework.cloud.deployer.resource.docker.DockerResource;\nimport org.springframework.cloud.deployer.spi.core.AppDeploymentRequest;\nimport org.springframework.core.io.ClassPathResource;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.mockito.Mockito.when;\n\n/**\n * @author Christian Tzolov\n */\n@ExtendWith(MockitoExtension.class)\npublic class RandomPortRangeTests {\n\n\tprivate AbstractLocalDeployerSupport localDeployerSupport;\n\n\t@Mock\n\tAppDeploymentRequest appDeploymentRequest;\n\n\t@BeforeEach\n\tpublic void setUp() {\n\t\tLocalDeployerProperties properties = new LocalDeployerProperties();\n\t\tproperties.getPortRange().setLow(30001);\n\t\tproperties.getPortRange().setHigh(30213);\n\n\t\tproperties.getDocker().getPortRange().setLow(40001);\n\t\tproperties.getDocker().getPortRange().setHigh(40213);\n\n\t\tlocalDeployerSupport = new AbstractLocalDeployerSupport(properties) {};\n\n\t}\n\n\t@Test\n\tpublic void portTests() {\n\t\twhen(appDeploymentRequest.getResource()).thenReturn(new ClassPathResource(\"\"));\n\t\tfor (int i = 0; i < 30; i++) {\n\t\t\tint port = localDeployerSupport.getRandomPort(appDeploymentRequest);\n\t\t\tassertThat(port).isGreaterThanOrEqualTo(30001);\n\t\t\tassertThat(port).isLessThanOrEqualTo(30213 + 5);\n\t\t}\n\t}\n\n\t@Test\n\tpublic void portTests2() {\n\t\twhen(appDeploymentRequest.getResource()).thenReturn(new DockerResource(\"/test:test\"));\n\t\tfor (int i = 0; i < 5; i++) {\n\t\t\tint port = localDeployerSupport.getRandomPort(appDeploymentRequest);\n\t\t\tassertThat(port).isGreaterThanOrEqualTo(40001);\n\t\t\tassertThat(port).isLessThanOrEqualTo(40213 + 5);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "spring-cloud-deployer-local/src/test/java/org/springframework/cloud/deployer/spi/local/RandomPortTests.java",
    "content": "/*\n * Copyright 2018 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage org.springframework.cloud.deployer.spi.local;\n\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.mockito.Mock;\nimport org.mockito.junit.jupiter.MockitoExtension;\n\nimport org.springframework.cloud.deployer.spi.core.AppDeploymentRequest;\nimport org.springframework.core.io.ClassPathResource;\n\nimport static org.mockito.Mockito.when;\n\n/**\n * @author Mark Pollack\n */\n@ExtendWith(MockitoExtension.class)\npublic class RandomPortTests {\n\n\tprivate AbstractLocalDeployerSupport localDeployerSupport;\n\n\t@Mock\n\tAppDeploymentRequest appDeploymentRequest;\n\n\t@BeforeEach\n\tpublic void setUp() {\n\t\tlocalDeployerSupport = new AbstractLocalDeployerSupport(new LocalDeployerProperties()) {};\n\t\twhen(appDeploymentRequest.getResource()).thenReturn(new ClassPathResource(\"\"));\n\t}\n\n\t@Test\n\tpublic void portTests() {\n\t\t//No exception should be thrown\n\t\tfor (int i = 0; i < 100; i++) {\n\t\t\tlocalDeployerSupport.getRandomPort(appDeploymentRequest);\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "spring-cloud-deployer-local/src/test/resources/testResource.txt",
    "content": ""
  },
  {
    "path": "spring-cloud-deployer-resource-docker/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd\">\n\t<modelVersion>4.0.0</modelVersion>\n\n\t<artifactId>spring-cloud-deployer-resource-docker</artifactId>\n\t<packaging>jar</packaging>\n\t<name>Spring Cloud Deployer Resource Docker</name>\n\n\t<parent>\n\t\t<groupId>org.springframework.cloud</groupId>\n\t\t<artifactId>spring-cloud-deployer-parent</artifactId>\n\t\t<version>3.0.0-SNAPSHOT</version>\n\t\t<relativePath>..</relativePath>\n\t</parent>\n\n\t<dependencies>\n\t\t<dependency>\n\t\t\t<groupId>org.springframework</groupId>\n\t\t\t<artifactId>spring-core</artifactId>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.junit.jupiter</groupId>\n\t\t\t<artifactId>junit-jupiter</artifactId>\n\t\t\t<scope>test</scope>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.assertj</groupId>\n\t\t\t<artifactId>assertj-core</artifactId>\n\t\t\t<scope>test</scope>\n\t\t</dependency>\n\t</dependencies>\n\n</project>\n"
  },
  {
    "path": "spring-cloud-deployer-resource-docker/src/main/java/org/springframework/cloud/deployer/resource/docker/DockerResource.java",
    "content": "/*\n * Copyright 2016 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.resource.docker;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.net.URI;\n\nimport org.springframework.core.io.AbstractResource;\nimport org.springframework.core.io.Resource;\nimport org.springframework.util.Assert;\n\n/**\n * A {@link Resource} implementation for resolving a Docker image.\n *\n * Note: {@link #getInputStream()} throws {@code UnsupportedOperationException}.\n *\n * @author Thomas Risberg\n */\npublic class DockerResource extends AbstractResource {\n\n\tpublic final static String URI_SCHEME = \"docker\";\n\n\tprivate URI uri;\n\n\t/**\n\t * Create a new {@code DockerResource} from an image name.\n\t * @param imageName the name of the image in a docker registry.\n\t */\n\tpublic DockerResource(String imageName) {\n\t\tAssert.hasText(imageName, \"An image name is required\");\n\t\tthis.uri = URI.create(URI_SCHEME + \":\" + imageName);\n\t}\n\n\t/**\n\t * Create a new {@code DockerResource} from a URI\n\t * @param uri a URI\n\t */\n\tpublic DockerResource(URI uri) {\n\t\tAssert.notNull(uri, \"A URI is required\");\n\t\tAssert.isTrue(\"docker\".equals(uri.getScheme()), \"A 'docker' scheme is required\");\n\t\tthis.uri = uri;\n\t}\n\n\n\t@Override\n\tpublic String getDescription() {\n\t\treturn \"Docker Resource [\" + uri + \"]\";\n\t}\n\n\t/**\n\t * This implementation currently throws {@code UnsupportedOperationException}\n\t */\n\t@Override\n\tpublic InputStream getInputStream() throws IOException {\n\t\tthrow new UnsupportedOperationException(\"getInputStream not supported\");\n\t}\n\n\t@Override\n\tpublic URI getURI() throws IOException {\n\t\treturn uri;\n\t}\n\n}\n"
  },
  {
    "path": "spring-cloud-deployer-resource-docker/src/main/java/org/springframework/cloud/deployer/resource/docker/DockerResourceLoader.java",
    "content": "/*\n * Copyright 2016 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.resource.docker;\n\nimport org.springframework.core.io.Resource;\nimport org.springframework.core.io.ResourceLoader;\nimport org.springframework.util.Assert;\nimport org.springframework.util.ClassUtils;\n\n/**\n * A {@link ResourceLoader} that loads {@link DockerResource}s from locations of the format\n * {@literal docker:<repository>:<tag>} where the value for \"repository:tag\" conforms to the rules\n * for referencing Docker images.\n *\n * @author Thomas Risberg\n */\npublic class DockerResourceLoader  implements ResourceLoader {\n\n\tprivate final ClassLoader classLoader = ClassUtils.getDefaultClassLoader();\n\n\t/**\n\t * Returns a {@link DockerResource} for the provided location.\n\t *\n\t * @param location the image location. May optionally be preceded by {@value DockerResource#URI_SCHEME}\n\t * followed by a colon, e.g. {@literal docker:springcloud/app-name:tag}\n\t * @return the {@link DockerResource}\n\t */\n\t@Override\n\tpublic Resource getResource(String location) {\n\t\tAssert.hasText(location, \"image location is required\");\n\t\tString image = location.replaceFirst(DockerResource.URI_SCHEME + \":\\\\/*\", \"\");\n\t\treturn new DockerResource(image);\n\t}\n\n\t/**\n\t * Returns the {@link ClassLoader} for this ResourceLoader.\n\t */\n\t@Override\n\tpublic ClassLoader getClassLoader() {\n\t\treturn this.classLoader;\n\t}\n\n}\n"
  },
  {
    "path": "spring-cloud-deployer-resource-docker/src/test/java/org/springframework/cloud/deployer/resource/docker/DockerResourceLoaderTests.java",
    "content": "/*\n * Copyright 2016 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.resource.docker;\n\nimport java.io.IOException;\n\nimport org.springframework.core.io.Resource;\n\nimport org.junit.jupiter.api.Test;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\n/**\n * Tests for the {@link DockerResourceLoader}.\n *\n * @author Thomas Risberg\n */\npublic class DockerResourceLoaderTests {\n\n\t@Test\n\tpublic void verifyImageUri() throws IOException {\n\t\tString location = \"docker:springcloud/test-app:v1\";\n\t\tDockerResourceLoader loader = new DockerResourceLoader();\n\t\tResource resource = loader.getResource(location);\n\t\tassertEquals(DockerResource.class, resource.getClass());\n\t\tDockerResource dockerResource = (DockerResource) resource;\n\t\tassertEquals(location, dockerResource.getURI().toString());\n\t\tassertEquals(\"springcloud/test-app:v1\", dockerResource.getURI().getSchemeSpecificPart());\n\t\tassertEquals(\"docker\", dockerResource.getURI().getScheme().toString());\n\t}\n\n\t@Test\n\tpublic void verifyImageUriWithSlashes() throws IOException {\n\t\tString location = \"docker://springcloud/test-app:v1\";\n\t\tDockerResourceLoader loader = new DockerResourceLoader();\n\t\tResource resource = loader.getResource(location);\n\t\tassertEquals(DockerResource.class, resource.getClass());\n\t\tDockerResource dockerResource = (DockerResource) resource;\n\t\tassertEquals(\"docker:springcloud/test-app:v1\", dockerResource.getURI().toString());\n\t\tassertEquals(\"springcloud/test-app:v1\", dockerResource.getURI().getSchemeSpecificPart());\n\t\tassertEquals(\"docker\", dockerResource.getURI().getScheme().toString());\n\t}\n\n\t@Test\n\tpublic void verifyImageUriWithoutPrefix() throws IOException {\n\t\tString location = \"springcloud/test-app:v1\";\n\t\tDockerResourceLoader loader = new DockerResourceLoader();\n\t\tResource resource = loader.getResource(location);\n\t\tassertEquals(DockerResource.class, resource.getClass());\n\t\tDockerResource dockerResource = (DockerResource) resource;\n\t\tassertEquals(DockerResource.URI_SCHEME + \":\" + location, dockerResource.getURI().toString());\n\t\tassertEquals(\"springcloud/test-app:v1\", dockerResource.getURI().getSchemeSpecificPart());\n\t\tassertEquals(\"docker\", dockerResource.getURI().getScheme().toString());\n\t}\n\n}\n"
  },
  {
    "path": "spring-cloud-deployer-resource-docker/src/test/java/org/springframework/cloud/deployer/resource/docker/DockerResourceTests.java",
    "content": "/*\n * Copyright 2016 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.resource.docker;\n\nimport java.io.IOException;\nimport java.net.URI;\nimport java.net.URISyntaxException;\n\nimport org.junit.jupiter.api.Test;\n\nimport static org.assertj.core.api.AssertionsForClassTypes.assertThatExceptionOfType;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\n/**\n * Tests for the {@link DockerResource}.\n *\n * @author Thomas Risberg\n */\npublic class DockerResourceTests {\n\n\tString image = \"sringcloud/hello-kube:latest\";\n\n\t@Test\n\tpublic void testResource() throws IOException, URISyntaxException {\n\t\tDockerResource r = new DockerResource(image);\n\t\tassertEquals(image, r.getURI().getSchemeSpecificPart());\n\t}\n\n\t@Test\n\tpublic void testUri() throws IOException, URISyntaxException {\n\t\tDockerResource r = new DockerResource(URI.create(DockerResource.URI_SCHEME + \":\" + image));\n\t\tassertEquals(image, r.getURI().getSchemeSpecificPart());\n\t}\n\n\t@Test\n\tpublic void testInvalidUri() {\n\t\tassertThatExceptionOfType(IllegalArgumentException.class).isThrownBy(() -> {\n\t\t\tDockerResource r = new DockerResource(URI.create(\"http:\" + image));\n\t\t});\n\t}\n}\n"
  },
  {
    "path": "spring-cloud-deployer-resource-maven/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd\">\n\t<modelVersion>4.0.0</modelVersion>\n\n\t<artifactId>spring-cloud-deployer-resource-maven</artifactId>\n\t<packaging>jar</packaging>\n\t<name>Spring Cloud Deployer Resource Maven</name>\n\n\t<parent>\n\t\t<groupId>org.springframework.cloud</groupId>\n\t\t<artifactId>spring-cloud-deployer-parent</artifactId>\n\t\t<version>3.0.0-SNAPSHOT</version>\n\t\t<relativePath>..</relativePath>\n\t</parent>\n\n\t<dependencies>\n\t\t<dependency>\n\t\t\t<groupId>org.springframework</groupId>\n\t\t\t<artifactId>spring-core</artifactId>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.apache.maven</groupId>\n\t\t\t<artifactId>maven-model-builder</artifactId>\n\t\t\t<version>${maven.version}</version>\n\t\t\t<!-- @TODO boot3 remove when updated -->\n\t\t\t<exclusions>\n\t\t\t\t<exclusion>\n\t\t\t\t\t<groupId>javax.inject</groupId>\n\t\t\t\t\t<artifactId>javax.inject</artifactId>\n\t\t\t\t</exclusion>\n\t\t\t</exclusions>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.apache.maven</groupId>\n\t\t\t<artifactId>maven-resolver-provider</artifactId>\n\t\t\t<version>${maven.version}</version>\n\t\t\t<!-- @TODO boot3 remove when updated -->\n\t\t\t<exclusions>\n\t\t\t\t<exclusion>\n\t\t\t\t\t<groupId>javax.inject</groupId>\n\t\t\t\t\t<artifactId>javax.inject</artifactId>\n\t\t\t\t</exclusion>\n\t\t\t</exclusions>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.apache.maven.resolver</groupId>\n\t\t\t<artifactId>maven-resolver-connector-basic</artifactId>\n\t\t\t<version>${maven-resolver.version}</version>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.apache.maven.resolver</groupId>\n\t\t\t<artifactId>maven-resolver-transport-file</artifactId>\n\t\t\t<version>${maven-resolver.version}</version>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.apache.maven.resolver</groupId>\n\t\t\t<artifactId>maven-resolver-transport-http</artifactId>\n\t\t\t<version>${maven-resolver.version}</version>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.apache.maven.resolver</groupId>\n\t\t\t<artifactId>maven-resolver-transport-wagon</artifactId>\n\t\t\t<version>${maven-resolver.version}</version>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.apache.maven.resolver</groupId>\n\t\t\t<artifactId>maven-resolver-impl</artifactId>\n\t\t\t<version>${maven-resolver.version}</version>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>commons-io</groupId>\n\t\t\t<artifactId>commons-io</artifactId>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.apache.maven.wagon</groupId>\n\t\t\t<artifactId>wagon-http</artifactId>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.slf4j</groupId>\n\t\t\t<artifactId>slf4j-api</artifactId>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.springframework.boot</groupId>\n\t\t\t<artifactId>spring-boot-starter-web</artifactId>\n\t\t\t<scope>test</scope>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.springframework.boot</groupId>\n\t\t\t<artifactId>spring-boot-starter-test</artifactId>\n\t\t\t<scope>test</scope>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.springframework.security</groupId>\n\t\t\t<artifactId>spring-security-web</artifactId>\n\t\t\t<scope>test</scope>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.springframework.security</groupId>\n\t\t\t<artifactId>spring-security-config</artifactId>\n\t\t\t<scope>test</scope>\n\t\t</dependency>\n\t</dependencies>\n\n</project>\n"
  },
  {
    "path": "spring-cloud-deployer-resource-maven/src/main/java/org/springframework/cloud/deployer/resource/maven/LoggingRepositoryListener.java",
    "content": "/*\n * Copyright 2019 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage org.springframework.cloud.deployer.resource.maven;\n\nimport org.eclipse.aether.AbstractRepositoryListener;\nimport org.eclipse.aether.RepositoryEvent;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * @author Corneil du Plessis\n */\npublic class LoggingRepositoryListener extends AbstractRepositoryListener {\n\n\tprivate static final Logger logger = LoggerFactory.getLogger(LoggingRepositoryListener.class);\n\n\tpublic void artifactDeployed(RepositoryEvent event) {\n\t\tprintln(\"artifactDeployed\", event.getArtifact() + \" to \" + event.getRepository());\n\t}\n\n\tpublic void artifactDeploying(RepositoryEvent event) {\n\t\tprintln(\"artifactDeploying\", event.getArtifact() + \" to \" + event.getRepository());\n\t}\n\n\tpublic void artifactDescriptorInvalid(RepositoryEvent event) {\n\t\tprintln(\"artifactDescriptorInvalid\", \"for \" + event.getArtifact() + \": \" + event.getException().getMessage());\n\t}\n\n\tpublic void artifactDescriptorMissing(RepositoryEvent event) {\n\t\tprintln(\"artifactDescriptorMissing\", \"for \" + event.getArtifact());\n\t}\n\n\tpublic void artifactInstalled(RepositoryEvent event) {\n\t\tprintln(\"artifactInstalled\", event.getArtifact() + \" to \" + event.getFile());\n\t}\n\n\tpublic void artifactInstalling(RepositoryEvent event) {\n\t\tprintln(\"artifactInstalling\", event.getArtifact() + \" to \" + event.getFile());\n\t}\n\n\tpublic void artifactResolved(RepositoryEvent event) {\n\t\tprintln(\"artifactResolved\", event.getArtifact() + \" from \" + event.getRepository());\n\t}\n\n\tpublic void artifactDownloading(RepositoryEvent event) {\n\t\tprintln(\"artifactDownloading\", event.getArtifact() + \" from \" + event.getRepository());\n\t}\n\n\tpublic void artifactDownloaded(RepositoryEvent event) {\n\t\tprintln(\"artifactDownloaded\", event.getArtifact() + \" from \" + event.getRepository());\n\t}\n\n\tpublic void artifactResolving(RepositoryEvent event) {\n\t\tprintln(\"artifactResolving\", event.getArtifact().toString());\n\t}\n\n\tpublic void metadataDeployed(RepositoryEvent event) {\n\t\tprintln(\"metadataDeployed\", event.getMetadata() + \" to \" + event.getRepository());\n\t}\n\n\tpublic void metadataDeploying(RepositoryEvent event) {\n\t\tprintln(\"metadataDeploying\", event.getMetadata() + \" to \" + event.getRepository());\n\t}\n\n\tpublic void metadataInstalled(RepositoryEvent event) {\n\t\tprintln(\"metadataInstalled\", event.getMetadata() + \" to \" + event.getFile());\n\t}\n\n\tpublic void metadataInstalling(RepositoryEvent event) {\n\t\tprintln(\"metadataInstalling\", event.getMetadata() + \" to \" + event.getFile());\n\t}\n\n\tpublic void metadataInvalid(RepositoryEvent event) {\n\t\tprintln(\"metadataInvalid\", event.getMetadata().toString());\n\t}\n\n\tpublic void metadataResolved(RepositoryEvent event) {\n\t\tprintln(\"metadataResolved\", event.getMetadata() + \" from \" + event.getRepository());\n\t}\n\n\tpublic void metadataResolving(RepositoryEvent event) {\n\t\tprintln(\"metadataResolving\", event.getMetadata() + \" from \" + event.getRepository());\n\t}\n\n\tprivate void println(String event, String message) {\n\t\tlogger.info(\"Aether Repository - \" + event + \": \" + message);\n\t}\n}\n"
  },
  {
    "path": "spring-cloud-deployer-resource-maven/src/main/java/org/springframework/cloud/deployer/resource/maven/MavenArtifactResolver.java",
    "content": "/*\n * Copyright 2015-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.resource.maven;\n\nimport java.io.File;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.LinkedHashMap;\nimport java.util.LinkedList;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Map.Entry;\nimport java.util.stream.Collectors;\n\nimport org.apache.maven.repository.internal.MavenRepositorySystemUtils;\nimport org.eclipse.aether.ConfigurationProperties;\nimport org.eclipse.aether.DefaultRepositorySystemSession;\nimport org.eclipse.aether.RepositorySystem;\nimport org.eclipse.aether.RepositorySystemSession;\nimport org.eclipse.aether.artifact.Artifact;\nimport org.eclipse.aether.artifact.DefaultArtifact;\nimport org.eclipse.aether.connector.basic.BasicRepositoryConnectorFactory;\nimport org.eclipse.aether.impl.DefaultServiceLocator;\nimport org.eclipse.aether.repository.Authentication;\nimport org.eclipse.aether.repository.AuthenticationContext;\nimport org.eclipse.aether.repository.AuthenticationDigest;\nimport org.eclipse.aether.repository.LocalRepository;\nimport org.eclipse.aether.repository.Proxy;\nimport org.eclipse.aether.repository.RemoteRepository;\nimport org.eclipse.aether.repository.RepositoryPolicy;\nimport org.eclipse.aether.resolution.ArtifactRequest;\nimport org.eclipse.aether.resolution.ArtifactResolutionException;\nimport org.eclipse.aether.resolution.ArtifactResult;\nimport org.eclipse.aether.resolution.VersionRangeRequest;\nimport org.eclipse.aether.resolution.VersionRangeResolutionException;\nimport org.eclipse.aether.resolution.VersionRangeResult;\nimport org.eclipse.aether.spi.connector.RepositoryConnectorFactory;\nimport org.eclipse.aether.spi.connector.transport.TransporterFactory;\nimport org.eclipse.aether.transport.file.FileTransporterFactory;\nimport org.eclipse.aether.transport.http.HttpTransporterFactory;\nimport org.eclipse.aether.transport.wagon.WagonConfigurator;\nimport org.eclipse.aether.transport.wagon.WagonProvider;\nimport org.eclipse.aether.transport.wagon.WagonTransporterFactory;\nimport org.eclipse.aether.util.artifact.JavaScopes;\nimport org.eclipse.aether.util.repository.DefaultProxySelector;\nimport org.eclipse.aether.version.Version;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport org.springframework.core.io.FileSystemResource;\nimport org.springframework.core.io.Resource;\nimport org.springframework.util.Assert;\n\n/**\n * Resolves a {@link MavenResource} using <a href=\"https://www.eclipse.org/aether/>aether</a> to\n * locate the artifact (uber jar) in a local Maven repository, downloading the latest update from a\n * remote repository if necessary.\n * <p>A set of default remote repos (Maven Central, Spring Snapshots, Spring Milestones) will be automatically added to\n * the head of the list of remote repos. If the default repo is already explicitly configured (exact match on the repo url)\n * then that particular default will be omitted. To skip the automatic default repos behavior altogether, set the\n * {@link MavenProperties#isIncludeDefaultRemoteRepos()} property to {@code false}.\n *\n * @author David Turanski\n * @author Mark Fisher\n * @author Marius Bogoevici\n * @author Ilayaperumal Gopinathan\n * @author Donovan Muller\n * @author Corneil du Plessis\n * @author Chris Bono\n */\nclass MavenArtifactResolver {\n\n\tprivate static final Logger logger = LoggerFactory.getLogger(MavenArtifactResolver.class);\n\n\tprivate static final String DEFAULT_CONTENT_TYPE = \"default\";\n\n\tprivate final RepositorySystem repositorySystem;\n\n\tprivate final MavenProperties properties;\n\n\tprivate final List<RemoteRepository> remoteRepositories = new LinkedList<>();\n\n\tprivate final Authentication proxyAuthentication;\n\n\t/**\n\t * Create an instance using the provided properties.\n\t *\n\t * @param properties the properties for the maven repositories, proxies, and authentication\n\t */\n\tpublic MavenArtifactResolver(MavenProperties properties) {\n\t\tAssert.notNull(properties, \"MavenProperties must not be null\");\n\t\tAssert.notNull(properties.getLocalRepository(), \"Local repository path cannot be null\");\n\t\tthis.properties = properties;\n\t\tif (logger.isDebugEnabled()) {\n\t\t\tlogger.debug(\"Configured local repository: \" + properties.getLocalRepository());\n\t\t\tlogger.debug(\"Configured remote repositories: \" + configuredRemoteRepositoriesDescription());\n\t\t}\n\t\tif (isProxyEnabled() && proxyHasCredentials()) {\n\t\t\tfinal String username = this.properties.getProxy().getAuth().getUsername();\n\t\t\tfinal String password = this.properties.getProxy().getAuth().getPassword();\n\t\t\tthis.proxyAuthentication = newAuthentication(username, password);\n\t\t}\n\t\telse {\n\t\t\tthis.proxyAuthentication = null;\n\t\t}\n\t\tFile localRepository = new File(this.properties.getLocalRepository());\n\t\tif (!localRepository.exists()) {\n\t\t\tboolean created = localRepository.mkdirs();\n\t\t\t// May have been created by another thread after above check. Double check.\n\t\t\tAssert.isTrue(created || localRepository.exists(),\n\t\t\t\t\t\"Unable to create directory for local repository: \" + localRepository);\n\t\t}\n\n\t\tMap<String, String> defaultRepoUrlsToIds = defaultRemoteRepos();\n\n\t\tfor (Map.Entry<String, MavenProperties.RemoteRepository> entry : this.properties.getRemoteRepositories()\n\t\t\t\t.entrySet()) {\n\t\t\tMavenProperties.RemoteRepository remoteRepository = entry.getValue();\n\t\t\tRemoteRepository.Builder remoteRepositoryBuilder = new RemoteRepository.Builder(\n\t\t\t\t\tentry.getKey(), DEFAULT_CONTENT_TYPE, remoteRepository.getUrl());\n\t\t\t// Update policies when set.\n\t\t\tif (remoteRepository.getPolicy() != null) {\n\t\t\t\tremoteRepositoryBuilder.setPolicy(new RepositoryPolicy(remoteRepository.getPolicy().isEnabled(),\n\t\t\t\t\t\tremoteRepository.getPolicy().getUpdatePolicy(),\n\t\t\t\t\t\tremoteRepository.getPolicy().getChecksumPolicy()));\n\t\t\t}\n\t\t\tif (remoteRepository.getReleasePolicy() != null) {\n\t\t\t\tremoteRepositoryBuilder\n\t\t\t\t\t\t.setReleasePolicy(new RepositoryPolicy(remoteRepository.getReleasePolicy().isEnabled(),\n\t\t\t\t\t\t\t\tremoteRepository.getReleasePolicy().getUpdatePolicy(),\n\t\t\t\t\t\t\t\tremoteRepository.getReleasePolicy().getChecksumPolicy()));\n\t\t\t}\n\t\t\tif (remoteRepository.getSnapshotPolicy() != null) {\n\t\t\t\tremoteRepositoryBuilder\n\t\t\t\t\t\t.setSnapshotPolicy(new RepositoryPolicy(remoteRepository.getSnapshotPolicy().isEnabled(),\n\t\t\t\t\t\t\t\tremoteRepository.getSnapshotPolicy().getUpdatePolicy(),\n\t\t\t\t\t\t\t\tremoteRepository.getSnapshotPolicy().getChecksumPolicy()));\n\t\t\t}\n\t\t\tif (remoteRepositoryHasCredentials(remoteRepository)) {\n\t\t\t\tfinal String username = remoteRepository.getAuth().getUsername();\n\t\t\t\tfinal String password = remoteRepository.getAuth().getPassword();\n\t\t\t\tremoteRepositoryBuilder.setAuthentication(newAuthentication(username, password));\n\t\t\t}\n\t\t\t// do not add default repo if explicitly configured\n\t\t\tdefaultRepoUrlsToIds.remove(remoteRepository.getUrl());\n\n\t\t\tRemoteRepository repo = proxyRepoIfProxyEnabled(remoteRepositoryBuilder.build());\n\t\t\tthis.remoteRepositories.add(repo);\n\t\t}\n\n\t\tif (!defaultRepoUrlsToIds.isEmpty() && this.properties.isIncludeDefaultRemoteRepos()) {\n\t\t\tList<RemoteRepository> defaultRepos = new ArrayList<>();\n\t\t\tdefaultRepoUrlsToIds.forEach((url, id) -> {\n\t\t\t\tif (logger.isDebugEnabled()) {\n\t\t\t\t\tlogger.debug(\"Adding {} ({}) to remote repositories list\", id, url);\n\t\t\t\t}\n\t\t\t\tRemoteRepository defaultRepo = proxyRepoIfProxyEnabled(new RemoteRepository.Builder(id, DEFAULT_CONTENT_TYPE, url).build());\n\t\t\t\tdefaultRepos.add(defaultRepo);\n\t\t\t});\n\t\t\tthis.remoteRepositories.addAll(0, defaultRepos);\n\t\t}\n\t\tif (logger.isDebugEnabled()) {\n\t\t\tlogger.debug(\"Using remote repositories: {}\", actualRemoteRepositoriesDescription());\n\t\t}\n\t\tthis.repositorySystem = newRepositorySystem();\n\t}\n\n\t/**\n\t * Gets the default repos to automatically add.\n\t * @return map of default repos (repo url to repo id)\n\t */\n\tprotected Map<String, String> defaultRemoteRepos() {\n\t\tMap<String, String> defaultRepos = new LinkedHashMap<>();\n\t\tdefaultRepos.put(\"https://repo.maven.apache.org/maven2\", \"mavenCentral-default\");\n\t\tdefaultRepos.put(\"https://repo.spring.io/snapshot\", \"springSnapshot-default\");\n\t\tdefaultRepos.put(\"https://repo.spring.io/milestone\", \"springMilestone-default\");\n\t\treturn defaultRepos;\n\t}\n\n\tprivate RemoteRepository proxyRepoIfProxyEnabled(RemoteRepository remoteRepo) {\n\t\tif (!isProxyEnabled()) {\n\t\t\treturn remoteRepo;\n\t\t}\n\t\tProxy proxy;\n\t\tMavenProperties.Proxy proxyProperties = this.properties.getProxy();\n\t\tif (this.proxyAuthentication != null) {\n\t\t\tproxy = new Proxy(\n\t\t\t\t\tproxyProperties.getProtocol(),\n\t\t\t\t\tproxyProperties.getHost(),\n\t\t\t\t\tproxyProperties.getPort(),\n\t\t\t\t\tthis.proxyAuthentication);\n\t\t}\n\t\telse {\n\t\t\t// if proxy does not require authentication\n\t\t\tproxy = new Proxy(\n\t\t\t\t\tproxyProperties.getProtocol(),\n\t\t\t\t\tproxyProperties.getHost(),\n\t\t\t\t\tproxyProperties.getPort());\n\t\t}\n\t\tDefaultProxySelector proxySelector = new DefaultProxySelector();\n\t\tproxySelector.add(proxy, this.properties.getProxy().getNonProxyHosts());\n\t\tproxy = proxySelector.getProxy(remoteRepo);\n\n\t\tRemoteRepository.Builder remoteRepositoryBuilder = new RemoteRepository.Builder(remoteRepo);\n\t\tremoteRepositoryBuilder.setProxy(proxy);\n\t\treturn remoteRepositoryBuilder.build();\n\t}\n\n\t/**\n\t * Check if the proxy settings are provided.\n\t *\n\t * @return boolean true if the proxy settings are provided.\n\t */\n\tprivate boolean isProxyEnabled() {\n\t\treturn (this.properties.getProxy() != null &&\n\t\t\t\tthis.properties.getProxy().getHost() != null &&\n\t\t\t\tthis.properties.getProxy().getPort() > 0);\n\t}\n\n\t/**\n\t * Check if the proxy setting has username/password set.\n\t *\n\t * @return boolean true if both the username/password are set\n\t */\n\tprivate boolean proxyHasCredentials() {\n\t\treturn (this.properties.getProxy() != null &&\n\t\t\t\tthis.properties.getProxy().getAuth() != null &&\n\t\t\t\tthis.properties.getProxy().getAuth().getUsername() != null &&\n\t\t\t\tthis.properties.getProxy().getAuth().getPassword() != null);\n\t}\n\n\t/**\n\t * Check if the {@link MavenProperties.RemoteRepository} setting has username/password set.\n\t *\n\t * @return boolean true if both the username/password are set\n\t */\n\tprivate boolean remoteRepositoryHasCredentials(MavenProperties.RemoteRepository remoteRepository) {\n\t\treturn remoteRepository != null &&\n\t\t\t\tremoteRepository.getAuth() != null &&\n\t\t\t\tremoteRepository.getAuth().getUsername() != null &&\n\t\t\t\tremoteRepository.getAuth().getPassword() != null;\n\t}\n\n\t/**\n\t * Create an {@link Authentication} given a username/password\n\t *\n\t * @param username\n\t * @param password\n\t * @return a configured {@link Authentication}\n\t */\n\tprivate Authentication newAuthentication(final String username, final String password) {\n\t\treturn new Authentication() {\n\n\t\t\t@Override\n\t\t\tpublic void fill(AuthenticationContext context, String key, Map<String, String> data) {\n\t\t\t\tcontext.put(AuthenticationContext.USERNAME, username);\n\t\t\t\tcontext.put(AuthenticationContext.PASSWORD, password);\n\t\t\t}\n\n\t\t\t@Override\n\t\t\tpublic void digest(AuthenticationDigest digest) {\n\t\t\t\tdigest.update(AuthenticationContext.USERNAME, username,\n\t\t\t\t\t\tAuthenticationContext.PASSWORD, password);\n\t\t\t}\n\t\t};\n\t}\n\n\tDefaultRepositorySystemSession newRepositorySystemSession() {\n\t\treturn this.newRepositorySystemSession(this.repositorySystem, this.properties.getLocalRepository());\n\t}\n\n\t/*\n\t * Create a session to manage remote and local synchronization.\n\t */\n\tprivate DefaultRepositorySystemSession newRepositorySystemSession(RepositorySystem system, String localRepoPath) {\n\t\tDefaultRepositorySystemSession session = MavenRepositorySystemUtils.newSession();\n\t\tLocalRepository localRepo = new LocalRepository(localRepoPath);\n\t\tsession.setLocalRepositoryManager(system.newLocalRepositoryManager(session, localRepo));\n\t\tsession.setOffline(this.properties.isOffline());\n\t\tsession.setUpdatePolicy(this.properties.getUpdatePolicy());\n\t\tsession.setChecksumPolicy(this.properties.getChecksumPolicy());\n\t\tif (this.properties.isEnableRepositoryListener()) {\n\t\t\tsession.setRepositoryListener(new LoggingRepositoryListener());\n\t\t}\n\t\tif (this.properties.getConnectTimeout() != null) {\n\t\t\tsession.setConfigProperty(ConfigurationProperties.CONNECT_TIMEOUT, this.properties.getConnectTimeout());\n\t\t}\n\t\tif (this.properties.getRequestTimeout() != null) {\n\t\t\tsession.setConfigProperty(ConfigurationProperties.REQUEST_TIMEOUT, this.properties.getRequestTimeout());\n\t\t}\n\t\tif (isProxyEnabled()) {\n\t\t\tDefaultProxySelector proxySelector = new DefaultProxySelector();\n\t\t\tProxy proxy = new Proxy(this.properties.getProxy().getProtocol(),\n\t\t\t\t\tthis.properties.getProxy().getHost(),\n\t\t\t\t\tthis.properties.getProxy().getPort(),\n\t\t\t\t\tthis.proxyAuthentication);\n\t\t\tproxySelector.add(proxy, this.properties.getProxy().getNonProxyHosts());\n\t\t\tsession.setProxySelector(proxySelector);\n\t\t}\n\t\t// wagon configs\n\t\tfor (Entry<String, MavenProperties.RemoteRepository> entry : this.properties.getRemoteRepositories().entrySet()) {\n\t\t\tsession.setConfigProperty(\"aether.connector.wagon.config.\" + entry.getKey(), entry.getValue().getWagon());\n\t\t}\n\t\treturn session;\n\t}\n\n\t/*\n\t * Aether's components implement {@link org.eclipse.aether.spi.locator.Service} to ease manual wiring.\n\t * Using the prepopulated {@link DefaultServiceLocator}, we need to register the repository connector\n\t * and transporter factories\n\t */\n\tprivate RepositorySystem newRepositorySystem() {\n\t\tDefaultServiceLocator locator = MavenRepositorySystemUtils.newServiceLocator();\n\t\tlocator.addService(RepositoryConnectorFactory.class, BasicRepositoryConnectorFactory.class);\n\t\tlocator.addService(TransporterFactory.class, FileTransporterFactory.class);\n\n\t\tif (properties.isUseWagon()) {\n\t\t\tlocator.addService(WagonProvider.class, StaticWagonProvider.class);\n\t\t\tlocator.addService(WagonConfigurator.class, StaticWagonConfigurator.class);\n\t\t\tlocator.addService(TransporterFactory.class, WagonTransporterFactory.class);\n\t\t} else {\n\t\t\tlocator.addService(TransporterFactory.class, HttpTransporterFactory.class);\n\t\t}\n\n\t\tlocator.setErrorHandler(new DefaultServiceLocator.ErrorHandler() {\n\t\t\t@Override\n\t\t\tpublic void serviceCreationFailed(Class<?> type, Class<?> impl, Throwable exception) {\n\t\t\t\tthrow new RuntimeException(exception);\n\t\t\t}\n\t\t});\n\t\treturn locator.getService(RepositorySystem.class);\n\t}\n\n\t/**\n\t * Gets the list of configured remote repositories\n\t * @return unmodifiable list of configured remote repositories\n\t */\n\tList<RemoteRepository> remoteRepositories() {\n\t\treturn Collections.unmodifiableList(this.remoteRepositories);\n\t}\n\n\tprivate String actualRemoteRepositoriesDescription() {\n\t\treturn this.remoteRepositories.stream().map((repo) -> String.format(\"%s (%s)\", repo.getId(), repo.getUrl()))\n\t\t\t\t.collect(Collectors.joining(\", \", \"[\", \"]\"));\n\t}\n\n\tprivate String configuredRemoteRepositoriesDescription() {\n\t\treturn this.properties.getRemoteRepositories().entrySet().stream()\n\t\t\t\t.map((e) -> String.format(\"%s (%s)\", e.getKey(), e.getValue().getUrl()))\n\t\t\t\t.collect(Collectors.joining(\", \", \"[\", \"]\"));\n\t}\n\n\tList<String> getVersions(String coordinates) {\n\t\tArtifact artifact = new DefaultArtifact(coordinates);\n\t\tVersionRangeRequest rangeRequest = new VersionRangeRequest();\n\t\trangeRequest.setArtifact(artifact);\n\t\trangeRequest.setRepositories(this.remoteRepositories);\n\t\ttry {\n\t\t\tVersionRangeResult versionResult = this.repositorySystem.resolveVersionRange(newRepositorySystemSession(), rangeRequest);\n\t\t\tList<String> versions = new ArrayList<>();\n\t\t\tfor (Version version: versionResult.getVersions()) {\n\t\t\t\tversions.add(version.toString());\n\t\t\t}\n\t\t\treturn versions;\n\t\t}\n\t\tcatch (VersionRangeResolutionException e) {\n\t\t\tthrow new IllegalStateException(e);\n\t\t}\n\t}\n\n\t/**\n\t * Resolve an artifact and return its location in the local repository. Aether performs the normal\n\t * Maven resolution process ensuring that the latest update is cached to the local repository.\n\t * In addition, if the {@code MavenProperties.resolvePom} flag is <code>true</code>,\n\t * the POM is also resolved and cached.\n\t * @param resource the {@link MavenResource} representing the artifact\n\t * @return a {@link FileSystemResource} representing the resolved artifact in the local repository\n\t * @throws IllegalStateException if the artifact does not exist or the resolution fails\n\t */\n\tResource resolve(MavenResource resource) {\n\t\tAssert.notNull(resource, \"MavenResource must not be null\");\n\t\tvalidateCoordinates(resource);\n\t\tRepositorySystemSession session = newRepositorySystemSession(this.repositorySystem, this.properties.getLocalRepository());\n\t\ttry {\n\t\t\tList<ArtifactRequest> artifactRequests = new ArrayList<>(2);\n\t\t\tif (properties.isResolvePom()) {\n\t\t\t\tartifactRequests.add(new ArtifactRequest(toPomArtifact(resource), this.remoteRepositories, JavaScopes.RUNTIME));\n\t\t\t}\n\t\t\tartifactRequests.add(new ArtifactRequest(toJarArtifact(resource), this.remoteRepositories, JavaScopes.RUNTIME));\n\t\t\tList<ArtifactResult> results = this.repositorySystem.resolveArtifacts(session, artifactRequests);\n\t\t\treturn toResource(results.get(results.size() - 1));\n\t\t}\n\t\tcatch (ArtifactResolutionException ex) {\n\t\t\tString errorMsg = String.format(\"Failed to resolve %s using remote repo(s): %s\",\n\t\t\t\t\tresource, actualRemoteRepositoriesDescription());\n\t\t\tthrow new IllegalStateException(errorMsg, ex);\n\t\t}\n\t}\n\n\tprivate void validateCoordinates(MavenResource resource) {\n\t\tAssert.hasText(resource.getGroupId(), \"groupId must not be blank.\");\n\t\tAssert.hasText(resource.getArtifactId(), \"artifactId must not be blank.\");\n\t\tAssert.hasText(resource.getExtension(), \"extension must not be blank.\");\n\t\tAssert.hasText(resource.getVersion(), \"version must not be blank.\");\n\t}\n\n\tpublic FileSystemResource toResource(ArtifactResult resolvedArtifact) {\n\t\treturn new FileSystemResource(resolvedArtifact.getArtifact().getFile());\n\t}\n\n\tprivate Artifact toJarArtifact(MavenResource resource) {\n\t\treturn toArtifact(resource, resource.getExtension());\n\t}\n\n\tprivate Artifact toPomArtifact(MavenResource resource) {\n\t\treturn toArtifact(resource, \"pom\");\n\t}\n\n\tprivate Artifact toArtifact(MavenResource resource, String extension) {\n\t\treturn new DefaultArtifact(resource.getGroupId(),\n\t\t\t\tresource.getArtifactId(),\n\t\t\t\tresource.getClassifier() != null ? resource.getClassifier() : \"\",\n\t\t\t\textension,\n\t\t\t\tresource.getVersion());\n\t}\n}\n"
  },
  {
    "path": "spring-cloud-deployer-resource-maven/src/main/java/org/springframework/cloud/deployer/resource/maven/MavenProperties.java",
    "content": "/*\n * Copyright 2015-2017 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.resource.maven;\n\nimport java.io.File;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.TreeMap;\n\n/**\n * Configuration Properties for Maven.\n *\n * @author Ilayaperumal Gopinathan\n * @author Eric Bottard\n * @author Mark Fisher\n * @author Donovan Muller\n */\npublic class MavenProperties {\n\n\t/**\n\t * Default file path to a locally available maven repository.\n\t */\n\tprivate static String DEFAULT_LOCAL_REPO = System.getProperty(\"user.home\") +\n\t\t\tFile.separator + \".m2\" + File.separator + \"repository\";\n\n\t/**\n\t * Whether default remote repositories should be automatically included in the list of remote repositories.\n\t */\n\tprivate boolean includeDefaultRemoteRepos = true;\n\n\t/**\n\t * File path to a locally available maven repository, where artifacts will be downloaded.\n\t */\n\tprivate String localRepository = DEFAULT_LOCAL_REPO;\n\n\t/**\n\t * Locations of remote maven repositories from which artifacts will be downloaded, if not available locally.\n\t */\n\tprivate Map<String, RemoteRepository> remoteRepositories = new TreeMap<>();\n\n\t/**\n\t * Whether the resolver should operate in offline mode.\n\t */\n\tprivate boolean offline;\n\n\t/**\n\t * Proxy configuration properties.\n\t */\n\tprivate Proxy proxy;\n\n\t/**\n\t * The connect timeout. If <code>null</code>, the underlying default will be used.\n\t * @see {@link org.eclipse.aether.ConfigurationProperties#CONNECT_TIMEOUT}\n\t */\n\tprivate Integer connectTimeout;\n\n\t/**\n\t * The request timeout. If <code>null</code>, the underlying default will be used.\n\t * @see {@link org.eclipse.aether.ConfigurationProperties#REQUEST_TIMEOUT}\n\t */\n\tprivate Integer requestTimeout;\n\n\t/**\n\t * In addition to resolving the JAR artifact, if true, resolve the POM artifact.\n\t * This is consistent with the way that Maven resolves artifacts.\n\t */\n\tprivate boolean resolvePom;\n\n\t/**\n\t * Add the ConsoleRepositoryListener to the session for debugging of artifact resolution.\n\t */\n\tprivate boolean enableRepositoryListener = false;\n\n\tboolean isIncludeDefaultRemoteRepos() {\n\t\treturn includeDefaultRemoteRepos;\n\t}\n\n\tvoid setIncludeDefaultRemoteRepos(boolean includeDefaultRemoteRepos) {\n\t\tthis.includeDefaultRemoteRepos = includeDefaultRemoteRepos;\n\t}\n\n\t/**\n\t * Use maven wagon based transport for http based artifacts.\n\t */\n\tprivate boolean useWagon;\n\n\tpublic void setUseWagon(boolean useWagon) {\n\t\tthis.useWagon = useWagon;\n\t}\n\n\tpublic boolean isUseWagon() {\n\t\treturn useWagon;\n\t}\n\n\tpublic boolean isEnableRepositoryListener() {\n\t\treturn enableRepositoryListener;\n\t}\n\n\tpublic void setEnableRepositoryListener(boolean enableRepositoryListener) {\n\t\tthis.enableRepositoryListener = enableRepositoryListener;\n\t}\n\n\tpublic String updatePolicy;\n\n\tpublic String checksumPolicy;\n\n\tpublic String getUpdatePolicy() {\n\t\treturn updatePolicy;\n\t}\n\n\tpublic void setUpdatePolicy(String updatePolicy) {\n\t\tthis.updatePolicy = updatePolicy;\n\t}\n\n\tpublic String getChecksumPolicy() {\n\t\treturn checksumPolicy;\n\t}\n\n\tpublic void setChecksumPolicy(String checksumPolicy) {\n\t\tthis.checksumPolicy = checksumPolicy;\n\t}\n\n\tpublic Map<String, RemoteRepository> getRemoteRepositories() {\n\t\treturn remoteRepositories;\n\t}\n\n\tpublic void setRemoteRepositories(final Map<String, RemoteRepository> remoteRepositories) {\n\t\tthis.remoteRepositories = new TreeMap<>(remoteRepositories);\n\t}\n\n\tpublic void setLocalRepository(String localRepository) {\n\t\tthis.localRepository = localRepository;\n\t}\n\n\tpublic String getLocalRepository() {\n\t\treturn localRepository;\n\t}\n\n\tpublic boolean isOffline() {\n\t\treturn offline;\n\t}\n\n\tpublic void setOffline(Boolean offline) {\n\t\tthis.offline = offline;\n\t}\n\n\tpublic Integer getConnectTimeout() {\n\t\treturn this.connectTimeout;\n\t}\n\n\tpublic void setConnectTimeout(Integer connectTimeout) {\n\t\tthis.connectTimeout = connectTimeout;\n\t}\n\n\tpublic Integer getRequestTimeout() {\n\t\treturn this.requestTimeout;\n\t}\n\n\tpublic void setRequestTimeout(Integer requestTimeout) {\n\t\tthis.requestTimeout = requestTimeout;\n\t}\n\n\tpublic Proxy getProxy() {\n\t\treturn this.proxy;\n\t}\n\n\tpublic void setProxy(Proxy proxy) {\n\t\tthis.proxy = proxy;\n\t}\n\n\tpublic boolean isResolvePom() {\n\t\treturn resolvePom;\n\t}\n\n\tpublic void setResolvePom(final boolean resolvePom) {\n\t\tthis.resolvePom = resolvePom;\n\t}\n\n\tpublic static class Proxy {\n\n\t\t/**\n\t\t * Protocol to use for proxy settings.\n\t\t */\n\t\tprivate String protocol = \"http\";\n\n\t\t/**\n\t\t * Host for the proxy.\n\t\t */\n\t\tprivate String host;\n\n\t\t/**\n\t\t * Port for the proxy.\n\t\t */\n\t\tprivate int port;\n\n\t\t/**\n\t\t * List of non proxy hosts.\n\t\t */\n\t\tprivate String nonProxyHosts;\n\n\t\tprivate Authentication auth;\n\n\t\tpublic String getProtocol() {\n\t\t\treturn this.protocol;\n\t\t}\n\n\t\tpublic void setProtocol(String protocol) {\n\t\t\tthis.protocol = protocol;\n\t\t}\n\n\t\tpublic String getHost() {\n\t\t\treturn this.host;\n\t\t}\n\n\t\tpublic void setHost(String host) {\n\t\t\tthis.host = host;\n\t\t}\n\n\t\tpublic int getPort() {\n\t\t\treturn this.port;\n\t\t}\n\n\t\tpublic void setPort(int port) {\n\t\t\tthis.port = port;\n\t\t}\n\n\t\tpublic String getNonProxyHosts() {\n\t\t\treturn this.nonProxyHosts;\n\t\t}\n\n\t\tpublic void setNonProxyHosts(String nonProxyHosts) {\n\t\t\tthis.nonProxyHosts = nonProxyHosts;\n\t\t}\n\n\t\tpublic Authentication getAuth() {\n\t\t\treturn this.auth;\n\t\t}\n\n\t\tpublic void setAuth(Authentication auth) {\n\t\t\tthis.auth = auth;\n\t\t}\n\t}\n\n\tpublic static enum WagonHttpMethod {\n\t\t// directly maps to http methods in org.apache.maven.wagon.shared.http.HttpConfiguration\n\t\tall,\n\t\tget,\n\t\tput,\n\t\thead;\n\t}\n\n\tpublic static class WagonHttpMethodProperties {\n\t\t// directly maps to settings in org.apache.maven.wagon.shared.http.HttpMethodConfiguration\n\t\tprivate boolean usePreemptive;\n\t\tprivate boolean useDefaultHeaders;\n\t\tprivate Integer connectionTimeout;\n\t\tprivate Integer readTimeout;\n\t\tprivate Map<String, String> headers = new HashMap<>();\n\t\tprivate Map<String, String> params = new HashMap<>();\n\n\t\tpublic boolean isUsePreemptive() {\n\t\t\treturn usePreemptive;\n\t\t}\n\n\t\tpublic void setUsePreemptive(boolean usePreemptive) {\n\t\t\tthis.usePreemptive = usePreemptive;\n\t\t}\n\n\t\tpublic boolean isUseDefaultHeaders() {\n\t\t\treturn useDefaultHeaders;\n\t\t}\n\n\t\tpublic void setUseDefaultHeaders(boolean useDefaultHeaders) {\n\t\t\tthis.useDefaultHeaders = useDefaultHeaders;\n\t\t}\n\n\t\tpublic Integer getConnectionTimeout() {\n\t\t\treturn connectionTimeout;\n\t\t}\n\n\t\tpublic void setConnectionTimeout(Integer connectionTimeout) {\n\t\t\tthis.connectionTimeout = connectionTimeout;\n\t\t}\n\n\t\tpublic Integer getReadTimeout() {\n\t\t\treturn readTimeout;\n\t\t}\n\n\t\tpublic void setReadTimeout(Integer readTimeout) {\n\t\t\tthis.readTimeout = readTimeout;\n\t\t}\n\n\t\tpublic Map<String, String> getHeaders() {\n\t\t\treturn headers;\n\t\t}\n\n\t\tpublic void setHeaders(Map<String, String> headers) {\n\t\t\tthis.headers = headers;\n\t\t}\n\n\t\tpublic Map<String, String> getParams() {\n\t\t\treturn params;\n\t\t}\n\n\t\tpublic void setParams(Map<String, String> params) {\n\t\t\tthis.params = params;\n\t\t}\n\t}\n\n\tpublic static class Wagon {\n\n\t\tprivate Map<WagonHttpMethod, WagonHttpMethodProperties> http = new HashMap<>();\n\n\t\tpublic Map<WagonHttpMethod, WagonHttpMethodProperties> getHttp() {\n\t\t\treturn http;\n\t\t}\n\n\t\tpublic void setHttp(Map<WagonHttpMethod, WagonHttpMethodProperties> http) {\n\t\t\tthis.http = http;\n\t\t}\n\t}\n\n\tpublic static class RemoteRepository {\n\n\t\t/**\n\t\t * URL of the remote maven repository. E.g. https://my.repo.com\n\t\t */\n\t\tprivate String url;\n\n\t\tprivate Authentication auth;\n\n\t\tprivate RepositoryPolicy policy;\n\n\t\tprivate RepositoryPolicy snapshotPolicy;\n\n\t\tprivate RepositoryPolicy releasePolicy;\n\n\t\tprivate Wagon wagon = new Wagon();\n\n\t\tpublic RemoteRepository() {\n\t\t}\n\n\t\tpublic RemoteRepository(final String url) {\n\t\t\tthis.url = url;\n\t\t}\n\n\t\tpublic RemoteRepository(final String url, final Authentication auth) {\n\t\t\tthis.url = url;\n\t\t\tthis.auth = auth;\n\t\t}\n\n\t\tpublic Wagon getWagon() {\n\t\t\treturn wagon;\n\t\t}\n\n\t\tpublic void setWagon(Wagon wagon) {\n\t\t\tthis.wagon = wagon;\n\t\t}\n\n\t\tpublic String getUrl() {\n\t\t\treturn url;\n\t\t}\n\n\t\tpublic void setUrl(final String url) {\n\t\t\tthis.url = url;\n\t\t}\n\n\t\tpublic Authentication getAuth() {\n\t\t\treturn auth;\n\t\t}\n\n\t\tpublic void setAuth(final Authentication auth) {\n\t\t\tthis.auth = auth;\n\t\t}\n\n\t\tpublic RepositoryPolicy getPolicy() {\n\t\t\treturn policy;\n\t\t}\n\n\t\tpublic void setPolicy(RepositoryPolicy policy) {\n\t\t\tthis.policy = policy;\n\t\t}\n\n\t\tpublic RepositoryPolicy getSnapshotPolicy() {\n\t\t\treturn snapshotPolicy;\n\t\t}\n\n\t\tpublic void setSnapshotPolicy(RepositoryPolicy snapshotPolicy) {\n\t\t\tthis.snapshotPolicy = snapshotPolicy;\n\t\t}\n\n\t\tpublic RepositoryPolicy getReleasePolicy() {\n\t\t\treturn releasePolicy;\n\t\t}\n\n\t\tpublic void setReleasePolicy(RepositoryPolicy releasePolicy) {\n\t\t\tthis.releasePolicy = releasePolicy;\n\t\t}\n\t}\n\n\tpublic static class RepositoryPolicy {\n\n\t\tprivate boolean enabled = true;\n\n\t\tprivate String updatePolicy = \"always\";\n\n\t\tprivate String checksumPolicy = \"warn\";\n\n\t\tpublic boolean isEnabled() {\n\t\t\treturn enabled;\n\t\t}\n\n\t\tpublic void setEnabled(boolean enabled) {\n\t\t\tthis.enabled = enabled;\n\t\t}\n\n\t\tpublic String getUpdatePolicy() {\n\t\t\treturn updatePolicy;\n\t\t}\n\n\t\tpublic void setUpdatePolicy(String updatePolicy) {\n\t\t\tthis.updatePolicy = updatePolicy;\n\t\t}\n\n\t\tpublic String getChecksumPolicy() {\n\t\t\treturn checksumPolicy;\n\t\t}\n\n\t\tpublic void setChecksumPolicy(String checksumPolicy) {\n\t\t\tthis.checksumPolicy = checksumPolicy;\n\t\t}\n\n\t}\n\n\tpublic static class Authentication {\n\n\t\tprivate String username;\n\n\t\tprivate String password;\n\n\t\tpublic Authentication() {\n\t\t}\n\n\t\tpublic Authentication(String username, String password) {\n\t\t\tthis.username = username;\n\t\t\tthis.password = password;\n\t\t}\n\n\t\tpublic String getUsername() {\n\t\t\treturn this.username;\n\t\t}\n\n\t\tpublic void setUsername(String username) {\n\t\t\tthis.username = username;\n\t\t}\n\n\t\tpublic String getPassword() {\n\t\t\treturn this.password;\n\t\t}\n\n\t\tpublic void setPassword(String password) {\n\t\t\tthis.password = password;\n\t\t}\n\n\t}\n}\n"
  },
  {
    "path": "spring-cloud-deployer-resource-maven/src/main/java/org/springframework/cloud/deployer/resource/maven/MavenResource.java",
    "content": "/*\n * Copyright 2015-2016 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.resource.maven;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.net.URI;\nimport java.util.List;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\n\nimport org.springframework.core.io.AbstractResource;\nimport org.springframework.core.io.Resource;\nimport org.springframework.util.Assert;\nimport org.springframework.util.StringUtils;\n\n/**\n * A {@link Resource} implementation for resolving an artifact via maven coordinates.\n * <p>\n * The {@code MavenResource} class contains <a href=\"https://maven.apache.org/pom.html#Maven_Coordinates\">\n * Maven coordinates</a> for a jar file containing an app/library, or a Bill of Materials pom.\n * <p>\n * To create a new instance, either use {@link Builder} to set the individual fields:\n * <pre>\n * new MavenResource.Builder()\n *     .setGroupId(\"org.springframework.sample\")\n *     .setArtifactId(\"some-app\")\n *     .setExtension(\"jar\") //optional\n *     .setClassifier(\"exec\") //optional\n *     .setVersion(\"2.0.0\")\n *     .build()\n * </pre>\n * ...or use {@link #parse(String)} to parse the coordinates as a colon delimited string:\n * <code>&lt;groupId&gt;:&lt;artifactId&gt;[:&lt;extension&gt;[:&lt;classifier&gt;]]:&lt;version&gt;</code>\n * <pre>\n * MavenResource.parse(\"org.springframework.sample:some-app:2.0.0);\n * MavenResource.parse(\"org.springframework.sample:some-app:jar:exec:2.0.0);\n * </pre>\n * @author David Turanski\n * @author Mark Fisher\n * @author Patrick Peralta\n * @author Venil Noronha\n * @author Ilayaperumal Gopinathan\n */\npublic class MavenResource extends AbstractResource {\n\n\tpublic static String URI_SCHEME = \"maven\";\n\n\t/**\n\t * The default extension for the artifact.\n\t */\n\tfinal static String DEFAULT_EXTENSION = \"jar\";\n\n\t/**\n\t * String representing an empty classifier.\n\t */\n\tfinal static String EMPTY_CLASSIFIER = \"\";\n\n\t/**\n\t * Group ID for artifact; generally this includes the name of the\n\t * organization that generated the artifact.\n\t */\n\tprivate final String groupId;\n\n\t/**\n\t * Artifact ID; generally this includes the name of the app or library.\n\t */\n\tprivate final String artifactId;\n\n\t/**\n\t * Extension of the artifact.\n\t */\n\tprivate final String extension;\n\n\t/**\n\t * Classifier of the artifact.\n\t */\n\tprivate final String classifier;\n\n\t/**\n\t * Version of the artifact.\n\t */\n\tprivate final String version;\n\n\tprivate final MavenArtifactResolver resolver;\n\n\t/**\n\t * Construct a {@code MavenResource} object.\n\t *\n\t * @param groupId group ID for artifact\n\t * @param artifactId artifact ID\n\t * @param extension the file extension\n\t * @param classifier artifact classifier - can be null\n\t * @param version artifact version\n\t * @param properties Maven configuration properties\n\t */\n\tprivate MavenResource(String groupId, String artifactId, String extension, String classifier,\n\t\t\tString version, MavenProperties properties) {\n\t\tAssert.hasText(groupId, \"groupId must not be blank\");\n\t\tAssert.hasText(artifactId, \"artifactId must not be blank\");\n\t\tAssert.hasText(extension, \"extension must not be blank\");\n\t\tAssert.hasText(version, \"version must not be blank\");\n\t\tthis.groupId = groupId;\n\t\tthis.artifactId = artifactId;\n\t\tthis.extension = extension;\n\t\tthis.classifier = classifier == null ? EMPTY_CLASSIFIER : classifier;\n\t\tthis.version = version;\n\t\tthis.resolver = new MavenArtifactResolver(properties != null ? properties : new MavenProperties());\n\t}\n\n\t/**\n\t * @see #groupId\n\t * @return the group id of the maven resource\n\t */\n\tpublic String getGroupId() {\n\t\treturn groupId;\n\t}\n\n\t/**\n\t * @see #artifactId\n\t * @return the artifact id of the maven resource\n\t */\n\tpublic String getArtifactId() {\n\t\treturn artifactId;\n\t}\n\n\t/**\n\t * @see #extension\n\t * @return the extension of the maven resource\n\t */\n\tpublic String getExtension() {\n\t\treturn extension;\n\t}\n\n\t/**\n\t * @see #version\n\t * @return the classifier of the maven resource\n\t */\n\tpublic String getClassifier() {\n\t\treturn classifier;\n\t}\n\n\t/**\n\t * @see #version\n\t * @return the version of the maven resource\n\t */\n\tpublic String getVersion() {\n\t\treturn version;\n\t}\n\n\t@Override\n\tpublic String getDescription() {\n\t\treturn this.toString();\n\t}\n\n\t@Override\n\tpublic InputStream getInputStream() throws IOException {\n\t\treturn resolver.resolve(this).getInputStream();\n\t}\n\n\t@Override\n\tpublic File getFile() throws IOException {\n\t\treturn resolver.resolve(this).getFile();\n\t}\n\t\n\t@Override\n\tpublic String getFilename() {\n\t\treturn StringUtils.hasLength(classifier) ?\n\t\t\t\tString.format(\"%s-%s-%s.%s\", artifactId, version, classifier, extension) :\n\t\t\t\tString.format(\"%s-%s.%s\", artifactId, version, extension);\n\t}\n\n\t@Override\n\tpublic boolean exists() {\n\t\ttry {\n\t\t\treturn super.exists();\n\t\t}\n\t\tcatch (Exception e) {\n\t\t\t// Resource.exists() has no throws clause, so return false\n\t\t\treturn false;\n\t\t}\n\t}\n\n\t@Override\n\tpublic final boolean equals(Object o) {\n\t\tif (this == o) {\n\t\t\treturn true;\n\t\t}\n\t\tif (!(o instanceof MavenResource)) {\n\t\t\treturn false;\n\t\t}\n\t\tMavenResource that = (MavenResource) o;\n\t\treturn this.groupId.equals(that.groupId) &&\n\t\t\t\tthis.artifactId.equals(that.artifactId) &&\n\t\t\t\tthis.extension.equals(that.extension) &&\n\t\t\t\tthis.classifier.equals(that.classifier) &&\n\t\t\t\tthis.version.equals(that.version);\n\t}\n\n\t@Override\n\tpublic int hashCode() {\n\t\tint result = groupId.hashCode();\n\t\tresult = 31 * result + artifactId.hashCode();\n\t\tresult = 31 * result + extension.hashCode();\n\t\tif (StringUtils.hasLength(classifier)) {\n\t\t\tresult = 31 * result + classifier.hashCode();\n\t\t}\n\t\tresult = 31 * result + version.hashCode();\n\t\treturn result;\n\t}\n\n\t/**\n\t * Returns the coordinates encoded as\n\t * &lt;groupId&gt;:&lt;artifactId&gt;[:&lt;extension&gt;[:&lt;classifier&gt;]]:&lt;version&gt;,\n\t * conforming to the <a href=\"https://www.eclipse.org/aether\">Aether</a> convention.\n\t */\n\t@Override\n\tpublic String toString() {\n\t\treturn StringUtils.hasLength(classifier) ?\n\t\t\t\tString.format(\"%s:%s:%s:%s:%s\", groupId, artifactId, extension, classifier, version) :\n\t\t\t\tString.format(\"%s:%s:%s:%s\", groupId, artifactId, extension, version);\n\t}\n\n\t@Override\n\tpublic URI getURI() throws IOException {\n\t\treturn URI.create(URI_SCHEME + \"://\" + toString());\n\t}\n\n\t/**\n\t * Create a {@link MavenResource} for the provided coordinates and default properties.\n\t *\n\t * @param coordinates coordinates encoded as &lt;groupId&gt;:&lt;artifactId&gt;[:&lt;extension&gt;[:&lt;classifier&gt;]]:&lt;version&gt;,\n\t * conforming to the <a href=\"https://www.eclipse.org/aether\">Aether</a> convention.\n\t * @return the {@link MavenResource}\n\t */\n\tpublic static MavenResource parse(String coordinates) {\n\t\treturn parse(coordinates, null);\n\t}\n\t\t\t\n\t/**\n\t * Create a {@link MavenResource} for the provided coordinates and properties.\n\t *\n\t * @param coordinates coordinates encoded as &lt;groupId&gt;:&lt;artifactId&gt;[:&lt;extension&gt;[:&lt;classifier&gt;]]:&lt;version&gt;,\n\t * conforming to the <a href=\"https://www.eclipse.org/aether\">Aether</a> convention.\n\t * @param properties the properties for the repositories, proxies, and authentication\n\t * @return the {@link MavenResource}\n\t */\n\tpublic static MavenResource parse(String coordinates, MavenProperties properties) {\n\t\tAssert.hasText(coordinates, \"coordinates are required\");\n\t\tPattern p = Pattern.compile(\"([^: ]+):([^: ]+)(:([^: ]*)(:([^: ]+))?)?:([^: ]+)\");\n\t\tMatcher m = p.matcher(coordinates);\n\t\tAssert.isTrue(m.matches(), \"Bad artifact coordinates \" + coordinates\n\t\t\t\t+ \", expected format is <groupId>:<artifactId>[:<extension>[:<classifier>]]:<version>\");\n\t\tString groupId = m.group(1);\n\t\tString artifactId = m.group(2);\n\t\tString extension = StringUtils.hasLength(m.group(4)) ? m.group(4) : DEFAULT_EXTENSION;\n\t\tString classifier = StringUtils.hasLength(m.group(6)) ? m.group(6) : EMPTY_CLASSIFIER;\n\t\tString version = m.group(7);\n\t\treturn new MavenResource(groupId, artifactId, extension, classifier, version, properties);\n\t}\n\n\t/**\n\t * Get all the available versions on this maven co-ordinate.\n\t * @param coordinates the co-ordinate with the version constraint added.\n\t *                    Example: org.springframework.cloud.stream.app:http-source-rabbit:[0,)\n\t * @return the list of all the available versions\n\t */\n\tpublic List<String> getVersions(String coordinates) {\n\t\treturn this.resolver.getVersions(coordinates);\n\t}\n\n\tpublic static class Builder {\n\n\t\tprivate String groupId;\n\n\t\tprivate String artifactId;\n\n\t\tprivate String extension = DEFAULT_EXTENSION;\n\n\t\tprivate String classifier = EMPTY_CLASSIFIER;\n\n\t\tprivate String version;\n\n\t\tprivate final MavenProperties properties;\n\n\t\tpublic Builder() {\n\t\t\tthis(null);\n\t\t}\n\n\t\tpublic Builder(MavenProperties properties) {\n\t\t\tthis.properties = properties;\n\t\t}\n\n\t\tpublic Builder groupId(String groupId) {\n\t\t\tthis.groupId = groupId;\n\t\t\treturn this;\n\t\t}\n\n\t\tpublic Builder artifactId(String artifactId) {\n\t\t\tthis.artifactId = artifactId;\n\t\t\treturn this;\n\t\t}\n\n\t\tpublic Builder extension(String extension) {\n\t\t\tthis.extension = extension;\n\t\t\treturn this;\n\t\t}\n\n\t\tpublic Builder classifier(String classifier) {\n\t\t\tthis.classifier = classifier;\n\t\t\treturn this;\n\t\t}\n\n\t\tpublic Builder version(String version) {\n\t\t\tthis.version = version;\n\t\t\treturn this;\n\t\t}\n\n\t\tpublic MavenResource build() {\n\t\t\treturn new MavenResource(groupId, artifactId, extension, classifier, version, properties);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "spring-cloud-deployer-resource-maven/src/main/java/org/springframework/cloud/deployer/resource/maven/MavenResourceLoader.java",
    "content": "/*\n * Copyright 2016 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.resource.maven;\n\nimport org.springframework.core.io.Resource;\nimport org.springframework.core.io.ResourceLoader;\nimport org.springframework.util.Assert;\nimport org.springframework.util.ClassUtils;\n\n/**\n * A {@link ResourceLoader} that loads {@link MavenResource}s from locations of the format\n * {@literal maven://<coordinates>} where the value for \"coordinates\" conforms to the rules\n * described on {@link MavenResource#parse(String)} \n *\n * @author Mark Fisher\n */\npublic class MavenResourceLoader implements ResourceLoader {\n\n\tprivate static final String URI_SCHEME = \"maven\";\n\n\tprivate final MavenProperties properties;\n\n\tprivate final ClassLoader classLoader = ClassUtils.getDefaultClassLoader();\n\n\t/**\n\t * Create a {@link MavenResourceLoader} that uses the provided {@link MavenProperties}.\n\t *\n\t * @param properties the {@link MavenProperties} to use when instantiating {@link MavenResource}s\n\t */\n\tpublic MavenResourceLoader(MavenProperties properties) {\n\t\tAssert.notNull(properties, \"MavenProperties must not be null\");\n\t\tthis.properties = properties;\n\t}\n\n\t/**\n\t * Returns a {@link MavenResource} for the provided location.\n\t *\n\t * @param location the coordinates conforming to the rules described on\n\t * {@link MavenResource#parse(String)}. May optionally be preceded by {@value #URI_SCHEME}\n\t * followed by a colon and zero or more forward slashes, e.g.\n\t * {@literal maven://group:artifact:version}\n\t * @return the {@link MavenResource}\n\t */\n\t@Override\n\tpublic Resource getResource(String location) {\n\t\tAssert.hasText(location, \"location is required\");\n\t\tString coordinates = location.replaceFirst(URI_SCHEME + \":\\\\/*\", \"\");\n\t\treturn MavenResource.parse(coordinates, this.properties);\n\t}\n\n\t/**\n\t * Returns the {@link ClassLoader} for this ResourceLoader.\n\t */\n\t@Override\n\tpublic ClassLoader getClassLoader() {\n\t\treturn this.classLoader;\n\t}\n\n}\n"
  },
  {
    "path": "spring-cloud-deployer-resource-maven/src/main/java/org/springframework/cloud/deployer/resource/maven/StaticWagonConfigurator.java",
    "content": "/*\n * Copyright 2020 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage org.springframework.cloud.deployer.resource.maven;\n\nimport java.util.Map;\nimport java.util.Map.Entry;\nimport java.util.Properties;\n\nimport org.apache.maven.wagon.Wagon;\nimport org.apache.maven.wagon.providers.http.HttpWagon;\nimport org.apache.maven.wagon.shared.http.HttpConfiguration;\nimport org.apache.maven.wagon.shared.http.HttpMethodConfiguration;\nimport org.eclipse.aether.transport.wagon.WagonConfigurator;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport org.springframework.cloud.deployer.resource.maven.MavenProperties.WagonHttpMethod;\nimport org.springframework.cloud.deployer.resource.maven.MavenProperties.WagonHttpMethodProperties;\n\n/**\n * Simple implementation of a {@link WagonConfigurator} which creates and supports\n * those providers we need. Maven resolver itself only provides PlexusWagonConfigurator\n * which is more involved with actual maven pom configuration and would not\n * suit our needs as things get a bit crazy with it due to its use of Guice.\n *\n * @author Janne Valkealahti\n * @author Corneil du Plessis\n *\n */\npublic class StaticWagonConfigurator implements WagonConfigurator {\n\n\tprivate static final Logger logger = LoggerFactory.getLogger(StaticWagonConfigurator.class);\n\n\t@Override\n\tpublic void configure(Wagon wagon, Object configuration) throws Exception {\n\t\tlogger.debug(\"Configuring wagon {} with {}\", wagon, configuration);\n\t\tif (wagon instanceof HttpWagon && configuration instanceof MavenProperties.Wagon) {\n\t\t\tHttpWagon httpWagon = (HttpWagon)wagon;\n\t\t\tMap<WagonHttpMethod, WagonHttpMethodProperties> httpMethodProperties = ((MavenProperties.Wagon) configuration)\n\t\t\t\t\t.getHttp();\n\t\t\tHttpConfiguration httpConfiguration = new HttpConfiguration();\n\t\t\tfor (Entry<WagonHttpMethod, WagonHttpMethodProperties> entry : httpMethodProperties.entrySet()) {\n\t\t\t\tswitch (entry.getKey()) {\n\t\t\t\t\tcase all:\n\t\t\t\t\t\thttpConfiguration.setAll(buildConfig(entry.getValue()));\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase get:\n\t\t\t\t\t\thttpConfiguration.setGet(buildConfig(entry.getValue()));\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase head:\n\t\t\t\t\t\thttpConfiguration.setHead(buildConfig(entry.getValue()));\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase put:\n\t\t\t\t\t\thttpConfiguration.setPut(buildConfig(entry.getValue()));\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tdefault:\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t\thttpWagon.setHttpConfiguration(httpConfiguration);\n\t\t}\n\t}\n\n\tprivate static HttpMethodConfiguration buildConfig(WagonHttpMethodProperties properties) {\n\t\tHttpMethodConfiguration config = new HttpMethodConfiguration();\n\t\tconfig.setUsePreemptive(properties.isUsePreemptive());\n\t\tconfig.setUseDefaultHeaders(properties.isUseDefaultHeaders());\n\t\tif (properties.getConnectionTimeout() != null) {\n\t\t\tconfig.setConnectionTimeout(properties.getConnectionTimeout());\n\t\t}\n\t\tif (properties.getReadTimeout() != null) {\n\t\t\tconfig.setReadTimeout(properties.getReadTimeout());\n\t\t}\n\t\tProperties params = new Properties();\n\t\tparams.putAll(properties.getParams());\n\t\tconfig.setParams(params);\n\t\tProperties headers = new Properties();\n\t\theaders.putAll(properties.getHeaders());\n\t\tconfig.setHeaders(headers);\n\t\treturn config;\n\t}\n}\n"
  },
  {
    "path": "spring-cloud-deployer-resource-maven/src/main/java/org/springframework/cloud/deployer/resource/maven/StaticWagonProvider.java",
    "content": "/*\n * Copyright 2020 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.resource.maven;\n\nimport org.apache.maven.wagon.Wagon;\nimport org.apache.maven.wagon.providers.http.HttpWagon;\nimport org.eclipse.aether.transport.wagon.WagonProvider;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * Simple implementation of a {@link WagonProvider} which creates and supports\n * those providers we need. Maven resolver itself only provides PlexusWagonProvider\n * which is more involved with actual maven pom configuration and would not\n * suit our needs as things get a bit crazy with it due to its use of Guice.\n *\n * @author Janne Valkealahti\n */\npublic class StaticWagonProvider implements WagonProvider {\n\n\tprivate static final Logger logger = LoggerFactory.getLogger(StaticWagonProvider.class);\n\n\tpublic StaticWagonProvider() {\n\t}\n\n\tpublic Wagon lookup( String roleHint ) throws Exception {\n\t\tlogger.debug(\"Looking up wagon for roleHint {}\", roleHint);\n\t\tif (\"https\".equals(roleHint)) {\n\t\t\treturn new HttpWagon();\n\t\t} else if (\"http\".equals(roleHint)) {\n\t\t\treturn new HttpWagon();\n\t\t}\n\t\tthrow new IllegalArgumentException(\"No wagon available for \" + roleHint);\n\t}\n\n\tpublic void release(Wagon wagon) {\n\t\t// nothing to do now\n\t}\n}\n"
  },
  {
    "path": "spring-cloud-deployer-resource-maven/src/test/java/org/springframework/cloud/deployer/resource/maven/MavenArtifactResolverTests.java",
    "content": "/*\n * Copyright 2016-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.resource.maven;\n\nimport java.net.UnknownHostException;\nimport java.util.Collections;\nimport java.util.stream.Stream;\n\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.Arguments;\nimport org.junit.jupiter.params.provider.MethodSource;\n\nimport org.springframework.core.io.Resource;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy;\nimport static org.assertj.core.api.AssertionsForClassTypes.tuple;\nimport static org.junit.jupiter.params.provider.Arguments.arguments;\n\n/**\n * @author Eric Chen\n * @author Chris Bono\n */\nclass MavenArtifactResolverTests {\n\n    @Test\n    void resolveFailsOnProxyWithUnknownHostException() {\n        String location = \"maven://foo:bar:1.0.1\";\n        MavenResourceLoader loader = new MavenResourceLoader(new MavenProperties());\n        Resource resource = loader.getResource(location);\n        assertThat(MavenResource.class).isEqualTo(resource.getClass());\n        MavenResource mavenResource = (MavenResource) resource;\n        MavenArtifactResolver resolver = new MavenArtifactResolver(mavenPropertiesWithProxyRepo());\n        assertThatThrownBy(() -> resolver.resolve(mavenResource))\n                .rootCause()\n                .isInstanceOf(UnknownHostException.class)\n                .hasMessageStartingWith(\"proxy.example.com:\");\n    }\n\n    private MavenProperties mavenPropertiesWithProxyRepo() {\n        MavenProperties mavenProperties = new MavenProperties();\n        mavenProperties.setLocalRepository(\"~/.m2\");\n        mavenProperties.setIncludeDefaultRemoteRepos(false);\n        MavenProperties.RemoteRepository remoteRepo2 = new MavenProperties.RemoteRepository();\n        remoteRepo2.setUrl(\"http://myrepo.com:99999\");\n        mavenProperties.getRemoteRepositories().put(\"repo2\", remoteRepo2);\n        MavenProperties.Proxy proxy = new MavenProperties.Proxy();\n        proxy.setHost(\"proxy.example.com\");\n        proxy.setPort(8080);\n        proxy.setNonProxyHosts(\"apache*|*.springframework.org|127.0.0.1|localhost\");\n        mavenProperties.setProxy(proxy);\n        return mavenProperties;\n    }\n\n    @Test\n    void resolveFailsOnNoProxyWithIllegalArgumentException() {\n        String location = \"maven://foo:bar:1.0.1\";\n        MavenResourceLoader loader = new MavenResourceLoader(new MavenProperties());\n        Resource resource = loader.getResource(location);\n        assertThat(MavenResource.class).isEqualTo(resource.getClass());\n        MavenResource mavenResource = (MavenResource) resource;\n        MavenArtifactResolver resolver = new MavenArtifactResolver(mavenPropertiesWithNoProxyRepo());\n        assertThatThrownBy(() -> resolver.resolve(mavenResource))\n                .hasRootCauseInstanceOf(IllegalArgumentException.class)\n                .hasRootCauseMessage(\"port out of range:99999\");\n    }\n\n    private MavenProperties mavenPropertiesWithNoProxyRepo() {\n        MavenProperties mavenProperties = new MavenProperties();\n        mavenProperties.setLocalRepository(\"~/.m2\");\n        mavenProperties.setIncludeDefaultRemoteRepos(false);\n        MavenProperties.RemoteRepository remoteRepo1 = new MavenProperties.RemoteRepository();\n        remoteRepo1.setUrl(\"http://localhost:99999\");\n        mavenProperties.getRemoteRepositories().put(\"repo1\", remoteRepo1);\n        MavenProperties.Proxy proxy = new MavenProperties.Proxy();\n        proxy.setHost(\"http://proxy.example.com\");\n        proxy.setPort(8080);\n        proxy.setNonProxyHosts(\"apache*|*.springframework.org|127.0.0.1|localhost\");\n        mavenProperties.setProxy(proxy);\n        return mavenProperties;\n    }\n\n    @Test\n    void resolveFailsWithErrorMessageIncludingRemoteRepos() {\n        String location = \"maven://one:car:1.0.1\";\n        MavenResourceLoader loader = new MavenResourceLoader(new MavenProperties());\n        Resource resource = loader.getResource(location);\n        assertThat(MavenResource.class).isEqualTo(resource.getClass());\n        MavenResource mavenResource = (MavenResource) resource;\n        MavenProperties mavenProps = new MavenProperties();\n        mavenProps.setIncludeDefaultRemoteRepos(false);\n        MavenProperties.RemoteRepository remoteRepo = new MavenProperties.RemoteRepository();\n        remoteRepo.setUrl(\"http://myrepo.com:99999\");\n        mavenProps.getRemoteRepositories().put(\"repo2\", remoteRepo);\n\n        MavenArtifactResolver resolver = new MavenArtifactResolver(mavenProps);\n        assertThatThrownBy(() -> resolver.resolve(mavenResource))\n                .hasMessage(\"Failed to resolve one:car:jar:1.0.1 using remote repo(s): [repo2 (http://myrepo.com:99999)]\");\n    }\n\n    @Test\n    void defaultReposAddedWhenNoOtherRemoteRepos() {\n        MavenProperties mavenProps = new MavenProperties();\n        MavenArtifactResolver mavenResolver = new MavenArtifactResolver(mavenProps);\n        assertThat(mavenResolver.remoteRepositories())\n                .extracting(\"id\", \"url\")\n                .containsExactly(tuple(\"mavenCentral-default\", \"https://repo.maven.apache.org/maven2\"),\n                        tuple(\"springSnapshot-default\", \"https://repo.spring.io/snapshot\"),\n                        tuple(\"springMilestone-default\", \"https://repo.spring.io/milestone\"));\n    }\n\n    @Test\n    void defaultReposAddedInFrontOfOtherRemoteRepos() {\n        MavenProperties mavenProps = new MavenProperties();\n        mavenProps.setRemoteRepositories(Collections.singletonMap(\"myRepo\", new MavenProperties.RemoteRepository(\"https://my.custom.repo/snapshot\")));\n        MavenArtifactResolver mavenResolver = new MavenArtifactResolver(mavenProps);\n        assertThat(mavenResolver.remoteRepositories())\n                .extracting(\"id\", \"url\")\n                .containsExactly(tuple(\"mavenCentral-default\", \"https://repo.maven.apache.org/maven2\"),\n                        tuple(\"springSnapshot-default\", \"https://repo.spring.io/snapshot\"),\n                        tuple(\"springMilestone-default\", \"https://repo.spring.io/milestone\"),\n                        tuple(\"myRepo\", \"https://my.custom.repo/snapshot\"));\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"defaultReposIdAndUrlProvider\")\n    void defaultRepoAddedWhenNotAlreadyConfigured(String defaultRepoId, String defaultRepoUrl) {\n        MavenProperties mavenProps = new MavenProperties();\n        mavenProps.setRemoteRepositories(Collections.singletonMap(\"myRepo\", new MavenProperties.RemoteRepository(defaultRepoUrl + \"/foo\")));\n        MavenArtifactResolver mavenResolver = new MavenArtifactResolver(mavenProps);\n        assertThat(mavenResolver.remoteRepositories())\n                .extracting(\"id\", \"url\")\n                .hasSize(4)\n                .contains(tuple(\"myRepo\", defaultRepoUrl + \"/foo\"))\n                .contains(tuple(defaultRepoId, defaultRepoUrl));\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"defaultReposIdAndUrlProvider\")\n    void defaultRepoNotAddedWhenAlreadyConfigured(String defaultRepoId, String defaultRepoUrl) {\n        MavenProperties mavenProps = new MavenProperties();\n        mavenProps.setRemoteRepositories(Collections.singletonMap(\"myRepo\", new MavenProperties.RemoteRepository(defaultRepoUrl)));\n        MavenArtifactResolver mavenResolver = new MavenArtifactResolver(mavenProps);\n        assertThat(mavenResolver.remoteRepositories())\n                .extracting(\"id\", \"url\")\n                .hasSize(3)\n                .contains(tuple(\"myRepo\", defaultRepoUrl))\n                .doesNotContain(tuple(defaultRepoId, defaultRepoUrl));\n    }\n\n    static Stream<Arguments> defaultReposIdAndUrlProvider() {\n        return Stream.of(\n                arguments(\"mavenCentral-default\", \"https://repo.maven.apache.org/maven2\"),\n                arguments(\"springSnapshot-default\", \"https://repo.spring.io/snapshot\"),\n                arguments(\"springMilestone-default\", \"https://repo.spring.io/milestone\")\n        );\n    }\n\n    @Test\n    void defaultReposNotAddedWhenPropertyIsDisabledWithNoOtherRepos() {\n        MavenProperties mavenProps = new MavenProperties();\n        mavenProps.setIncludeDefaultRemoteRepos(false);\n        MavenArtifactResolver mavenResolver = new MavenArtifactResolver(mavenProps);\n        assertThat(mavenResolver.remoteRepositories()).isEmpty();\n    }\n\n    @Test\n    void defaultReposNotAddedWhenPropertyIsDisabledWithOtherRepo() {\n        MavenProperties mavenProps = new MavenProperties();\n        mavenProps.setIncludeDefaultRemoteRepos(false);\n        mavenProps.setRemoteRepositories(Collections.singletonMap(\"myRepo\",\n                new MavenProperties.RemoteRepository(\"https://repo.snap.io/snapshot\")));\n        MavenArtifactResolver mavenResolver = new MavenArtifactResolver(mavenProps);\n        assertThat(mavenResolver.remoteRepositories())\n                .extracting(\"url\")\n                .containsExactly(\"https://repo.snap.io/snapshot\");\n    }\n\n    @Test\n    void defaultReposAddedAndProxiedWhenProxyEnabled() {\n        MavenProperties.Proxy proxy = new MavenProperties.Proxy();\n        proxy.setHost(\"proxy.example.com\");\n        proxy.setPort(8080);\n        MavenProperties mavenProps = new MavenProperties();\n        mavenProps.setProxy(proxy);\n        MavenArtifactResolver mavenResolver = new MavenArtifactResolver(mavenProps);\n        assertThat(mavenResolver.remoteRepositories())\n                .extracting(\"url\", \"proxy.type\", \"proxy.host\", \"proxy.port\")\n                .containsExactly(tuple(\"https://repo.maven.apache.org/maven2\", \"http\", \"proxy.example.com\", 8080),\n                        tuple(\"https://repo.spring.io/snapshot\", \"http\", \"proxy.example.com\", 8080),\n                        tuple(\"https://repo.spring.io/milestone\", \"http\", \"proxy.example.com\", 8080));\n    }\n\n}\n"
  },
  {
    "path": "spring-cloud-deployer-resource-maven/src/test/java/org/springframework/cloud/deployer/resource/maven/MavenExtension.java",
    "content": "/*\n * Copyright 2020 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage org.springframework.cloud.deployer.resource.maven;\n\nimport java.util.Objects;\n\nimport org.junit.jupiter.api.extension.AfterEachCallback;\nimport org.junit.jupiter.api.extension.BeforeEachCallback;\nimport org.junit.jupiter.api.extension.ExtensionContext;\n\nimport org.springframework.boot.SpringApplication;\nimport org.springframework.boot.autoconfigure.SpringBootApplication;\nimport org.springframework.context.ConfigurableApplicationContext;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.core.annotation.Order;\nimport org.springframework.http.HttpStatus;\nimport org.springframework.security.config.Customizer;\nimport org.springframework.security.config.annotation.web.builders.HttpSecurity;\nimport org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;\nimport org.springframework.security.config.annotation.web.configurers.ExceptionHandlingConfigurer;\nimport org.springframework.security.core.userdetails.User;\nimport org.springframework.security.core.userdetails.User.UserBuilder;\nimport org.springframework.security.core.userdetails.UserDetailsService;\nimport org.springframework.security.provisioning.InMemoryUserDetailsManager;\nimport org.springframework.security.web.SecurityFilterChain;\nimport org.springframework.security.web.authentication.HttpStatusEntryPoint;\nimport org.springframework.web.bind.annotation.GetMapping;\nimport org.springframework.web.bind.annotation.RequestMapping;\nimport org.springframework.web.bind.annotation.RestController;\n\n/**\n * Simple junit5 extension which bootstraps a server to simulate various\n * scenarios for artifact resolving via http.\n *\n * @author Janne Valkealahti\n * @author Corneil du Plessis\n */\npublic class MavenExtension implements AfterEachCallback, BeforeEachCallback {\n\n\tprivate ConfigurableApplicationContext context;\n\n\tpublic int getPort() {\n\t\treturn Integer.parseInt(Objects.requireNonNull(this.context.getEnvironment().getProperty(\"local.server.port\")));\n\t}\n\n\t@Override\n\tpublic void beforeEach(ExtensionContext context) throws Exception {\n\t\tSpringApplication application = new SpringApplication(ServerConfig.class);\n\t\tthis.context = application.run(\"--server.port=0\");\n\t}\n\n\t@Override\n\tpublic void afterEach(ExtensionContext context) throws Exception {\n\t\tif (this.context != null) {\n\t\t\tthis.context.close();\n\t\t}\n\t\tthis.context = null;\n\t}\n\n\t@SpringBootApplication\n\tstatic class ServerConfig {\n\t}\n\n\t@RestController\n\t@RequestMapping(\"/public\")\n\tstatic class PublicRepoController {\n\n\t\t@GetMapping(path = \"/org/example/app/1.0.0.RELEASE/app-1.0.0.RELEASE.jar\")\n\t\tpublic byte[] app100release() {\n\t\t\treturn new byte[0];\n\t\t}\n\n\t}\n\n\t@RestController\n\t@RequestMapping(\"/private\")\n\tstatic class PrivateRepoController {\n\n\t\t@GetMapping(path = \"/org/example/secured/1.0.0.RELEASE/secured-1.0.0.RELEASE.jar\")\n\t\tpublic byte[] secured100release() {\n\t\t\treturn new byte[0];\n\t\t}\n\n\t}\n\n\t@RestController\n\t@RequestMapping(\"/preemptive\")\n\t@EnableWebSecurity\n\tstatic class PreemptiveRepoController {\n\n\t\t@GetMapping(path = \"/org/example/preemptive/1.0.0.RELEASE/preemptive-1.0.0.RELEASE.jar\")\n\t\tpublic byte[] preemptive100release() {\n\t\t\treturn new byte[0];\n\t\t}\n\n\t}\n\n\t@Configuration\n\tstatic class BasicSecurityConfig {\n\n\t\t@Bean\n\t\tpublic UserDetailsService userDetailsService() {\n\t\t\tUserBuilder users = User.builder();\n\t\t\tInMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();\n\t\t\tmanager.createUser(users.username(\"user\").password(\"{noop}password\").roles(\"USER\").build());\n\t\t\treturn manager;\n\t\t}\n\n\t\t@Bean\n\t\tpublic SecurityFilterChain configureBasic(HttpSecurity http) throws Exception {\n\n\t\t\treturn http.authorizeHttpRequests(authorise ->\n\t\t\t\tauthorise\n\t\t\t\t\t.requestMatchers(\"/public/**\").permitAll()\n\t\t\t\t\t.requestMatchers(\"/private/**\").hasRole(\"USER\")\n\t\t\t\t\t.anyRequest().authenticated()\n\n\t\t\t).httpBasic(Customizer.withDefaults()).build();\n\t\t}\n\t}\n\n\t@Configuration\n\t@Order(1)\n\tstatic class PreemptiveSecurityConfig {\n\n\t\t@Bean\n\t\tprotected ExceptionHandlingConfigurer<HttpSecurity> configure(HttpSecurity http) throws Exception {\n\n\t\t\t// We add basic auth for /preemptive so server returns 403 as\n\t\t\t// exception handling is changed to force 403.\n\t\t\t// normal maven behaviour is that it needs 401 to continue with a challenge.\n\t\t\t// This is where preemptive auth takes place as client should send auth\n\t\t\t// with every request.\n\n\t\t\treturn http\n\t\t\t\t.securityMatcher(\"/preemptive/**\")\n\t\t\t\t.authorizeRequests(authorizeRequests ->\n\t\t\t\t\tauthorizeRequests.anyRequest().hasRole(\"USER\")\n\t\t\t\t)\n\t\t\t\t.httpBasic()\n\t\t\t\t.and()\n\t\t\t\t.exceptionHandling()\n\t\t\t\t.authenticationEntryPoint(new HttpStatusEntryPoint(HttpStatus.FORBIDDEN));\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "spring-cloud-deployer-resource-maven/src/test/java/org/springframework/cloud/deployer/resource/maven/MavenPropertiesTests.java",
    "content": "/*\n * Copyright 2020 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage org.springframework.cloud.deployer.resource.maven;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport org.junit.jupiter.api.Test;\n\nimport org.springframework.boot.context.properties.ConfigurationProperties;\nimport org.springframework.boot.context.properties.EnableConfigurationProperties;\nimport org.springframework.boot.test.context.runner.ApplicationContextRunner;\nimport org.springframework.cloud.deployer.resource.maven.MavenProperties.WagonHttpMethod;\nimport org.springframework.core.env.StandardEnvironment;\nimport org.springframework.core.env.SystemEnvironmentPropertySource;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\npublic class MavenPropertiesTests {\n\n\tprivate final ApplicationContextRunner contextRunner = new ApplicationContextRunner();\n\n\t@Test\n\tpublic void testDefaults() {\n\t\tthis.contextRunner\n\t\t\t.withUserConfiguration(Config1.class)\n\t\t\t.run((context) -> {\n\t\t\t\tMavenProperties properties = context.getBean(MavenProperties.class);\n\t\t\t\tassertThat(properties.isUseWagon()).isFalse();\n\t\t\t});\n\t}\n\n\t@Test\n\tpublic void testPreemtiveEnabled() {\n\t\tthis.contextRunner\n\t\t\t.withInitializer(context -> {\n\t\t\t\tMap<String, Object> map = new HashMap<>();\n\t\t\t\tmap.put(\"maven.use-wagon\", \"true\");\n\t\t\t\tcontext.getEnvironment().getPropertySources().addLast(new SystemEnvironmentPropertySource(\n\t\t\t\t\tStandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, map));\n\t\t\t})\n\t\t\t.withUserConfiguration(Config1.class)\n\t\t\t.run((context) -> {\n\t\t\t\tMavenProperties properties = context.getBean(MavenProperties.class);\n\t\t\t\tassertThat(properties.isUseWagon()).isTrue();\n\t\t\t});\n\t}\n\n\t@Test\n\tpublic void testRemoteRepositories() {\n\t\tthis.contextRunner\n\t\t\t.withInitializer(context -> {\n\t\t\t\tMap<String, Object> map = new HashMap<>();\n\t\t\t\tmap.put(\"maven.remote-repositories.repo1.url\", \"url1\");\n\t\t\t\tmap.put(\"maven.remote-repositories.repo1.wagon.http.all.use-preemptive\", \"true\");\n\t\t\t\tmap.put(\"maven.remote-repositories.repo1.wagon.http.all.use-default-headers\", \"true\");\n\t\t\t\tmap.put(\"maven.remote-repositories.repo1.wagon.http.all.connection-timeout\", \"2\");\n\t\t\t\tmap.put(\"maven.remote-repositories.repo1.wagon.http.all.read-timeout\", \"3\");\n\t\t\t\tmap.put(\"maven.remote-repositories.repo1.wagon.http.all.headers.header1\", \"value1\");\n\t\t\t\tmap.put(\"maven.remote-repositories.repo1.wagon.http.all.params.http.connection.timeout\", \"1\");\n\t\t\t\tcontext.getEnvironment().getPropertySources().addLast(new SystemEnvironmentPropertySource(\n\t\t\t\t\tStandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, map));\n\t\t\t})\n\t\t\t.withUserConfiguration(Config1.class)\n\t\t\t.run((context) -> {\n\t\t\t\tMavenProperties properties = context.getBean(MavenProperties.class);\n\t\t\t\tassertThat(properties.getRemoteRepositories().size()).isEqualTo(1);\n\t\t\t\tassertThat(properties.getRemoteRepositories()).containsOnlyKeys(\"repo1\");\n\t\t\t\tassertThat(properties.getRemoteRepositories().get(\"repo1\").getWagon().getHttp().size())\n\t\t\t\t\t.isEqualTo(1);\n\t\t\t\tassertThat(properties.getRemoteRepositories().get(\"repo1\").getWagon().getHttp()\n\t\t\t\t\t.get(WagonHttpMethod.all).isUsePreemptive()).isTrue();\n\t\t\t\tassertThat(properties.getRemoteRepositories().get(\"repo1\").getWagon().getHttp()\n\t\t\t\t\t.get(WagonHttpMethod.all).isUseDefaultHeaders()).isTrue();\n\t\t\t\tassertThat(properties.getRemoteRepositories().get(\"repo1\").getWagon().getHttp()\n\t\t\t\t\t.get(WagonHttpMethod.all).getConnectionTimeout()).isEqualTo(2);\n\t\t\t\tassertThat(properties.getRemoteRepositories().get(\"repo1\").getWagon().getHttp()\n\t\t\t\t\t.get(WagonHttpMethod.all).getReadTimeout()).isEqualTo(3);\n\t\t\t\tassertThat(properties.getRemoteRepositories().get(\"repo1\").getWagon().getHttp()\n\t\t\t\t\t.get(WagonHttpMethod.all).getHeaders()).containsOnlyKeys(\"header1\");\n\t\t\t\tassertThat(properties.getRemoteRepositories().get(\"repo1\").getWagon().getHttp()\n\t\t\t\t\t.get(WagonHttpMethod.all).getHeaders().get(\"header1\")).isEqualTo(\"value1\");\n\t\t\t\tassertThat(properties.getRemoteRepositories().get(\"repo1\").getWagon().getHttp()\n\t\t\t\t\t.get(WagonHttpMethod.all).getParams()).containsOnlyKeys(\"http.connection.timeout\");\n\t\t\t\tassertThat(properties.getRemoteRepositories().get(\"repo1\").getWagon().getHttp()\n\t\t\t\t\t.get(WagonHttpMethod.all).getParams().get(\"http.connection.timeout\")).isEqualTo(\"1\");\n\t\t\t\t});\n\t}\n\n\t@EnableConfigurationProperties({ MavenConfigurationProperties.class })\n\tprivate static class Config1 {\n\t}\n\n\t@ConfigurationProperties(prefix = \"maven\")\n\tpublic static class MavenConfigurationProperties extends MavenProperties {\n\t}\n}\n"
  },
  {
    "path": "spring-cloud-deployer-resource-maven/src/test/java/org/springframework/cloud/deployer/resource/maven/MavenResourceLoaderTests.java",
    "content": "/*\n * Copyright 2016 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.resource.maven;\n\nimport org.junit.jupiter.api.Test;\n\nimport org.springframework.core.io.Resource;\n\nimport static org.assertj.core.api.AssertionsForClassTypes.assertThatExceptionOfType;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\n/**\n * Tests for the {@link MavenResourceLoader}.\n *\n * @author Mark Fisher\n */\npublic class MavenResourceLoaderTests {\n\n\t@Test\n\tpublic void verifyCoordinates() {\n\t\tString location = \"maven://foo:bar:1.0.1\";\n\t\tMavenResourceLoader loader = new MavenResourceLoader(new MavenProperties());\n\t\tResource resource = loader.getResource(location);\n\t\tassertEquals(MavenResource.class, resource.getClass());\n\t\tMavenResource mavenResource = (MavenResource) resource;\n\t\tassertEquals(\"foo\", mavenResource.getGroupId());\n\t\tassertEquals(\"bar\", mavenResource.getArtifactId());\n\t\tassertEquals(\"1.0.1\", mavenResource.getVersion());\n\t}\n\n\t@Test\n\tpublic void invalidPrefix() {\n\t\tassertThatExceptionOfType(IllegalArgumentException.class).isThrownBy(() -> {\n\t\t\tMavenResourceLoader loader = new MavenResourceLoader(new MavenProperties());\n\t\t\tloader.getResource(\"foo://bar\");\n\t\t});\n\t}\n\n}\n"
  },
  {
    "path": "spring-cloud-deployer-resource-maven/src/test/java/org/springframework/cloud/deployer/resource/maven/MavenResourceTests.java",
    "content": "/*\n * Copyright 2016-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.resource.maven;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.lang.reflect.Field;\nimport java.lang.reflect.Method;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\nimport org.eclipse.aether.RepositorySystem;\nimport org.eclipse.aether.RepositorySystemSession;\nimport org.eclipse.aether.repository.RemoteRepository;\nimport org.eclipse.aether.repository.RepositoryPolicy;\nimport org.junit.jupiter.api.Test;\n\nimport org.springframework.util.Assert;\nimport org.springframework.util.ReflectionUtils;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.AssertionsForClassTypes.assertThatExceptionOfType;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\n/**\n * Tests for {@link MavenResource}\n *\n * @author Venil Noronha\n * @author Janne Valkealahti\n * @author Mark Fisher\n * @author Ilayaperumal Gopinathan\n */\npublic class MavenResourceTests {\n\n\t@Test\n\tpublic void mavenResourceFilename() throws IOException {\n\t\tMavenResource resource = new MavenResource.Builder()\n\t\t\t\t.artifactId(\"timestamp-task\")\n\t\t\t\t.groupId(\"org.springframework.cloud.task.app\")\n\t\t\t\t.version(\"1.0.0.BUILD-SNAPSHOT\")\n\t\t\t\t.build();\n\t\tassertThat(resource.getFilename()).as(\"getFilename() returned null\").isNotNull();\n\t\tassertEquals(\"timestamp-task-1.0.0.BUILD-SNAPSHOT.jar\", resource.getFilename(), \"getFilename() doesn't match the expected filename\");\n\t\tassertEquals(\"maven://org.springframework.cloud.task.app:timestamp-task:jar:1.0.0.BUILD-SNAPSHOT\",\n\t\t\t\tresource.getURI().toString(),\n\t\t\t\t\"getURI doesn't match the expected URI\");\n\t}\n\n\t@Test\n\tpublic void resourceExists() {\n\t\tMavenProperties mavenProperties = new MavenProperties();\n\t\tMap<String, MavenProperties.RemoteRepository> remoteRepositoryMap = new HashMap<>();\n\t\tremoteRepositoryMap.put(\"default\",\n\t\t\t\tnew MavenProperties.RemoteRepository(\"https://repo.spring.io/libs-snapshot\"));\n\t\tmavenProperties.setRemoteRepositories(remoteRepositoryMap);\n\t\tMavenResource resource = MavenResource\n\t\t\t\t.parse(\"org.springframework.cloud.task.app:timestamp-task:jar:1.0.0.BUILD-SNAPSHOT\", mavenProperties);\n\t\tassertEquals(resource.exists(), true);\n\t}\n\n\t@Test\n\tpublic void resourceDoesNotExist() {\n\t\tMavenProperties mavenProperties = new MavenProperties();\n\t\tMap<String, MavenProperties.RemoteRepository> remoteRepositoryMap = new HashMap<>();\n\t\tremoteRepositoryMap.put(\"default\",\n\t\t\t\tnew MavenProperties.RemoteRepository(\"https://repo.spring.io/libs-snapshot\"));\n\t\tmavenProperties.setRemoteRepositories(remoteRepositoryMap);\n\t\tMavenResource resource = MavenResource\n\t\t\t\t.parse(\"org.springframework.cloud.task.app:doesnotexist:jar:1.0.0.BUILD-SNAPSHOT\", mavenProperties);\n\t\tassertEquals(resource.exists(), false);\n\t}\n\n\t@Test\n\tpublic void coordinatesParsed() {\n\t\tMavenResource resource = MavenResource\n\t\t\t\t.parse(\"org.springframework.cloud.task.app:timestamp-task:jar:exec:1.0.0.BUILD-SNAPSHOT\");\n\t\tassertEquals(\"timestamp-task-1.0.0.BUILD-SNAPSHOT-exec.jar\", resource.getFilename(), \"getFilename() doesn't match the expected filename\");\n\t\tresource = MavenResource.parse(\"org.springframework.cloud.task.app:timestamp-task:jar:1.0.0.BUILD-SNAPSHOT\");\n\t\tassertEquals(\"timestamp-task-1.0.0.BUILD-SNAPSHOT.jar\", resource.getFilename(), \"getFilename() doesn't match the expected filename\");\n\t}\n\n\t@Test\n\tpublic void mavenResourceRetrievedFromNonDefaultRemoteRepository() throws Exception {\n\t\tString coordinates = \"org.springframework.cloud.task.app:timestamp-task:jar:1.0.0.BUILD-SNAPSHOT\";\n\t\tMavenProperties properties = new MavenProperties();\n\t\tString tempLocalRepo = System.getProperty(\"java.io.tmpdir\") + File.separator + \".m2-test1\";\n\t\tnew File(tempLocalRepo).deleteOnExit();\n\t\tproperties.setLocalRepository(tempLocalRepo);\n\t\tMap<String, MavenProperties.RemoteRepository> remoteRepositoryMap = new HashMap<>();\n\t\tremoteRepositoryMap.put(\"default\",\n\t\t\t\tnew MavenProperties.RemoteRepository(\"https://repo.spring.io/libs-snapshot\"));\n\t\tproperties.setRemoteRepositories(remoteRepositoryMap);\n\t\tMavenResource resource = MavenResource.parse(coordinates, properties);\n\t\tassertEquals(\"timestamp-task-1.0.0.BUILD-SNAPSHOT.jar\", resource.getFilename(), \"getFilename() doesn't match the expected filename\");\n\t}\n\n\t@Test\n\tpublic void localResolutionFailsIfNotCached() {\n\t\tassertThatExceptionOfType(IllegalStateException.class).isThrownBy(() -> {\n\t\t\tString tempLocalRepo = System.getProperty(\"java.io.tmpdir\") + File.separator + \".m2-test2\";\n\t\t\tnew File(tempLocalRepo).deleteOnExit();\n\t\t\tMavenProperties properties = new MavenProperties();\n\t\t\tproperties.setLocalRepository(tempLocalRepo);\n\t\t\tproperties.setOffline(true);\n\t\t\tMavenResource resource = new MavenResource.Builder(properties)\n\t\t\t\t\t.artifactId(\"timestamp-task\")\n\t\t\t\t\t.groupId(\"org.springframework.cloud.task.app\")\n\t\t\t\t\t.version(\"1.0.0.BUILD-SNAPSHOT\")\n\t\t\t\t\t.build();\n\t\t\tresource.getFile();\n\t\t});\n\t}\n\n\t@Test\n\tpublic void localResolutionSucceedsIfCached() throws Exception {\n\t\tString coordinates = \"org.springframework.cloud.task.app:timestamp-task:jar:1.0.0.BUILD-SNAPSHOT\";\n\t\tMavenProperties properties1 = new MavenProperties();\n\t\tString tempLocalRepo = System.getProperty(\"java.io.tmpdir\") + File.separator + \".m2-test3\";\n\t\tnew File(tempLocalRepo).deleteOnExit();\n\t\tproperties1.setLocalRepository(tempLocalRepo);\n\t\tMap<String, MavenProperties.RemoteRepository> remoteRepositoryMap = new HashMap<>();\n\t\tremoteRepositoryMap.put(\"default\",\n\t\t\t\tnew MavenProperties.RemoteRepository(\"https://repo.spring.io/libs-snapshot\"));\n\t\tproperties1.setRemoteRepositories(remoteRepositoryMap);\n\t\tMavenResource resource = MavenResource.parse(coordinates, properties1);\n\t\tresource.getFile();\n\n\t\t// no remotes; should not fail anymore\n\t\tMavenProperties properties2 = new MavenProperties();\n\t\tproperties2.setLocalRepository(tempLocalRepo);\n\t\tproperties2.setOffline(true);\n\t\tresource = new MavenResource.Builder(properties2)\n\t\t\t\t.artifactId(\"timestamp-task\")\n\t\t\t\t.groupId(\"org.springframework.cloud.task.app\")\n\t\t\t\t.version(\"1.0.0.BUILD-SNAPSHOT\")\n\t\t\t\t.build();\n\t\tresource.getFile();\n\t}\n\n\t@Test\n\tpublic void testGetVersions() throws Exception {\n\t\tString coordinates = \"org.springframework.cloud.task.app:timestamp-task:jar:1.0.0.BUILD-SNAPSHOT\";\n\t\tMavenProperties properties = new MavenProperties();\n\t\tString tempLocalRepo = System.getProperty(\"java.io.tmpdir\") + File.separator + \".m2-test3\";\n\t\tnew File(tempLocalRepo).deleteOnExit();\n\t\tproperties.setLocalRepository(tempLocalRepo);\n\t\tMap<String, MavenProperties.RemoteRepository> remoteRepositoryMap = new HashMap<>();\n\t\tremoteRepositoryMap.put(\"default\",\n\t\t\t\tnew MavenProperties.RemoteRepository(\"https://repo.spring.io/libs-snapshot\"));\n\t\tproperties.setRemoteRepositories(remoteRepositoryMap);\n\t\tMavenResource resource = MavenResource.parse(coordinates, properties);\n\t\tAssert.isTrue(!resource.getVersions(\"org.springframework.cloud.task.app:timestamp-task:jar:[0,)\").isEmpty(), \"Versions shouldn't be empty\");\n\t}\n\n\t@Test\n\tpublic void checkRepositoryPolicies() {\n\t\tMavenProperties mavenProperties = new MavenProperties();\n\t\tmavenProperties.setChecksumPolicy(\"always\");\n\t\tmavenProperties.setUpdatePolicy(\"fail\");\n\t\tmavenProperties.setIncludeDefaultRemoteRepos(false);\n\t\tMap<String, MavenProperties.RemoteRepository> remoteRepositoryMap = new HashMap<>();\n\t\tMavenProperties.RemoteRepository remoteRepo1 = new MavenProperties.RemoteRepository(\n\t\t\t\t\"https://repo.spring.io/libs-snapshot\");\n\t\tMavenProperties.RepositoryPolicy snapshotPolicy = new MavenProperties.RepositoryPolicy();\n\t\tsnapshotPolicy.setEnabled(true);\n\t\tsnapshotPolicy.setUpdatePolicy(\"always\");\n\t\tsnapshotPolicy.setChecksumPolicy(\"warn\");\n\t\tremoteRepo1.setSnapshotPolicy(snapshotPolicy);\n\t\tMavenProperties.RepositoryPolicy releasePolicy = new MavenProperties.RepositoryPolicy();\n\t\treleasePolicy.setEnabled(true);\n\t\treleasePolicy.setUpdatePolicy(\"interval\");\n\t\treleasePolicy.setChecksumPolicy(\"ignore\");\n\t\tremoteRepo1.setReleasePolicy(releasePolicy);\n\t\tremoteRepositoryMap.put(\"repo1\", remoteRepo1);\n\t\tMavenProperties.RemoteRepository remoteRepo2 = new MavenProperties.RemoteRepository(\n\t\t\t\t\"https://repo.spring.io/libs-milestone\");\n\t\tMavenProperties.RepositoryPolicy policy = new MavenProperties.RepositoryPolicy();\n\t\tpolicy.setEnabled(true);\n\t\tpolicy.setUpdatePolicy(\"daily\");\n\t\tpolicy.setChecksumPolicy(\"fail\");\n\t\tremoteRepo2.setPolicy(policy);\n\t\tremoteRepositoryMap.put(\"repo2\", remoteRepo2);\n\t\tmavenProperties.setRemoteRepositories(remoteRepositoryMap);\n\t\tMavenArtifactResolver artifactResolver = new MavenArtifactResolver(mavenProperties);\n\t\tField remoteRepositories = ReflectionUtils.findField(MavenArtifactResolver.class, \"remoteRepositories\");\n\t\tReflectionUtils.makeAccessible(remoteRepositories);\n\t\tList<RemoteRepository> remoteRepositoryList = (List<RemoteRepository>) ReflectionUtils\n\t\t\t\t.getField(remoteRepositories, artifactResolver);\n\t\tField repositorySystem = ReflectionUtils.findField(MavenArtifactResolver.class, \"repositorySystem\");\n\t\tReflectionUtils.makeAccessible(repositorySystem);\n\t\tRepositorySystem repositorySystem1 = (RepositorySystem) ReflectionUtils.getField(repositorySystem, artifactResolver);\n\t\tMethod repositorySystemSessionMethod = ReflectionUtils.findMethod(MavenArtifactResolver.class, \"newRepositorySystemSession\", RepositorySystem.class, String.class);\n\t\tReflectionUtils.makeAccessible(repositorySystemSessionMethod);\n\t\tRepositorySystemSession repositorySystemSession = (RepositorySystemSession)\n\t\t\t\tReflectionUtils.invokeMethod(repositorySystemSessionMethod, artifactResolver, repositorySystem1, \"file://local\");\n\t\tassertEquals(\"always\", repositorySystemSession.getChecksumPolicy());\n\t\tassertEquals(\"fail\", repositorySystemSession.getUpdatePolicy());\n\t\tfor (RemoteRepository remoteRepository : remoteRepositoryList) {\n\t\t\tassertEquals(2, remoteRepositoryList.size());\n\t\t\tassertEquals(true, remoteRepositoryList.get(0).getId().equals(\"repo1\")\n\t\t\t\t\t|| remoteRepositoryList.get(0).getId().equals(\"repo2\"));\n\t\t\tassertEquals(true, remoteRepositoryList.get(1).getId().equals(\"repo2\")\n\t\t\t\t\t|| remoteRepositoryList.get(1).getId().equals(\"repo1\"));\n\t\t\tif (remoteRepository.getId().equals(\"repo1\")) {\n\t\t\t\tRepositoryPolicy snapshotPolicy1 = remoteRepository.getPolicy(true);\n\t\t\t\tassertEquals(true, snapshotPolicy1.isEnabled());\n\t\t\t\tassertEquals(\"always\", snapshotPolicy1.getUpdatePolicy());\n\t\t\t\tassertEquals(\"warn\", snapshotPolicy1.getChecksumPolicy());\n\t\t\t\tRepositoryPolicy releasePolicy1 = remoteRepository.getPolicy(false);\n\t\t\t\tassertEquals(true, releasePolicy1.isEnabled());\n\t\t\t\tassertEquals(\"interval\", releasePolicy1.getUpdatePolicy());\n\t\t\t\tassertEquals(\"ignore\", releasePolicy1.getChecksumPolicy());\n\t\t\t}\n\t\t\telse if (remoteRepository.getId().equals(\"repo2\")) {\n\t\t\t\tRepositoryPolicy snapshotPolicy2 = remoteRepository.getPolicy(true);\n\t\t\t\tassertEquals(true, snapshotPolicy2.isEnabled());\n\t\t\t\tassertEquals(\"daily\", snapshotPolicy2.getUpdatePolicy());\n\t\t\t\tassertEquals(\"fail\", snapshotPolicy2.getChecksumPolicy());\n\t\t\t\tRepositoryPolicy releasePolicy2 = remoteRepository.getPolicy(false);\n\t\t\t\tassertEquals(true, releasePolicy2.isEnabled());\n\t\t\t\tassertEquals(\"daily\", releasePolicy2.getUpdatePolicy());\n\t\t\t\tassertEquals(\"fail\", releasePolicy2.getChecksumPolicy());\n\t\t\t}\n\t\t}\n\t\tMavenResource resource = MavenResource\n\t\t\t\t.parse(\"org.springframework.cloud.task.app:timestamp-task:jar:1.0.0.BUILD-SNAPSHOT\", mavenProperties);\n\t\tassertEquals(resource.exists(), false);\n\t}\n\n}\n"
  },
  {
    "path": "spring-cloud-deployer-resource-maven/src/test/java/org/springframework/cloud/deployer/resource/maven/WagonHttpTests.java",
    "content": "/*\n * Copyright 2020 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage org.springframework.cloud.deployer.resource.maven;\n\nimport java.nio.file.Path;\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.api.io.TempDir;\n\nimport org.springframework.cloud.deployer.resource.maven.MavenProperties.Authentication;\nimport org.springframework.cloud.deployer.resource.maven.MavenProperties.RemoteRepository;\nimport org.springframework.cloud.deployer.resource.maven.MavenProperties.WagonHttpMethod;\nimport org.springframework.cloud.deployer.resource.maven.MavenProperties.WagonHttpMethodProperties;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * Tests for Maven Wagon resolver.\n *\n * @author Janne Valkealahti\n * @author Corneil du Plessis\n */\npublic class WagonHttpTests {\n\n\t@RegisterExtension\n\tstatic MavenExtension server = new MavenExtension();\n\n\t@Test\n\tpublic void resourceDoesNotExistWagon(@TempDir Path tempDir) {\n\t\tMavenProperties mavenProperties = new MavenProperties();\n\t\tmavenProperties.setLocalRepository(tempDir.toAbsolutePath().toString());\n\t\tmavenProperties.setUseWagon(true);\n\t\tMap<String, MavenProperties.RemoteRepository> remoteRepositoryMap = new HashMap<>();\n\t\tremoteRepositoryMap.put(\"default\",\n\t\t\t\tnew MavenProperties.RemoteRepository(\"http://localhost:\" + server.getPort() + \"/public\"));\n\t\tmavenProperties.setRemoteRepositories(remoteRepositoryMap);\n\t\tMavenResource resource = MavenResource\n\t\t\t\t.parse(\"org.example:doesnotexist:jar:1.0.0.RELEASE\", mavenProperties);\n\t\tassertThat(resource.exists()).isFalse();\n\t}\n\n\n\t@Test\n\tpublic void resourceDoesExistWagon(@TempDir Path tempDir) {\n\t\tMavenProperties mavenProperties = new MavenProperties();\n\t\tmavenProperties.setLocalRepository(tempDir.toAbsolutePath().toString());\n\t\tmavenProperties.setUseWagon(true);\n\t\tMap<String, MavenProperties.RemoteRepository> remoteRepositoryMap = new HashMap<>();\n\t\tremoteRepositoryMap.put(\"default\",\n\t\t\t\tnew MavenProperties.RemoteRepository(\"http://localhost:\" + server.getPort() + \"/public\"));\n\t\tmavenProperties.setRemoteRepositories(remoteRepositoryMap);\n\t\tMavenResource resource = MavenResource\n\t\t\t\t.parse(\"org.example:app:jar:1.0.0.RELEASE\", mavenProperties);\n\t\tassertThat(resource.exists()).isTrue();\n\t}\n\n\t@Test\n\tpublic void resourceDoesExistWithAuth(@TempDir Path tempDir) {\n\t\tMavenProperties mavenProperties = new MavenProperties();\n\t\tmavenProperties.setLocalRepository(tempDir.toAbsolutePath().toString());\n\t\tmavenProperties.setUseWagon(true);\n\t\tMap<String, RemoteRepository> remoteRepositories = new HashMap<>();\n\t\tRemoteRepository remoteRepository = new RemoteRepository(\"http://localhost:\" + server.getPort() + \"/private\");\n\t\tAuthentication auth = new Authentication(\"user\", \"password\");\n\t\tremoteRepository.setAuth(auth);\n\t\tremoteRepositories.put(\"default\", remoteRepository);\n\t\tmavenProperties.setRemoteRepositories(remoteRepositories);\n\t\tMavenResource resource = MavenResource\n\t\t\t\t.parse(\"org.example:secured:jar:1.0.0.RELEASE\", mavenProperties);\n\t\tassertThat(resource.exists()).isTrue();\n\t}\n\n\t@Test\n\tpublic void resourceDoesExistWithPreemptiveAuth(@TempDir Path tempDir) {\n\t\tMavenProperties mavenProperties = new MavenProperties();\n\t\tmavenProperties.setLocalRepository(tempDir.toAbsolutePath().toString());\n\n\t\tmavenProperties.setUseWagon(true);\n\t\tMap<String, RemoteRepository> remoteRepositories = new HashMap<>();\n\t\tRemoteRepository remoteRepository = new RemoteRepository(\"http://localhost:\" + server.getPort() + \"/preemptive\");\n\t\tWagonHttpMethodProperties wagonHttpMethodProperties = new WagonHttpMethodProperties();\n\t\twagonHttpMethodProperties.setUsePreemptive(true);\n\n\t\tMap<String, String> headers = new HashMap<>();\n\t\theaders.put(\"Foo\", \"Bar\");\n\t\tMap<String, String> params = new HashMap<>();\n\t\tparams.put(\"http.connection.stalecheck\", \"true\");\n\t\twagonHttpMethodProperties.setHeaders(headers);\n\t\twagonHttpMethodProperties.setParams(params);\n\n\t\tremoteRepository.getWagon().getHttp().put(WagonHttpMethod.all, wagonHttpMethodProperties);\n\t\tAuthentication auth = new Authentication(\"user\", \"password\");\n\t\tremoteRepository.setAuth(auth);\n\t\tremoteRepositories.put(\"default\", remoteRepository);\n\t\tmavenProperties.setRemoteRepositories(remoteRepositories);\n\t\tMavenResource resource = MavenResource\n\t\t\t\t.parse(\"org.example:preemptive:jar:1.0.0.RELEASE\", mavenProperties);\n\t\tassertThat(resource.exists()).isTrue();\n\t}\n}\n"
  },
  {
    "path": "spring-cloud-deployer-resource-maven/src/test/resources/application.properties",
    "content": "logging.level.org.springframework.cloud.deployer=debug\nlogging.level.org.springframework.web=debug\nlogging.level.org.apache.http.headers=debug\n"
  },
  {
    "path": "spring-cloud-deployer-resource-support/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd\">\n\t<modelVersion>4.0.0</modelVersion>\n\n\t<artifactId>spring-cloud-deployer-resource-support</artifactId>\n\t<packaging>jar</packaging>\n\t<name>Spring Cloud Deployer Resource Support</name>\n\n\t<parent>\n\t\t<groupId>org.springframework.cloud</groupId>\n\t\t<artifactId>spring-cloud-deployer-parent</artifactId>\n\t\t<version>3.0.0-SNAPSHOT</version>\n\t\t<relativePath>..</relativePath>\n\t</parent>\n\n\t<dependencies>\n\t\t<dependency>\n\t\t\t<groupId>org.springframework</groupId>\n\t\t\t<artifactId>spring-context</artifactId>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.slf4j</groupId>\n\t\t\t<artifactId>slf4j-api</artifactId>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.junit.jupiter</groupId>\n\t\t\t<artifactId>junit-jupiter</artifactId>\n\t\t\t<scope>test</scope>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.slf4j</groupId>\n\t\t\t<artifactId>slf4j-simple</artifactId>\n\t\t\t<scope>test</scope>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.assertj</groupId>\n\t\t\t<artifactId>assertj-core</artifactId>\n\t\t\t<scope>test</scope>\n\t\t</dependency>\n\t</dependencies>\n\n</project>\n"
  },
  {
    "path": "spring-cloud-deployer-resource-support/src/main/java/org/springframework/cloud/deployer/resource/registry/InMemoryUriRegistry.java",
    "content": "/*\n * Copyright 2016 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.resource.registry;\n\nimport java.net.URI;\nimport java.util.Collections;\nimport java.util.Map;\nimport java.util.concurrent.ConcurrentHashMap;\n\nimport org.springframework.util.Assert;\n\n/**\n * In-memory (non persistent) {@link UriRegistry} implementation.\n *\n * @author Patrick Peralta\n */\npublic class InMemoryUriRegistry implements UriRegistry {\n\n\tprivate final Map<String, URI> map = new ConcurrentHashMap<>();\n\n\t@Override\n\tpublic URI find(String key) {\n\t\tAssert.hasLength(key, \"key required\");\n\t\tURI uri = this.map.get(key);\n\t\tif (uri == null) {\n\t\t\tthrow new IllegalArgumentException(\"No URI found for \" + key);\n\t\t}\n\t\treturn uri;\n\t}\n\n\t@Override\n\tpublic Map<String, URI> findAll() {\n\t\treturn Collections.unmodifiableMap(this.map);\n\t}\n\n\t@Override\n\tpublic void register(String key, URI uri) {\n\t\tthis.map.put(key, uri);\n\t}\n\n\t@Override\n\tpublic void unregister(String key) {\n\t\tthis.map.remove(key);\n\t}\n\n}\n"
  },
  {
    "path": "spring-cloud-deployer-resource-support/src/main/java/org/springframework/cloud/deployer/resource/registry/UriRegistry.java",
    "content": "/*\n * Copyright 2016 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.resource.registry;\n\nimport java.net.URI;\nimport java.util.Map;\n\n/**\n * Registry for storing and finding {@link URI}s via a string key.\n *\n * @author Patrick Peralta\n */\npublic interface UriRegistry {\n\n\t/**\n\t * Return a {@link URI} for a string key.\n\t *\n\t * @param key the key for the URI\n\t * @return the {@code URI} for the given key\n\t * @throws IllegalArgumentException if no URI is registered with the key\n\t */\n\tURI find(String key);\n\n\t/**\n\t * Return all registered {@code URI}s.\n\t *\n\t * @return map of keys to {@code URI}s.\n\t */\n\tMap<String, URI> findAll();\n\n\t/**\n\t * Register a {@link URI} with a string key. Existing\n\t * registrations will be overwritten.\n\t *\n\t * @param key the key for the URI\n\t * @param uri the {@code URI} to associate with the key\n\t */\n\tvoid register(String key, URI uri);\n\n\t/**\n\t * Remove the registration for a string key.\n\t *\n\t * @param key the key for the {@code URI} to unregister\n\t */\n\tvoid unregister(String key);\n\n}\n"
  },
  {
    "path": "spring-cloud-deployer-resource-support/src/main/java/org/springframework/cloud/deployer/resource/registry/UriRegistryPopulator.java",
    "content": "/*\n * Copyright 2016-2017 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.resource.registry;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.net.URI;\nimport java.net.URISyntaxException;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.Properties;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport org.springframework.context.ResourceLoaderAware;\nimport org.springframework.core.io.Resource;\nimport org.springframework.core.io.ResourceLoader;\nimport org.springframework.util.Assert;\nimport org.springframework.util.ObjectUtils;\nimport org.springframework.util.StringUtils;\n\n/**\n * Utility class for populating a {@link UriRegistry} via a\n * {@link Properties} file. One or more URI strings indicating the\n * location of property files is supplied via the constructor,\n * and the files themselves are loaded via the {@link Resource}\n * provided by {@link #resourceLoader}.\n *\n * @author Patrick Peralta\n * @author Ilayaperumal Gopinathan\n */\npublic class UriRegistryPopulator implements ResourceLoaderAware {\n\n\tprivate static final Logger logger = LoggerFactory.getLogger(UriRegistryPopulator.class);\n\n\tprivate volatile ResourceLoader resourceLoader;\n\n\n\t@Override\n\tpublic void setResourceLoader(ResourceLoader resourceLoader) {\n\t\tthis.resourceLoader = resourceLoader;\n\t}\n\n\t/**\n\t * Populate the provided registry with the contents of\n\t * the property files indicated by {@code resourceUris}.\n\t *\n\t * @param overwrite    if {@code true}, overwrites any pre-existing registrations with the same key\n\t * @param registry     the registry to populate\n\t * @param resourceUris string(s) indicating the URIs to load properties from\n\t * @return the registered URI values in the map with the keys being the property names\n\t */\n\tpublic Map<String, URI> populateRegistry(boolean overwrite, UriRegistry registry, String... resourceUris) {\n\t\tAssert.notEmpty(resourceUris, \"resourceUri must not be empty\");\n\t\tMap<String, URI> registered = new HashMap<>();\n\t\tfor (String resourceUri : resourceUris) {\n\t\t\tResource resource = this.resourceLoader.getResource(resourceUri);\n\t\t\tProperties properties = new Properties();\n\t\t\ttry (InputStream is = resource.getInputStream()) {\n\t\t\t\tproperties.load(is);\n\t\t\t\tfor (String key : properties.stringPropertyNames()) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tURI uri = new URI(properties.getProperty(key));\n\t\t\t\t\t\tboolean validUri = true;\n\t\t\t\t\t\tif (ObjectUtils.isEmpty(uri)) {\n\t\t\t\t\t\t\tlogger.warn(String.format(\"Error when registering '%s': URI is required\", key));\n\t\t\t\t\t\t\tvalidUri = false;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (validUri && !StringUtils.hasText(uri.getScheme())) {\n\t\t\t\t\t\t\tlogger.warn(String.format(\"Error when registering '%s' with URI %s: URI scheme must be specified\", key, uri));\n\t\t\t\t\t\t\tvalidUri = false;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (validUri && !StringUtils.hasText(uri.getSchemeSpecificPart())) {\n\t\t\t\t\t\t\tlogger.warn(String.format(\"Error when registering '%s' with URI %s: URI scheme-specific part must be specified\", key, uri));\n\t\t\t\t\t\t\tvalidUri = false;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (!overwrite) {\n\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\tif (registry.find(key) != null) {\n\t\t\t\t\t\t\t\t\t// already exists; move on\n\t\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tcatch (IllegalArgumentException e) {\n\t\t\t\t\t\t\t\t// this key does not exist; will add\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (validUri) {\n\t\t\t\t\t\t\tregistry.register(key, uri);\n\t\t\t\t\t\t\tregistered.put(key, uri);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tcatch (URISyntaxException e) {\n\t\t\t\t\t\tthrow new IllegalArgumentException(String.format(\"'%s' for '%s' is not a properly formed URI\",\n\t\t\t\t\t\t\t\tproperties.getProperty(key), key), e);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tcatch (IOException e) {\n\t\t\t\tthrow new RuntimeException(e);\n\t\t\t}\n\t\t}\n\t\treturn registered;\n\t}\n\n}\n"
  },
  {
    "path": "spring-cloud-deployer-resource-support/src/main/java/org/springframework/cloud/deployer/resource/support/DelegatingResourceLoader.java",
    "content": "/*\n * Copyright 2016-2018 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.resource.support;\n\nimport java.net.URI;\nimport java.util.Collections;\nimport java.util.Map;\n\nimport org.springframework.context.ResourceLoaderAware;\nimport org.springframework.core.io.DefaultResourceLoader;\nimport org.springframework.core.io.Resource;\nimport org.springframework.core.io.ResourceLoader;\nimport org.springframework.util.Assert;\nimport org.springframework.util.ClassUtils;\nimport org.springframework.util.CollectionUtils;\n\n/**\n * A {@link ResourceLoader} implementation that delegates to other {@link ResourceLoader} instances\n * that are stored in a Map with their associated URI schemes as the keys. If a scheme does not\n * exist within the Map, it will fallback to a {@link DefaultResourceLoader}.\n * The Map may be empty (or {@literal null}).\n *\n * @author Mark Fisher\n * @author Janne Valkealahti\n * @author Ilayaperumal Gopinathan\n */\npublic class DelegatingResourceLoader implements ResourceLoader, ResourceLoaderAware {\n\n\tprivate final ClassLoader classLoader = ClassUtils.getDefaultClassLoader();\n\n\tprivate final Map<String, ResourceLoader> loaders;\n\n\tprivate ResourceLoader defaultResourceLoader = new DefaultResourceLoader();\n\n\t/**\n\t * Instantiates a new delegating resource loader.\n\t */\n\tpublic DelegatingResourceLoader() {\n\t\tthis(null);\n\t}\n\n\t/**\n\t * Instantiates a new delegating resource loader.\n\t *\n\t * @param loaders the loaders\n\t */\n\tpublic DelegatingResourceLoader(Map<String, ResourceLoader> loaders) {\n\t\tthis.loaders = CollectionUtils.isEmpty(loaders)\n\t\t\t\t? Collections.<String, ResourceLoader>emptyMap()\n\t\t\t\t: Collections.unmodifiableMap(loaders);\n\t}\n\n\t@Override\n\tpublic void setResourceLoader(ResourceLoader contextResourceLoader) {\n\t\tif (contextResourceLoader != null && contextResourceLoader != this) {\n\t\t\tthis.defaultResourceLoader = contextResourceLoader;\n\t\t}\n\t}\n\n\t@Override\n\tpublic Resource getResource(String location) {\n\t\ttry {\n\t\t\tURI uri = new URI(location);\n\t\t\tString scheme = uri.getScheme();\n\t\t\tAssert.notNull(scheme, \"a scheme (prefix) is required\");\n\t\t\tResourceLoader loader = this.loaders.get(scheme);\n\t\t\tif (loader == null) {\n\t\t\t\tif (scheme.equalsIgnoreCase(\"http\") || scheme.equalsIgnoreCase(\"https\")) {\n\t\t\t\t\tloader = new DownloadingUrlResourceLoader();\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tloader = this.defaultResourceLoader;\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn loader.getResource(location);\n\t\t}\n\t\tcatch (Exception e) {\n\t\t\tthrow new ResourceNotResolvedException(e.getMessage(), e);\n\t\t}\n\n\t}\n\n\t@Override\n\tpublic ClassLoader getClassLoader() {\n\t\treturn this.classLoader;\n\t}\n\n\t/**\n\t * Gets a map of configured loaders.\n\t *\n\t * @return the loaders\n\t */\n\tpublic Map<String, ResourceLoader> getLoaders() {\n\t\treturn loaders;\n\t}\n}\n"
  },
  {
    "path": "spring-cloud-deployer-resource-support/src/main/java/org/springframework/cloud/deployer/resource/support/DownloadingUrlResource.java",
    "content": "/*\n * Copyright 2017 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.resource.support;\n\nimport java.io.File;\nimport java.io.FileOutputStream;\nimport java.io.IOException;\nimport java.net.MalformedURLException;\nimport java.net.URISyntaxException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport org.springframework.core.io.Resource;\nimport org.springframework.core.io.UrlResource;\nimport org.springframework.util.FileCopyUtils;\n\n/**\n * A {@link Resource} implementation that will download a {@link UrlResource} to a temp file when\n * the {@link DownloadingUrlResource#getFile()} is invoked.\n *\n * @author Ilayaperumal Gopinathan\n * @author Mark Pollack\n */\npublic class DownloadingUrlResource extends UrlResource {\n\n\tprivate static final Logger logger = LoggerFactory.getLogger(DownloadingUrlResource.class);\n\n\tprivate File file;\n\n\t/**\n\t * Create a new {@code DownloadingUrlResource} based on the given URI object.\n\t * @param uri a URI\n\t * @throws MalformedURLException if the given URL path is not valid\n\t */\n\tpublic DownloadingUrlResource(String uri) throws MalformedURLException {\n\t\tsuper(uri);\n\t}\n\n\n\t/**\n\t * Downloads the file from the HTTP location to a temporary file.\n\t * The temporary file uses the directory prefix \"spring-cloud-deployer\" and the filename is\n\t * the SHA1 hash of the URL.  The file will only be downloaded on the first invocation\n\t * of this method.\n\t * @return The downloaded file.\n\t * @throws IOException if there are errors downloading or writing the temporary file.\n\t */\n\t@Override\n\tpublic synchronized File getFile() throws IOException {\n\t\tif (file == null) {\n\t\t\t// Create a well formatted filename, no dashes, slashes, etc from the URL\n\t\t\tString simpleName = null;\n\t\t\ttry {\n\t\t\t\tPath path = Paths.get(getURL().toURI().getPath());\n\t\t\t\tsimpleName = path.getFileName().toString().replaceAll(\"[^\\\\p{IsAlphabetic}^\\\\p{IsDigit}]\", \"\");\n\t\t\t} catch (URISyntaxException e) {\n\t\t\t\tlogger.info(\"Could not create simple name from last part of URL\", e.getMessage());\n\t\t\t}\n\t\t\tString fileName = ShaUtils.sha1(getURL().toString());\n\t\t\tif (simpleName != null) {\n\t\t\t\ttry {\n\t\t\t\t\tthis.file = new File(Files.createTempDirectory(\"spring-cloud-deployer\").toFile(),\n\t\t\t\t\t\t\tfileName + \"-\" + simpleName);\n\t\t\t\t} catch (IOException e) {\n\t\t\t\t\tlogger.info(\"Could not create simple temp file name using last part of URL\");\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (file == null) {\n\t\t\t\tthis.file = new File(Files.createTempDirectory(\"spring-cloud-deployer\").toFile(), fileName);\n\t\t\t}\n\t\t\t// Get the input stream for the URLResource\n\t\t\tlogger.info(\"Downloading [\" + getURL().toString() + \"] to \" + this.file.getAbsolutePath());\n\t\t\tFileCopyUtils.copy(this.getInputStream(), new FileOutputStream(file));\n\t\t}\n\t\treturn file;\n\t}\n\n\t@Override\n\tpublic synchronized String getDescription() {\n\t\tStringBuffer sb = new StringBuffer();\n\t\tsb.append(\"URL [\" + getURL() + \"]\");\n\t\tif (file != null) {\n\t\t\tsb.append(\", file [\" + this.file.getAbsolutePath() + \"]\");\n\t\t}\n\t\treturn sb.toString();\n\t}\n}\n"
  },
  {
    "path": "spring-cloud-deployer-resource-support/src/main/java/org/springframework/cloud/deployer/resource/support/DownloadingUrlResourceLoader.java",
    "content": "/*\n * Copyright 2017 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage org.springframework.cloud.deployer.resource.support;\n\nimport java.net.MalformedURLException;\n\nimport org.springframework.core.io.DefaultResourceLoader;\nimport org.springframework.core.io.Resource;\n\n/**\n * Resource loader that will return a {@link DownloadingUrlResource}\n *\n * @author Ilayaperumal Gopinathan\n */\npublic class DownloadingUrlResourceLoader extends DefaultResourceLoader {\n\n\t@Override\n\tpublic Resource getResource(String location) {\n\t\ttry {\n\t\t\treturn new DownloadingUrlResource(location);\n\t\t}\n\t\tcatch (MalformedURLException e) {\n\t\t\tthrow new IllegalStateException(e);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "spring-cloud-deployer-resource-support/src/main/java/org/springframework/cloud/deployer/resource/support/ResourceNotResolvedException.java",
    "content": "/*\n * Copyright 2016 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage org.springframework.cloud.deployer.resource.support;\n\n/**\n * Thrown to indicate failure resolving a {@link org.springframework.core.io.Resource}.\n *\n * @author Mark Pollack\n */\n@SuppressWarnings(\"serial\")\npublic class ResourceNotResolvedException extends RuntimeException {\n\n\t/**\n\t * Create a new {@link ResourceNotResolvedException} with the specified options.\n\t * @param message the exception message to display to the user\n\t * @param cause the underlying cause\n\t */\n\tpublic ResourceNotResolvedException(String message, Throwable cause) {\n\t\tsuper(message, cause);\n\t}\n}\n"
  },
  {
    "path": "spring-cloud-deployer-resource-support/src/main/java/org/springframework/cloud/deployer/resource/support/ShaUtils.java",
    "content": "/*\n * Copyright 2016 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.resource.support;\n\nimport java.io.UnsupportedEncodingException;\nimport java.security.MessageDigest;\nimport java.security.NoSuchAlgorithmException;\n\n/**\n * Simple sha utils.\n *\n * @author Janne Valkealahti\n */\npublic abstract class ShaUtils {\n\n\tprivate static final char[] CHARS = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };\n\n\t/**\n\t * Creates a sha1 out from a given data.\n\t *\n\t * @param data the data\n\t * @return the sha1 for data\n\t */\n\tprotected static String sha1(String data) {\n\t\ttry {\n\t\t\treturn new String(encodeHex(MessageDigest.getInstance(\"SHA-1\").digest(data.getBytes(\"UTF-8\"))));\n\t\t}\n\t\tcatch (NoSuchAlgorithmException e) {\n\t\t\tthrow new IllegalStateException(e);\n\t\t}\n\t\tcatch (UnsupportedEncodingException e) {\n\t\t\tthrow new IllegalStateException(e);\n\t\t}\n\t}\n\n\t/**\n\t * Encode given data as lower case hex chars.\n\t *\n\t * @param data the data\n\t * @return the endoced chars\n\t */\n\tprivate static char[] encodeHex(final byte[] data) {\n\t\tfinal int len = data.length;\n\t\tfinal char[] out = new char[len << 1];\n\t\tfor (int i = 0, j = 0; i < len; i++) {\n\t\t\tout[j++] = CHARS[(0xF0 & data[i]) >>> 4];\n\t\t\tout[j++] = CHARS[0x0F & data[i]];\n\t\t}\n\t\treturn out;\n\t}\n}\n"
  },
  {
    "path": "spring-cloud-deployer-resource-support/src/test/java/org/springframework/cloud/deployer/resource/StubResourceLoader.java",
    "content": "/*\n * Copyright 2016 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.resource;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\n\nimport org.springframework.core.io.Resource;\nimport org.springframework.core.io.ResourceLoader;\nimport org.springframework.util.ClassUtils;\n\n/**\n * Simple {@link ResourceLoader} that returns the {@link Resource}\n * provided in the constructor. It also keeps track of the locations\n * of requested resources.\n *\n * @author Patrick Peralta\n */\npublic class StubResourceLoader implements ResourceLoader {\n\n\tprivate final Resource resource;\n\n\tprivate final List<String> requestedLocations = new ArrayList<>();\n\n\n\t/**\n\t * Construct a {@code StubResourceLoader} that returns the provided\n\t * resource.\n\t *\n\t * @param resource resource to return via {@link #getResource(String)}\n\t */\n\tpublic StubResourceLoader(Resource resource) {\n\t\tthis.resource = resource;\n\t}\n\n\t@Override\n\tpublic Resource getResource(String location) {\n\t\tthis.requestedLocations.add(location);\n\t\treturn this.resource;\n\t}\n\n\t@Override\n\tpublic ClassLoader getClassLoader() {\n\t\treturn ClassUtils.getDefaultClassLoader();\n\t}\n\n\tpublic List<String> getRequestedLocations() {\n\t\treturn Collections.unmodifiableList(this.requestedLocations);\n\t}\n\n}\n"
  },
  {
    "path": "spring-cloud-deployer-resource-support/src/test/java/org/springframework/cloud/deployer/resource/registry/UriRegistryPopulatorTests.java",
    "content": "/*\n * Copyright 2016-2017 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.resource.registry;\n\nimport java.io.ByteArrayInputStream;\nimport java.io.ByteArrayOutputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.net.URI;\nimport java.util.Map;\nimport java.util.Properties;\n\nimport org.springframework.cloud.deployer.resource.StubResourceLoader;\n\nimport org.junit.jupiter.api.Test;\nimport org.springframework.core.io.AbstractResource;\nimport org.springframework.core.io.Resource;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * @author Patrick Peralta\n * @author Ilayaperumal Gopinathan\n */\npublic class UriRegistryPopulatorTests {\n\n\tprivate final Properties uris;\n\n\tpublic UriRegistryPopulatorTests() {\n\t\tthis.uris = new Properties();\n\t\tthis.uris.setProperty(\"foo.1\", \"maven://group1:foo:jar:classifier1:1.0.1\");\n\t\tthis.uris.setProperty(\"foo-2\", \"maven://group2:foo:2.1.7\");\n\t\tthis.uris.setProperty(\"bar\", \"file:///bar-1.2.3.jar\");\n\t}\n\n\t@Test\n\tpublic void populateRegistry() throws Exception {\n\t\tString localUri = \"local://local\";\n\t\tUriRegistryPopulator populator = new UriRegistryPopulator();\n\t\tStubResourceLoader resourceLoader = new StubResourceLoader(new PropertiesResource(uris));\n\t\tpopulator.setResourceLoader(resourceLoader);\n\n\t\tUriRegistry registry = new InMemoryUriRegistry();\n\t\tpopulator.populateRegistry(true, registry, localUri);\n\t\tassertThat(resourceLoader.getRequestedLocations().contains(localUri)).isTrue();\n\t\tassertThat(resourceLoader.getRequestedLocations().size()).isEqualTo(1);\n\t\tassertThat(registry.findAll().size()).isEqualTo(this.uris.size());\n\t\tfor (String key : this.uris.stringPropertyNames()) {\n\t\t\tassertThat(registry.find(key).toString()).isEqualTo(this.uris.getProperty(key));\n\t\t}\n\n\t\tboolean thrown = false;\n\t\ttry {\n\t\t\tregistry.find(\"not present\");\n\t\t}\n\t\tcatch (IllegalArgumentException e) {\n\t\t\tthrown = true;\n\t\t}\n\t\tfinally {\n\t\t\tassertThat(thrown).isTrue();\n\t\t}\n\t}\n\n\t@Test\n\tpublic void populateRegistryWithOverwrites() throws Exception {\n\t\tString localUri = \"local://local\";\n\t\tUriRegistryPopulator populator = new UriRegistryPopulator();\n\t\tPropertiesResource propertiesResource = new PropertiesResource(uris);\n\t\tStubResourceLoader resourceLoader = new StubResourceLoader(propertiesResource);\n\t\tpopulator.setResourceLoader(resourceLoader);\n\t\tUriRegistry registry = new InMemoryUriRegistry();\n\t\tMap<String, URI> registered = populator.populateRegistry(true, registry, localUri);\n\t\tassertThat(registered.size() == 3).isTrue();\n\t\t// Perform overwrites on the existing keys\n\t\tMap<String, URI> registeredWithNoOverwrites = populator.populateRegistry(false, registry, localUri);\n\t\tassertThat(registeredWithNoOverwrites.size() == 0).isTrue();\n\t\tpropertiesResource.addNewProperty(\"another\", \"maven://somegroup:someartifact:jar:exec:1.0.0\");\n\t\tMap<String, URI> newlyRegisteredWithNoOverwrites = populator.populateRegistry(false, registry, localUri);\n\t\tassertThat(newlyRegisteredWithNoOverwrites.size() == 1).isTrue();\n\t\tpropertiesResource.addNewProperty(\"yet-another\", \"file:///tmp/yet-another.jar\");\n\t\tMap<String, URI> newlyRegisteredWithOverwrites = populator.populateRegistry(true, registry, localUri);\n\t\tassertThat(newlyRegisteredWithOverwrites.size() == 5).isTrue();\n\t}\n\n\t@Test\n\tpublic void populateRegistryInvalidUri() throws Exception {\n\t\tString localUri = \"local://local\";\n\t\tProperties props = new Properties();\n\t\tprops.setProperty(\"test\", \"file:///bar-1.2.3.jar\");\n\t\tUriRegistryPopulator populator = new UriRegistryPopulator();\n\t\tStubResourceLoader resourceLoader = new StubResourceLoader(new PropertiesResource(props));\n\t\tpopulator.setResourceLoader(resourceLoader);\n\t\tUriRegistry registry = new InMemoryUriRegistry();\n\t\tpopulator.populateRegistry(true, registry, localUri);\n\t\tassertThat(resourceLoader.getRequestedLocations().contains(localUri)).isTrue();\n\t\tassertThat(resourceLoader.getRequestedLocations().size()).isEqualTo(1);\n\t\tassertThat(registry.findAll().size()).isEqualTo(1);\n\t\tassertThat(registry.find(\"test\").toString()).isEqualTo(\"file:///bar-1.2.3.jar\");\n\t\tpopulator.populateRegistry(true, registry, localUri);\n\t\tprops.setProperty(\"test\", \"invalid\");\n\t\tpopulator.populateRegistry(true, registry, localUri);\n\t\tassertThat(registry.find(\"test\").toString()).isEqualTo(\"file:///bar-1.2.3.jar\");\n\t}\n\n\n\t/**\n\t * {@link Resource} implementation that returns an {@link InputStream}\n\t * fed by a {@link Properties} object.\n\t */\n\tstatic class PropertiesResource extends AbstractResource {\n\n\t\tprivate final Properties properties;\n\n\t\tpublic PropertiesResource(Properties properties) {\n\t\t\tthis.properties = properties;\n\t\t}\n\n\t\tpublic Properties addNewProperty(String key, String value) {\n\t\t\tif (this.properties != null) {\n\t\t\t\tthis.properties.put(key, value);\n\t\t\t}\n\t\t\treturn this.properties;\n\t\t}\n\n\t\t@Override\n\t\tpublic String getDescription() {\n\t\t\treturn null;\n\t\t}\n\n\t\t@Override\n\t\tpublic InputStream getInputStream() throws IOException {\n\t\t\tByteArrayOutputStream out = new ByteArrayOutputStream();\n\t\t\tthis.properties.store(out, \"URIs\");\n\t\t\treturn new ByteArrayInputStream(out.toByteArray());\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "spring-cloud-deployer-resource-support/src/test/java/org/springframework/cloud/deployer/resource/support/DelegatingResourceLoaderIntegrationTests.java",
    "content": "/*\n * Copyright 2017 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage org.springframework.cloud.deployer.resource.support;\n\nimport java.io.File;\n\nimport org.springframework.core.io.Resource;\n\nimport org.junit.jupiter.api.Test;\nimport static org.assertj.core.api.AssertionsForClassTypes.assertThat;\n\n\n/**\n * @author Mark Pollack\n */\npublic class DelegatingResourceLoaderIntegrationTests {\n\n\t@Test\n\tpublic void test() throws Exception {\n\t\tDelegatingResourceLoader delegatingResourceLoader = new DelegatingResourceLoader();\n\t\tResource resource = delegatingResourceLoader.getResource(\"https://repo1.maven.org/maven2/org/springframework/cloud/stream/app/file-sink-rabbit/3.2.1/file-sink-rabbit-3.2.1.jar\");\n\t\tFile file1 = resource.getFile();\n\t\tFile file2 = resource.getFile();\n\t\tassertThat(file1).isEqualTo(file2);\n\t}\n}\n"
  },
  {
    "path": "spring-cloud-deployer-resource-support/src/test/java/org/springframework/cloud/deployer/resource/support/DelegatingResourceLoaderTests.java",
    "content": "/*\n * Copyright 2016 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.resource.support;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.io.TempDir;\n\nimport org.springframework.cloud.deployer.resource.StubResourceLoader;\nimport org.springframework.core.io.AbstractResource;\nimport org.springframework.core.io.ResourceLoader;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\n/**\n * Tests for {@link DelegatingResourceLoader}.\n *\n * @author Patrick Peralta\n * @author Janne Valkealahti\n * @author Ilayaperumal Gopinathan\n */\npublic class DelegatingResourceLoaderTests {\n\n\t@TempDir\n\tpublic File folder;\n\n\t@Test\n\tpublic void test() {\n\t\tNullResource one = new NullResource(\"one\");\n\t\tNullResource two = new NullResource(\"two\");\n\t\tNullResource three = new NullResource(\"three\");\n\n\t\tassertThat(two).isNotEqualTo(one);\n\t\tassertThat(three).isNotEqualTo(two);\n\n\t\tMap<String, ResourceLoader> map = new HashMap<>();\n\t\tmap.put(\"one\", new StubResourceLoader(one));\n\t\tmap.put(\"two\", new StubResourceLoader(two));\n\t\tmap.put(\"three\", new StubResourceLoader(three));\n\n\t\tDelegatingResourceLoader resourceLoader = new DelegatingResourceLoader(map);\n\t\tassertEquals(one, resourceLoader.getResource(\"one://one\"));\n\t\tassertEquals(two, resourceLoader.getResource(\"two://two\"));\n\t\tassertEquals(three, resourceLoader.getResource(\"three://three\"));\n\t}\n\n\tstatic class NullResource extends AbstractResource {\n\n\t\tfinal String description;\n\n\t\tpublic NullResource(String description) {\n\t\t\tthis.description = description;\n\t\t}\n\n\t\t@Override\n\t\tpublic String getDescription() {\n\t\t\treturn description;\n\t\t}\n\n\t\t@Override\n\t\tpublic File getFile() throws IOException {\n\t\t\treturn null;\n\t\t}\n\n\t\t@Override\n\t\tpublic InputStream getInputStream() throws IOException {\n\t\t\treturn null;\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "spring-cloud-deployer-resource-support/src/test/java/org/springframework/cloud/deployer/resource/support/DownloadingUrlResourceTests.java",
    "content": "/*\n * Copyright 2017 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage org.springframework.cloud.deployer.resource.support;\n\nimport java.io.File;\n\nimport org.junit.jupiter.api.Test;\n\nimport static org.assertj.core.api.AssertionsForClassTypes.assertThat;\n\n/**\n * @author Mark Pollack\n */\npublic class DownloadingUrlResourceTests {\n\n\t@Test\n\tpublic void test() throws Exception {\n\t\tDownloadingUrlResource httpResource = new DownloadingUrlResource(\"https://repo1.maven.org/maven2/org/springframework/cloud/stream/app/file-sink-rabbit/3.2.1/file-sink-rabbit-3.2.1.jar\");\n\t\tFile file1 = httpResource.getFile();\n\t\tFile file2 = httpResource.getFile();\n\t\tassertThat(file1).isEqualTo(file2);\n\t\tassertThat(file1.getName()).isEqualTo(\"81a23583726958052fdf75c399b81a3c4fcab6d1-filesinkrabbit321jar\");\n\t}\n}\n"
  },
  {
    "path": "spring-cloud-deployer-resource-support/src/test/java/org/springframework/cloud/deployer/resource/support/ShaUtilsTests.java",
    "content": "/*\n * Copyright 2016 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.resource.support;\n\nimport java.security.SecureRandom;\nimport java.util.HashSet;\nimport java.util.Set;\n\nimport org.junit.jupiter.api.Test;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\n/**\n * Simple sha utils tests.\n *\n * @author Janne Valkealahti\n */\npublic class ShaUtilsTests {\n\n\t@Test\n\tpublic void testSimpleSmoke() {\n\t\tfor (int j = 0; j < 100; j++) {\n\t\t\tSet<String> nodups = new HashSet<>();\n\t\t\tfor (int i = 0; i < 1000; i++) {\n\t\t\t\tnodups.add(ShaUtils.sha1(randomString(20)));\n\t\t\t}\n\t\t\tassertEquals(nodups.size() == 1000, true);\n\t\t}\n\t}\n\n\tstatic final String CHARS = \"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz!£$%^&*()-=+_\";\n\tstatic SecureRandom rnd = new SecureRandom();\n\n\tString randomString(int len) {\n\t\tStringBuilder sb = new StringBuilder(len);\n\t\tfor (int i = 0; i < len; i++)\n\t\t\tsb.append(CHARS.charAt(rnd.nextInt(CHARS.length())));\n\t\treturn sb.toString();\n\t}\n}\n"
  },
  {
    "path": "spring-cloud-deployer-spi/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd\">\n\t<modelVersion>4.0.0</modelVersion>\n\n\t<artifactId>spring-cloud-deployer-spi</artifactId>\n\t<packaging>jar</packaging>\n\t<name>Spring Cloud Deployer SPI</name>\n\n\t<parent>\n\t\t<groupId>org.springframework.cloud</groupId>\n\t\t<artifactId>spring-cloud-deployer-parent</artifactId>\n\t\t<version>3.0.0-SNAPSHOT</version>\n\t\t<relativePath>..</relativePath>\n\t</parent>\n\n\t<dependencies>\n\t\t<dependency>\n\t\t\t<groupId>org.springframework</groupId>\n\t\t\t<artifactId>spring-core</artifactId>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.springframework</groupId>\n\t\t\t<artifactId>spring-web</artifactId>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>io.projectreactor</groupId>\n\t\t\t<artifactId>reactor-core</artifactId>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.junit.jupiter</groupId>\n\t\t\t<artifactId>junit-jupiter</artifactId>\n\t\t\t<scope>test</scope>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.assertj</groupId>\n\t\t\t<artifactId>assertj-core</artifactId>\n\t\t\t<scope>test</scope>\n\t\t</dependency>\n\t</dependencies>\n\n</project>\n"
  },
  {
    "path": "spring-cloud-deployer-spi/src/main/java/org/springframework/cloud/deployer/spi/app/AbstractActuatorTemplate.java",
    "content": "/*\n * Copyright 2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.app;\n\nimport java.nio.charset.Charset;\nimport java.util.Collections;\nimport java.util.Optional;\n\nimport org.apache.commons.logging.Log;\nimport org.apache.commons.logging.LogFactory;\n\nimport org.springframework.http.HttpEntity;\nimport org.springframework.http.HttpHeaders;\nimport org.springframework.http.HttpMethod;\nimport org.springframework.http.MediaType;\nimport org.springframework.http.ResponseEntity;\nimport org.springframework.util.Assert;\nimport org.springframework.web.client.RestTemplate;\nimport org.springframework.web.util.UriComponentsBuilder;\n\n/**\n * Base class for ActuatorTemplate implementations.\n *\n * @author David Turanski\n */\npublic abstract class AbstractActuatorTemplate implements ActuatorOperations {\n\n\tprotected final Log logger = LogFactory.getLog(getClass().getName());\n\n\tprotected final RestTemplate restTemplate;\n\n\tprotected final AppDeployer appDeployer;\n\n\tprivate final AppAdmin appAdmin;\n\n\tprivate final Optional<String> defaultAuthenticationHeaderValue;\n\n\tprotected AbstractActuatorTemplate(RestTemplate restTemplate, AppDeployer appDeployer, AppAdmin appAdmin) {\n\t\tAssert.notNull(restTemplate, \"'restTemplate' is required.\");\n\t\tAssert.notNull(appDeployer, \"'appDeployer' is required.\");\n\t\tAssert.notNull(appAdmin, \"'appAdmin' is required.\");\n\t\tthis.restTemplate = restTemplate;\n\t\tthis.appDeployer = appDeployer;\n\t\tthis.appAdmin = appAdmin;\n\t\tthis.defaultAuthenticationHeaderValue = prepareDefaultAthentication(appAdmin);\n\t}\n\n\n\t@Override\n\tpublic <T> T getFromActuator(String deploymentId, String guid, String endpoint, Class<T> responseType,\n\t\t\tOptional<HttpHeaders> optionalRequestHeaders) {\n\n\t\tAppInstanceStatus appInstanceStatus = getDeployedInstance(deploymentId, guid)\n\t\t\t\t.orElseThrow(() -> new IllegalStateException(\n\t\t\t\t\t\tString.format(\"App with deploymentId %s and guid %s not deployed\", deploymentId, guid)));\n\n\t\tString actuatorUrl = getActuatorUrl(appInstanceStatus);\n\n\t\tHttpHeaders requestHeaders = requestHeaders(httpHeadersForInstance(appInstanceStatus), optionalRequestHeaders);\n\n\t\tResponseEntity<T> responseEntity = httpGet(UriComponentsBuilder\n\t\t\t\t.fromUriString(actuatorUrl).path(normalizePath(endpoint)).toUriString(), responseType, requestHeaders);\n\t\tif (responseEntity.getStatusCode().isError()) {\n\t\t\tlogger.error(responseEntity.getStatusCode().toString());\n\t\t}\n\t\treturn responseEntity.getBody();\n\t}\n\n\t@Override\n\tpublic <T, R> R postToActuator(String deploymentId, String guid, String endpoint, T body,\n\t\t\tClass<R> responseType, Optional<HttpHeaders> optionalRequestHeaders) {\n\t\tAppInstanceStatus appInstanceStatus = getDeployedInstance(deploymentId, guid)\n\t\t\t\t.orElseThrow(() -> new IllegalStateException(\n\t\t\t\t\t\tString.format(\"App with deploymentId %s and guid %s not deployed\", deploymentId, guid)));\n\n\t\tString actuatorUrl = getActuatorUrl(appInstanceStatus);\n\n\t\tHttpHeaders requestHeaders = requestHeaders(httpHeadersForInstance(appInstanceStatus), optionalRequestHeaders);\n\n\t\tResponseEntity<R> responseEntity = httpPost(UriComponentsBuilder\n\t\t\t\t.fromUriString(actuatorUrl).path(normalizePath(endpoint)).toUriString(), body, responseType,\n\t\t\t\trequestHeaders);\n\t\tif (responseEntity.getStatusCode().isError()) {\n\t\t\tlogger.error(responseEntity.getStatusCode().toString());\n\t\t}\n\t\treturn responseEntity.getBody();\n\t}\n\n\tprotected final String getActuatorUrl(AppInstanceStatus appInstanceStatus) {\n\t\ttry {\n\t\t\treturn actuatorUrlForInstance(appInstanceStatus);\n\t\t} catch (Exception e) {\n\t\t\tthrow new IllegalArgumentException(String.format(\n\t\t\t\t\t\"Unable to determine actuator url for app with guid %s\",\n\t\t\t\t\tappInstanceStatus.getAttributes().get(\"guid\")));\n\t\t}\n\t}\n\n\tprotected abstract String actuatorUrlForInstance(AppInstanceStatus appInstanceStatus);\n\n\t/**\n\t * Hook to allow subclasses to add special headers derived {@link AppInstanceStatus} metadata if necessary.\n\t *\n\t * @param appInstanceStatus the AppInstanceStatus for the target instance.\n\t * @return HttpHeaders\n\t */\n\tprotected Optional<HttpHeaders> httpHeadersForInstance(AppInstanceStatus appInstanceStatus) {\n\t\treturn Optional.empty();\n\t}\n\n\n\tprivate final HttpHeaders requestHeaders(Optional<HttpHeaders> optionalAppInstanceHeaders,\n\t\t\tOptional<HttpHeaders> optionalRequestHeaders) {\n\t\tHttpHeaders requestHeaders = optionalAppInstanceHeaders\n\t\t\t\t.orElse(new HttpHeaders());\n\t\toptionalRequestHeaders.ifPresent(requestHeaders::addAll);\n\n\t\t//Any pass-thru auth overrides the default.\n\t\tif (!requestHeaders.containsKey(HttpHeaders.AUTHORIZATION)) {\n\t\t\tthis.defaultAuthenticationHeaderValue.ifPresent(auth -> requestHeaders.setBasicAuth(auth));\n\t\t}\n\n\t\trequestHeaders.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));\n\t\trequestHeaders.setContentType(MediaType.APPLICATION_JSON);\n\t\treturn requestHeaders;\n\t}\n\n\tprivate final String normalizePath(String path) {\n\t\treturn path.startsWith(\"/\") ? path : \"/\" + path;\n\t}\n\n\tprivate final <T> ResponseEntity<T> httpGet(String url, Class<T> responseType, HttpHeaders\n\t\t\trequestHeaders) {\n\t\treturn restTemplate.exchange(url, HttpMethod.GET, new HttpEntity(requestHeaders), responseType);\n\t}\n\n\tprivate final <T, R> ResponseEntity<R> httpPost(String url, T requestBody, Class<R> responseType,\n\t\t\tHttpHeaders requestHeaders) {\n\t\treturn restTemplate.exchange(url, HttpMethod.POST,\n\t\t\t\tnew HttpEntity(requestBody, requestHeaders), responseType);\n\t}\n\n\tprivate final Optional<AppInstanceStatus> getDeployedInstance(String deploymentId, String guid) {\n\t\tAppStatus appStatus = appDeployer.status(deploymentId);\n\t\tlong count = appStatus.getInstances().values().stream().filter(\n\t\t\t\tappInstance -> appInstance.getAttributes().get(\"guid\").equals(guid)).count();\n\n\t\tif (count == 0) {\n\t\t\treturn Optional.empty();\n\t\t}\n\t\telse if (count > 1) {\n\t\t\tthrow new IllegalStateException(String.format(\n\t\t\t\t\t\"guid %s is not unique for instances of deploymentId %s\", guid, deploymentId));\n\t\t}\n\n\t\treturn appStatus.getInstances().values().stream()\n\t\t\t\t.filter(appInstance -> appInstance.getState() == DeploymentState.deployed &&\n\t\t\t\t\t\tappInstance.getAttributes().get(\"guid\").equals(guid))\n\t\t\t\t.findFirst();\n\t}\n\n\tprivate Optional<String> prepareDefaultAthentication(AppAdmin appAdmin) {\n\t\tOptional<String> encodeBasicAuth;\n\t\tencodeBasicAuth = appAdmin.hasCredentials() ?\n\t\t\tOptional.of(HttpHeaders.encodeBasicAuth(appAdmin.getUser(), appAdmin.getPassword(),\n\t\t\t\t\tCharset.defaultCharset())) : Optional.empty();\n\n\t\tif (!encodeBasicAuth.isPresent()) {\n\t\t\tlogger.warn(\"No app admin credentials have been configured for \" + this.getClass().getName());\n\t\t}\n\t\treturn encodeBasicAuth;\n\t}\n\n}\n"
  },
  {
    "path": "spring-cloud-deployer-spi/src/main/java/org/springframework/cloud/deployer/spi/app/ActuatorOperations.java",
    "content": "/*\n * Copyright 2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.app;\n\nimport java.util.Optional;\n\nimport org.springframework.http.HttpHeaders;\n\n/**\n * Deployers may implement this extension to invoke actuator endpoints of deployed app instances.\n *\n * @author David Turanski\n */\npublic interface ActuatorOperations {\n\n\t/**\n\t * Get a resource from an actuator path.\n\t *\n\t * @param deploymentId   the deployment ID of the deployed app.\n\t * @param guid           unique id for the app instance.\n\t * @param endpoint       the endpoint path relative to the base actuator URL for the instance, with or without preceding '/'.\n\t * @param responseType   the expected response type.\n\t * @param requestHeaders optional request headers.\n\t * @param <T>            type of the response.\n\t * @return the contents as the given type.\n\t */\n\t<T> T getFromActuator(String deploymentId, String guid, String endpoint, Class<T> responseType, Optional<HttpHeaders> requestHeaders);\n\n\t/**\n\t * Get a resource from an actuator path.\n\t *\n\t * @param deploymentId the deployment ID of the deployed app.\n\t * @param guid         unique id for the app instance.\n\t * @param endpoint     the endpoint path relative to the base actuator URL for the instance, with or without preceding '/'.\n\t * @param responseType the expected response type.\n\t * @param <T>          type of the response\n\t * @return the contents as the given type.\n\t */\n\tdefault <T> T getFromActuator(String deploymentId, String guid, String endpoint, Class<T> responseType) {\n\t\treturn getFromActuator(deploymentId, guid, endpoint, responseType, Optional.empty());\n\t}\n\n\t/**\n\t * Get a resource from an actuator path.\n\t *\n\t * @param deploymentId   the deployment ID of the deployed app.\n\t * @param guid           unique id for the app instance.\n\t * @param endpoint       the endpoint path relative to the base actuator URL for the instance, with or without preceding '/'.\n\t * @param requestHeaders optional request headers.\n\t * @return the contents as a {@code String}.\n\t */\n\tdefault String getFromActuator(\n\t\tString deploymentId, String guid, String endpoint,\n\t\tOptional<HttpHeaders> requestHeaders\n\t) {\n\t\treturn getFromActuator(deploymentId, guid, endpoint, String.class, requestHeaders);\n\t}\n\n\t/**\n\t * Get a resource from an actuator path.\n\t *\n\t * @param deploymentId the deployment ID of the deployed app.\n\t * @param guid         unique id for the app instance.\n\t * @param endpoint     the endpoint path relative to the base actuator URL for the instance, with or without preceding '/'.\n\t * @return the contents as a {@code String}.\n\t */\n\tdefault String getFromActuator(String deploymentId, String guid, String endpoint) {\n\t\treturn getFromActuator(deploymentId, guid, endpoint, String.class, Optional.empty());\n\t}\n\n\t/**\n\t * Post to resource on actuator path.\n\t *\n\t * @param deploymentId   the deployment ID of the deployed app.\n\t * @param guid           unique id for the app instance.\n\t * @param endpoint       the endpoint path relative to the base actuator URL for the instance, with or without preceding '/'.\n\t * @param body           the request body.\n\t * @param responseType   the expected response type.\n\t * @param requestHeaders optional request headers.\n\t * @param <T>            type of the body.\n\t * @param <R>            type of the response.\n\t * @return the result (response body).\n\t */\n\t<T, R> R postToActuator(String deploymentId, String guid, String endpoint, T body, Class<R> responseType, Optional<HttpHeaders> requestHeaders);\n\n\t/**\n\t * Post to resource on actuator path.\n\t *\n\t * @param deploymentId the deployment ID of the deployed app.\n\t * @param guid         unique id for the app instance.\n\t * @param endpoint     the endpoint path relative to the base actuator URL for the instance, with or without preceding '/'.\n\t * @param body         the request body.\n\t * @param responseType the expected response type.\n\t * @param <R>          the type of the response.\n\t * @param <T>          the type of the body.\n\t * @return the result (response body).\n\t */\n\tdefault <T, R> R postToActuator(String deploymentId, String guid, String endpoint, T body, Class<R> responseType) {\n\t\treturn postToActuator(deploymentId, guid, endpoint, body, responseType, Optional.empty());\n\t}\n}\n"
  },
  {
    "path": "spring-cloud-deployer-spi/src/main/java/org/springframework/cloud/deployer/spi/app/AppAdmin.java",
    "content": "/*\n * Copyright 2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.app;\n\nimport java.util.Map;\n\nimport org.springframework.util.StringUtils;\n\npublic class AppAdmin {\n\tpublic static final String ADMIN_USER_KEY = \"SPRING_CLOUD_STREAMAPP_SECURITY_ADMIN-USER\";\n\tpublic static final String ADMIN_PASSWORD_KEY = \"SPRING_CLOUD_STREAMAPP_SECURITY_ADMIN-PASSWORD\";\n\tpublic static final String ADMIN_USER_PROPERTY_KEY = \"spring.cloud.streamapp.security.admin-user\";\n\tpublic static final String ADMIN_PASSWORD_PROPERTY_KEY = \"spring.cloud.streamapp.security.admin-password\";\n\t/**\n\t * Username used to access protected application resources, e.g., POST to actuator.\n\t */\n\tprivate String user;\n\n\t/**\n\t * Password used to access protected application resources, e.g., POST to actuator.\n\t */\n\tprivate  String password;\n\n\tpublic String getUser() {\n\t\treturn user;\n\t}\n\n\tpublic void setUser(String user) {\n\t\tthis.user = user;\n\t}\n\n\tpublic String getPassword() {\n\t\treturn password;\n\t}\n\n\tpublic void setPassword(String password) {\n\t\tthis.password = password;\n\t}\n\n\tpublic boolean hasCredentials() {\n\t\treturn StringUtils.hasText(this.user) &&  StringUtils.hasText(this.password);\n\t}\n\n\tpublic void addCredentialsToAppEnvironment(Map<String, String> environment) {\n\t\t/*\n\t\t * These are configured credentials which the app security can use configure an authorized user\n\t\t * to access protected resources, for example, ActuatorOperations.postToActuator() provides these credentials.\n\t\t * This is intended to work for any app autoconfigured with spring-cloud-stream-applications-common-security,\n\t\t * as are the pre-packaged stream apps.\n\t\t */\n\t\tif (this.hasCredentials()) {\n\t\t\tenvironment.put(ADMIN_USER_KEY, this.user);\n\t\t\tenvironment.put(ADMIN_PASSWORD_KEY, this.password);\n\t\t}\n\t}\n\n\tpublic void addCredentialsToAppEnvironmentAsProperties(Map<String, String> environment) {\n\t\t/*\n\t\t * These are configured credentials which the app security can use configure an authorized user\n\t\t * to access protected resources, for example, ActuatorOperations.postToActuator() provides these credentials.\n\t\t * This is intended to work for any app autoconfigured with spring-cloud-stream-applications-common-security,\n\t\t * as are the pre-packaged stream apps.\n\t\t */\n\t\tif (this.hasCredentials()) {\n\t\t\tenvironment.put(ADMIN_USER_PROPERTY_KEY, this.user);\n\t\t\tenvironment.put(ADMIN_PASSWORD_PROPERTY_KEY, this.password);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "spring-cloud-deployer-spi/src/main/java/org/springframework/cloud/deployer/spi/app/AppDeployer.java",
    "content": "/*\n * Copyright 2016-2020 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.app;\n\nimport reactor.core.publisher.Flux;\nimport reactor.core.publisher.Mono;\n\nimport org.springframework.cloud.deployer.spi.core.AppDeploymentRequest;\nimport org.springframework.cloud.deployer.spi.core.RuntimeEnvironmentInfo;\n\n/**\n * SPI defining a runtime environment capable of deploying and managing the\n * lifecycle of apps that are intended to run indefinitely (until undeployed),\n * as opposed to {@link org.springframework.cloud.deployer.spi.task.TaskLauncher\n * tasks}.\n *\n * @author Mark Fisher\n * @author Patrick Peralta\n * @author Marius Bogoevici\n * @author Janne Valkealahti\n * @author Thomas Risberg\n * @author Ilayaperumal Gopinathan\n * @author David Turanski\n */\npublic interface AppDeployer {\n\n\t/**\n\t * Common prefix used for deployment properties.\n\t */\n\tstatic final String PREFIX = \"spring.cloud.deployer.\";\n\n\t/**\n\t * The deployment property for the count (number of app instances).\n\t * If not provided, a deployer should assume 1 instance.\n\t */\n\tstatic final String COUNT_PROPERTY_KEY = PREFIX + \"count\";\n\n\t/**\n\t * The deployment property for the group to which an app belongs.\n\t * If not provided, a deployer should assume no group.\n\t */\n\tstatic final String GROUP_PROPERTY_KEY = PREFIX + \"group\";\n\n\t/**\n\t * The deployment property that indicates if each app instance should have an index value\n\t * within a sequence from 0 to N-1, where N is the value of the {@value #COUNT_PROPERTY_KEY}\n\t * property. If not provided, a deployer should assume app instance indexing is not necessary.\n\t */\n\tstatic final String INDEXED_PROPERTY_KEY = PREFIX + \"indexed\";\n\n\t/**\n\t * The property to be set at each instance level to specify the sequence number\n\t * amongst 0 to N-1, where N is the value of the {@value #COUNT_PROPERTY_KEY} property.\n\t * Specified as CAPITAL_WITH_UNDERSCORES as this is typically passed as an environment\n\t * variable, but when targeting a Spring app, other variations may apply.\n\t *\n\t * @see #INDEXED_PROPERTY_KEY\n\t */\n\tstatic final String INSTANCE_INDEX_PROPERTY_KEY = \"INSTANCE_INDEX\";\n\n\t/**\n\t * The deployment property for the memory setting for the container that will run the app.\n\t * The memory is specified in <a href=\"https://en.wikipedia.org/wiki/Mebibyte\">Mebibytes</a>,\n\t * by default, with optional case-insensitive trailing unit 'm' and 'g' being supported,\n\t * for mebi- and giga- respectively.\n\t * <p>\n\t * 1 MiB = 2^20 bytes = 1024*1024 bytes vs. the decimal based 1MB = 10^6 bytes = 1000*1000 bytes,\n\t * <p>\n\t * Implementations are expected to translate this value to the target platform as faithfully as possible.\n\t *\n\t * @see org.springframework.cloud.deployer.spi.util.ByteSizeUtils\n\t */\n\tstatic final String MEMORY_PROPERTY_KEY = PREFIX + \"memory\";\n\n\t/**\n\t * The deployment property for the disk setting for the container that will run the app.\n\t * The memory is specified in <a href=\"https://en.wikipedia.org/wiki/Mebibyte\">Mebibytes</a>,\n\t * by default, with optional case-insensitive trailing unit 'm' and 'g' being supported,\n\t * for mebi- and giga- respectively.\n\t * <p>\n\t * 1 MiB = 2^20 bytes = 1024*1024 bytes vs. the decimal based 1MB = 10^6 bytes = 1000*1000 bytes,\n\t * <p>\n\t * Implementations are expected to translate this value to the target platform as faithfully as possible.\n\t *\n\t * @see org.springframework.cloud.deployer.spi.util.ByteSizeUtils\n\t */\n\tstatic final String DISK_PROPERTY_KEY = PREFIX + \"disk\";\n\n\t/**\n\t * The deployment property for the cpu setting for the container that will run the app.\n\t * The cpu is specified as whole multiples or decimal fractions of virtual cores. Some platforms will not\n\t * support setting cpu and will ignore this setting. Other platforms may require whole numbers and might\n\t * round up. Exactly how this property affects the deployments will vary between implementations.\n\t */\n\tstatic final String CPU_PROPERTY_KEY = PREFIX + \"cpu\";\n\n\t/**\n\t * Deploy an app using an {@link AppDeploymentRequest}. The returned id is\n\t * later used with {@link #undeploy(String)} or {@link #status(String)} to\n\t * undeploy an app or check its status, respectively.\n\t *\n\t * Implementations may perform this operation asynchronously; therefore a\n\t * successful deployment may not be assumed upon return. To determine the\n\t * status of a deployment, invoke {@link #status(String)}.\n\t *\n\t * @param request the app deployment request\n\t * @return the deployment id for the app\n\t * @throws IllegalStateException if the app has already been deployed\n\t */\n\tString deploy(AppDeploymentRequest request);\n\n\t/**\n\t * Un-deploy an app using its deployment id. Implementations may perform\n\t * this operation asynchronously; therefore a successful un-deployment may\n\t * not be assumed upon return. To determine the status of a deployment,\n\t * invoke {@link #status(String)}.\n\t *\n\t * @param id the app deployment id, as returned by {@link #deploy}\n\t * @throws IllegalStateException if the app has not been deployed\n\t */\n\tvoid undeploy(String id);\n\n\t/**\n\t * Return the {@link AppStatus} for an app represented by a deployment id.\n\t *\n\t * @param id the app deployment id, as returned by {@link #deploy}\n\t * @return the app deployment status\n\t */\n\tAppStatus status(String id);\n\n\t/**\n\t * Return the {@link AppStatus} for an app represented by a deployment id.\n\t *\n\t * @param id the app deployment id, as returned by {@link #deploy}\n\t * @return the app deployment status\n\t */\n\tdefault Mono<AppStatus> statusReactive(String id) {\n\t\treturn Mono.defer(() -> Mono.just(status(id)));\n\t}\n\n\t/**\n\t * Return the {@link AppStatus}s for an app represented by a deployment ids.\n\t *\n\t * @param ids the app deployment ids, as returned by {@link #deploy}\n\t * @return the app deployment statuses\n\t */\n\tdefault Flux<AppStatus> statusesReactive(String... ids) {\n\t\treturn Flux.fromArray(ids).flatMap(id -> statusReactive(id));\n\t}\n\n\t/**\n\t * Return the environment info for this deployer.\n\t *\n\t * @return the runtime environment info\n\t */\n\tRuntimeEnvironmentInfo environmentInfo();\n\n\t/**\n\t * Return the log of the application identified by the deployment id.\n\t * @param id the id of the deployment.\n\t * @return the application log\n\t */\n\tdefault String getLog(String id) {\n\t\tthrow new UnsupportedOperationException(\"'getLog' is not implemented.\");\n\t}\n\n\t/**\n\t * Scale an app according to given values.\n\t *\n\t * @param appScaleRequest an {@link AppScaleRequest}.\n\t */\n\tdefault void scale(AppScaleRequest appScaleRequest) {\n\t\tthrow new UnsupportedOperationException(\"'scale' is not implemented.\");\n\t}\n}\n"
  },
  {
    "path": "spring-cloud-deployer-spi/src/main/java/org/springframework/cloud/deployer/spi/app/AppInstanceStatus.java",
    "content": "/*\n * Copyright 2015-2016 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.app;\n\nimport java.util.Map;\n\n/**\n * Status for an individual app instance deployment.\n * The underlying instance may be backed by an application context in\n * the JVM, or by a remote process managed by a distributed runtime.\n *\n * @author Patrick Peralta\n * @author Mark Fisher\n */\npublic interface AppInstanceStatus {\n\n\t/**\n\t * Return a unique identifier for the deployed app.\n\t *\n\t * @return identifier for the deployed app\n\t */\n\tString getId();\n\n\t/**\n\t * Return the state of the deployed app instance.\n\t *\n\t * @return state of the deployed app instance\n\t */\n\tDeploymentState getState();\n\n\t/**\n\t * Return a map of attributes for the deployed app instance. The specific\n\t * keys/values returned are dependent on the runtime executing the app.\n\t * This may include extra information such as deployment location\n\t * or specific error messages in the case of failure.\n\t *\n\t * @return map of attributes for the deployed app\n\t */\n\tMap<String, String> getAttributes();\n}\n"
  },
  {
    "path": "spring-cloud-deployer-spi/src/main/java/org/springframework/cloud/deployer/spi/app/AppScaleRequest.java",
    "content": "/*\n * Copyright 2019 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.app;\n\nimport java.util.Map;\nimport java.util.Optional;\n\nimport org.springframework.lang.Nullable;\nimport org.springframework.util.Assert;\n\n/**\n * Representation of an app scale request. This includes the application's deployment Id, the number of desired\n * instances, and optionally, properties that may be applied during the scaling operation.\n *\n * @author David Turanski\n * @since 2.1.0\n */\npublic class AppScaleRequest {\n    private final int count;\n    private final String deploymentId;\n    private final Optional<Map<String,String>> properties;\n\n    /**\n     *\n     * @param deploymentId the unique deployment ID of the application.\n     * @param count the desired instance count.\n     */\n    public AppScaleRequest(String deploymentId, int count) {\n        this(deploymentId, count, null);\n    }\n\n    /**\n     *\n     * @param deploymentId the unique deployment ID of the application.\n     * @param count the desired instance count.\n     * @param properties optional properties that may be applied during the scale operation.\n     */\n    public AppScaleRequest(String deploymentId, int count, @Nullable  Map<String, String> properties) {\n        Assert.hasText(deploymentId,\"'deploymentId', must not be empty or null\");\n        Assert.state(count >= 0, \"'count' must be >= 0\");\n        this.deploymentId = deploymentId;\n        this.count = count;\n        this.properties = Optional.ofNullable(properties);\n    }\n\n    /**\n     *\n     * @return the desired instance count.\n     */\n    public int getCount() {\n        return count;\n    }\n\n    /**\n     *\n     * @return the deployment ID.\n     */\n    public String getDeploymentId() {\n        return deploymentId;\n    }\n\n    /**\n     *\n     * @return the {@link Optional} properties.\n     */\n    public Optional<Map<String, String>> getProperties() {\n        return properties;\n    }\n\n    @Override\n    public String toString() {\n        final StringBuffer sb = new StringBuffer(\"AppScaleRequest{\");\n        sb.append(\"count=\").append(count);\n        sb.append(\", deploymentId='\").append(deploymentId).append('\\'');\n        sb.append(\", properties=\").append(properties);\n        sb.append('}');\n        return sb.toString();\n    }\n}\n"
  },
  {
    "path": "spring-cloud-deployer-spi/src/main/java/org/springframework/cloud/deployer/spi/app/AppStatus.java",
    "content": "/*\n * Copyright 2016-2017 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.app;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\n\nimport org.springframework.util.Assert;\n\n/**\n * Status of an app which is initially constructed from an\n * {@link org.springframework.cloud.deployer.spi.core.AppDeploymentRequest} and\n * runtime deployment properties by a deployer during deployment. This status is\n * composed of an aggregate of all individual app instance deployments.\n * <p>\n * Consumers of the SPI obtain the app status via\n * {@link org.springframework.cloud.deployer.spi.app.AppDeployer#status(String)}\n * , whereas SPI implementations create instances of this class via\n * {@link AppStatus.Builder}.\n *\n * @author Patrick Peralta\n * @author Mark Fisher\n * @see AppInstanceStatus\n */\npublic class AppStatus {\n\n\t/**\n\t * The id of the app this status is for.\n\t */\n\tprivate final String deploymentId;\n\n\t/**\n\t * Map of {@link AppInstanceStatus} keyed by a unique identifier\n\t * for each app deployment instance.\n\t */\n\tprivate final Map<String, AppInstanceStatus> instances = new HashMap<String, AppInstanceStatus>();\n\n\tprivate final DeploymentState generalState;\n\n\t/**\n\t * Construct a new {@code AppStatus}.\n\t *\n\t * @param deploymentId id of the app this status is for\n\t * @param generalState a value for general state of the app, or {@literal null} if this should be derived from instances\n\t */\n\tprotected AppStatus(String deploymentId, DeploymentState generalState) {\n\t\tthis.deploymentId = deploymentId;\n\t\tthis.generalState = generalState;\n\t}\n\n\t/**\n\t * Return the app deployment id.\n\t *\n\t * @return app deployment id\n\t */\n\tpublic String getDeploymentId() {\n\t\treturn deploymentId;\n\t}\n\n\t/**\n\t * Return the deployment state for the the app. If the descriptor\n\t * indicates multiple instances, this state represents an aggregate\n\t * of all individual app instances.\n\t *\n\t * @return deployment state for the app\n\t */\n\tpublic DeploymentState getState() {\n\t\tif (generalState != null) {\n\t\t\treturn generalState;\n\t\t}\n\t\tSet<DeploymentState> states = new HashSet<>();\n\t\tfor (Map.Entry<String, AppInstanceStatus> entry : instances.entrySet()) {\n\t\t\tstates.add(entry.getValue().getState());\n\t\t}\n\t\tif (states.isEmpty()) {\n\t\t\treturn DeploymentState.unknown;\n\t\t}\n\t\tif (states.size() == 1) {\n\t\t\treturn states.iterator().next();\n\t\t}\n\t\tif (states.contains(DeploymentState.error)) {\n\t\t\treturn DeploymentState.error;\n\t\t}\n\t\tif (states.contains(DeploymentState.deploying)) {\n\t\t\treturn DeploymentState.deploying;\n\t\t}\n\t\tif (states.contains(DeploymentState.deployed) || states.contains(DeploymentState.partial)) {\n\t\t\treturn DeploymentState.partial;\n\t\t}\n\t\tif (states.contains(DeploymentState.failed)) {\n\t\t\treturn DeploymentState.failed;\n\t\t}\n\t\t// reaching here is unlikely; it would require some\n\t\t// combination of unknown, undeployed, complete\n\t\treturn DeploymentState.partial;\n\t}\n\n\tpublic String toString() {\n\t\treturn this.getState().name();\n\t}\n\n\t/**\n\t * Return a map of {@code AppInstanceStatus} keyed by a unique identifier\n\t * for each app instance.\n\t * @return map of {@code AppInstanceStatus}\n\t */\n\tpublic Map<String, AppInstanceStatus> getInstances() {\n\t\treturn Collections.unmodifiableMap(this.instances);\n\t}\n\n\tprivate void addInstance(String id, AppInstanceStatus status) {\n\t\tthis.instances.put(id, status);\n\t}\n\n\t/**\n\t * Return a {@code Builder} for {@code AppStatus}.\n\t * @param id of the app this status is for\n\t * @return {@code Builder} for {@code AppStatus}\n\t */\n\tpublic static Builder of(String id) {\n\t\treturn new Builder(id);\n\t}\n\n\t/**\n\t * Utility class constructing an instance of {@link AppStatus}\n\t * using a builder pattern.\n\t */\n\tpublic static class Builder {\n\n\t\tprivate final String id;\n\n\t\tprivate DeploymentState generalState;\n\n\t\tprivate List<AppInstanceStatus> statuses = new ArrayList<>();\n\n\t\t/**\n\t\t * Instantiates a new builder.\n\t\t *\n\t\t * @param id the app deployment id\n\t\t */\n\t\tprivate Builder(String id) {\n\t\t\tthis.id = id;\n\t\t}\n\n\t\t/**\n\t\t * Add an instance of {@code AppInstanceStatus} to build the status for\n\t\t * the app. This will be invoked once per individual app instance.\n\t\t *\n\t\t * @param instance status of individual app deployment\n\t\t * @return this {@code Builder}\n\t\t */\n\t\tpublic Builder with(AppInstanceStatus instance) {\n\t\t\tAssert.isNull(generalState, \"Can't build an AppStatus from app instances if generalState has been set\");\n\t\t\tstatuses.add(instance);\n\t\t\treturn this;\n\t\t}\n\n\t\t/**\n\t\t * Set the state of the app as a direct value. This is to be used when no information about instances could\n\t\t * be determined (<i>e.g.</i> general error condition).\n\t\t * @param generalState the deployment state to set\n\t\t * @return this {@code Builder}\n\t\t */\n\t\tpublic Builder generalState(DeploymentState generalState) {\n\t\t\tAssert.isTrue(statuses.isEmpty(), \"Can't build an AppStatus from general state if some instances have been added\");\n\t\t\tthis.generalState = generalState;\n\t\t\treturn this;\n\t\t}\n\n\t\t/**\n\t\t * Return a new instance of {@code AppStatus} based on\n\t\t * the provided individual app instances via\n\t\t * {@link #with(AppInstanceStatus)}.\n\t\t * @return new instance of {@code AppStatus}\n\t\t */\n\t\tpublic AppStatus build() {\n\t\t\tAppStatus status = new AppStatus(id, generalState);\n\t\t\tfor (AppInstanceStatus instanceStatus : statuses) {\n\t\t\t\tstatus.addInstance(instanceStatus.getId(), instanceStatus);\n\t\t\t}\n\t\t\treturn status;\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "spring-cloud-deployer-spi/src/main/java/org/springframework/cloud/deployer/spi/app/DeploymentState.java",
    "content": "/*\r\n * Copyright 2015-2016 the original author or authors.\r\n *\r\n * Licensed under the Apache License, Version 2.0 (the \"License\");\r\n * you may not use this file except in compliance with the License.\r\n * You may obtain a copy of the License at\r\n *\r\n *      https://www.apache.org/licenses/LICENSE-2.0\r\n *\r\n * Unless required by applicable law or agreed to in writing, software\r\n * distributed under the License is distributed on an \"AS IS\" BASIS,\r\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n * See the License for the specific language governing permissions and\r\n * limitations under the License.\r\n */\r\n\r\npackage org.springframework.cloud.deployer.spi.app;\r\n\r\n/**\r\n * Deployment states for apps and groups. These may represent the\r\n * state of:\r\n * <ul>\r\n *   <li>an entire group of apps</li>\r\n *   <li>the global state of a deployed app as part of a group</li>\r\n *   <li>the state of a particular instance of an app, in cases where\r\n *   {@code app.count > 1}</li>\r\n * </ul>\r\n *\r\n * @author Patrick Peralta\r\n * @author Eric Bottard\r\n * @author Mark Fisher\r\n */\r\npublic enum DeploymentState {\r\n\r\n\t/**\r\n\t * The app or group is being deployed. If there are multiple apps or\r\n\t * app instances, at least one of them is still being deployed.\r\n\t */\r\n\tdeploying,\r\n\r\n\t/**\r\n\t * All apps have been successfully deployed.\r\n\t */\r\n\tdeployed,\r\n\r\n\t/**\r\n\t * The app or group is known to the system, but is not currently deployed.\r\n\t */\r\n\tundeployed,\r\n\r\n\t/**\r\n\t * In the case of multiple apps, some have successfully deployed, while\r\n\t * others have not. This state does not apply for individual app instances.\r\n\t */\r\n\tpartial,\r\n\r\n\t/**\r\n\t * All apps have failed deployment.\r\n\t */\r\n\tfailed,\r\n\r\n\t/**\r\n\t * A system error occurred trying to determine deployment status.\r\n\t */\r\n\terror,\r\n\r\n\t/**\r\n\t * The app or group deployment is not known to the system.\r\n\t */\r\n\tunknown;\r\n\r\n}\r\n"
  },
  {
    "path": "spring-cloud-deployer-spi/src/main/java/org/springframework/cloud/deployer/spi/app/MultiStateAppDeployer.java",
    "content": "/*\n * Copyright 2017-2020 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage org.springframework.cloud.deployer.spi.app;\n\nimport java.util.Map;\n\nimport reactor.core.publisher.Mono;\n\n/**\n * Extension of the AppDeployer interface that adds an additional\n * method to return the DeploymentState for a collection of deployment ids.\n */\npublic interface MultiStateAppDeployer extends AppDeployer {\n\n\t/**\n\t * Return the {@link DeploymentState} for all the apps represented by\n\t * a collection of deployment ids.\n\t *\n\t * @param ids the collection of app deployment ids, as returned by {@link #deploy}\n\t * @return a Map of deployment id and DeploymentState\n\t */\n\tMap<String, DeploymentState> states(String ... ids);\n\n\t/**\n\t * Return the {@link DeploymentState} for all the apps represented by\n\t * a collection of deployment ids.\n\t *\n\t * @param ids the collection of app deployment ids, as returned by {@link #deploy}\n\t * @return a Map of deployment id and DeploymentState\n\t */\n\tdefault Mono<Map<String, DeploymentState>> statesReactive(String ... ids) {\n\t\treturn Mono.defer(() -> Mono.just(states(ids)));\n\t}\n}\n"
  },
  {
    "path": "spring-cloud-deployer-spi/src/main/java/org/springframework/cloud/deployer/spi/core/AppDefinition.java",
    "content": "/*\n * Copyright 2016 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.core;\n\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport org.springframework.core.style.ToStringCreator;\nimport org.springframework.util.Assert;\n\n/**\n * Definition of an app, including its name and its properties.\n *\n * A deployer may not modify properties in this class as those are meant for an\n * actual app as is. The deployer's only responsibility is to pass those\n * properties into a runtime environment in a way that they are available in a\n * running application. For example, they could be passed as env vars.\n *\n * @author Mark Fisher\n * @author Janne Valkealahti\n */\npublic class AppDefinition {\n\n\t/**\n\t * Name of the app.\n\t */\n\tprivate final String name;\n\n\t/**\n\t * Properties for this app.\n\t */\n\tprivate final Map<String, String> properties;\n\n\t/**\n\t * Construct an {@code AppDefinition}.\n\t *\n\t * @param name name of app\n\t * @param properties app properties; may be {@code null}\n\t */\n\tpublic AppDefinition(String name, Map<String, String> properties) {\n\t\tAssert.notNull(name, \"name must not be null\");\n\t\tthis.name = name;\n\t\tthis.properties = properties == null\n\t\t\t\t? Collections.<String, String>emptyMap()\n\t\t\t\t: Collections.unmodifiableMap(new HashMap<String, String>(properties));\n\t}\n\n\t/**\n\t * Return the name of this app.\n\t *\n\t * @return the app name\n\t */\n\tpublic String getName() {\n\t\treturn name;\n\t}\n\n\t/**\n\t * Gets the app definition properties. These properties are passed into a running app.\n\t *\n\t * @return the unmodifiable map of app properties\n\t */\n\tpublic Map<String, String> getProperties() {\n\t\treturn properties;\n\t}\n\n\t@Override\n\tpublic String toString() {\n\t\treturn new ToStringCreator(this)\n\t\t\t\t.append(\"name\", this.name)\n\t\t\t\t.append(\"properties\", this.properties).toString();\n\t}\n}\n"
  },
  {
    "path": "spring-cloud-deployer-spi/src/main/java/org/springframework/cloud/deployer/spi/core/AppDeploymentRequest.java",
    "content": "/*\n * Copyright 2016 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.core;\n\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Map;\n\nimport org.springframework.core.io.Resource;\nimport org.springframework.core.style.ToStringCreator;\nimport org.springframework.util.Assert;\n\n/**\n * Representation of an app deployment request. This includes the\n * {@link AppDefinition}, the {@link Resource} representing its deployable\n * artifact, and any deployment properties.\n *\n * Deployment properties are related to a specific implementation of the SPI\n * and will never be passed into an app itself. For example, a runtime container\n * may allow the definition of various settings for a context where the actual\n * app is executed, such as allowed memory, cpu or simply a way to define\n * colocation like node labeling.\n *\n * For passing properties into the app itself, use {@link AppDefinition#getProperties()}.\n * Those could be passed as env vars, or whatever approach is best for the target\n * platform. Each deployer implementation should clearly document how it handles\n * these properties.\n *\n * For passing command line arguments into the app itself, use {@link #commandlineArguments}.\n *\n * @author Mark Fisher\n * @author Janne Valkealahti\n * @author Oleg Zhurakousky\n */\npublic class AppDeploymentRequest {\n\n\t/**\n\t * App definition.\n\t */\n\tprivate final AppDefinition definition;\n\n\t/**\n\t * Resource representing the artifact of the underlying app.\n\t */\n\tprivate final Resource resource;\n\n\t/**\n\t * Map of deployment properties to be used by the deployer.\n\t */\n\tprivate final Map<String, String> deploymentProperties;\n\n\t/**\n\t * List of command line arguments for the target runtime of the app.\n\t */\n\tprivate final List<String> commandlineArguments;\n\n\t/**\n\t * Construct an {@code AppDeploymentRequest}.\n\t *\n\t * @param definition app definition\n\t * @param resource resource for the underlying app's artifact\n\t * @param deploymentProperties map of deployment properties; may be {@code null}\n\t */\n\tpublic AppDeploymentRequest(AppDefinition definition, Resource resource,\n\t\t\tMap<String, String> deploymentProperties) {\n\t\tthis(definition, resource, deploymentProperties, null);\n\t}\n\n\t/**\n\t * Construct an {@code AppDeploymentRequest}.\n\t *\n\t * @param definition app definition\n\t * @param resource resource for the underlying app's artifact\n\t * @param deploymentProperties map of deployment properties; may be {@code null}\n\t * @param commandlineArguments set of command line arguments; may be {@code null}\n\t */\n\tpublic AppDeploymentRequest(AppDefinition definition, Resource resource,\n\t\t\tMap<String, String> deploymentProperties, List<String> commandlineArguments) {\n\t\tAssert.notNull(definition, \"definition must not be null\");\n\t\tAssert.notNull(resource, \"resource must not be null\");\n\t\tthis.definition = definition;\n\t\tthis.resource = resource;\n\t\tthis.deploymentProperties = deploymentProperties == null\n\t\t\t\t? Collections.<String, String>emptyMap()\n\t\t\t\t: Collections.unmodifiableMap(deploymentProperties);\n\t\tthis.commandlineArguments = commandlineArguments == null\n\t\t\t\t? Collections.<String>emptyList()\n\t\t\t\t: Collections.unmodifiableList(commandlineArguments);\n\t}\n\n\t/**\n\t * Construct an {@code AppDeploymentRequest} with no deployment properties.\n\t *\n\t * @param definition app definition\n\t * @param resource resource for the underlying app's artifact\n\t */\n\tpublic AppDeploymentRequest(AppDefinition definition, Resource resource) {\n\t\tthis(definition, resource, null);\n\t}\n\n\t/**\n\t * @see #definition\n\t * @return application definition\n\t */\n\tpublic AppDefinition getDefinition() {\n\t\treturn definition;\n\t}\n\n\t/**\n\t * @see #resource\n\t * @return the resource\n\t */\n\tpublic Resource getResource() {\n\t\treturn resource;\n\t}\n\n\t/**\n\t * @see #deploymentProperties\n\t * @return the deployment properties\n\t */\n\tpublic Map<String, String> getDeploymentProperties() {\n\t\treturn deploymentProperties;\n\t}\n\n\t/**\n\t * @see #commandlineArguments\n\t * @return the commandline arguments\n\t */\n\tpublic List<String> getCommandlineArguments() {\n\t\treturn commandlineArguments;\n\t}\n\n\t@Override\n\tpublic String toString(){\n\t\treturn new ToStringCreator(this)\n\t\t\t\t.append(\"commandlineArguments\", this.commandlineArguments)\n\t\t\t\t.append(\"deploymentProperties\", this.deploymentProperties)\n\t\t\t\t.append(\"definition\", this.definition)\n\t\t\t\t.append(\"resource\", this.resource)\n\t\t\t\t.toString();\n\t}\n}\n"
  },
  {
    "path": "spring-cloud-deployer-spi/src/main/java/org/springframework/cloud/deployer/spi/core/RuntimeEnvironmentInfo.java",
    "content": "/*\n * Copyright 2017 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.core;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport org.springframework.cloud.deployer.spi.util.RuntimeVersionUtils;\nimport org.springframework.core.SpringVersion;\nimport org.springframework.util.Assert;\n\n/**\n * Class used to communicate the runtime environment info.\n *\n * @author Thomas Risberg\n */\npublic class RuntimeEnvironmentInfo {\n\n\t/**\n\t * The SPI version used by this implementation.\n\t */\n\tprivate String spiVersion;\n\n\t/**\n\t * The name of this implementation (could be simple class name).\n\t */\n\tprivate String implementationName;\n\n\t/**\n\t * The version of this implementation.\n\t */\n\tprivate String implementationVersion;\n\n\t/**\n\t * The platform type for this implementation.\n\t */\n\tprivate String platformType;\n\n\t/**\n\t * The platform API version for this implementation.\n\t */\n\tprivate String platformApiVersion;\n\n\t/**\n\t * The client library version used by this implementation.\n\t */\n\tprivate String platformClientVersion;\n\n\t/**\n\t * The version running on the host of the platform used by this implementation.\n\t */\n\tprivate String platformHostVersion;\n\n\t/**\n\t * The Java version used by this implementation.\n\t */\n\tprivate String javaVersion;\n\n\t/**\n\t * The Spring Framework version used by this implementation.\n\t */\n\tprivate String springVersion;\n\n\t/**\n\t * The Spring Boot version used by this implementation.\n\t */\n\tprivate String springBootVersion;\n\n\t/**\n\t * Platform specific properties\n\t */\n\tprivate Map<String, String> platformSpecificInfo = new HashMap<>();\n\n\tprivate RuntimeEnvironmentInfo(Class spiClass, String implementationName, String implementationVersion,\n\t                               String platformType, String platformApiVersion, String platformClientVersion,\n\t                               String platformHostVersion, Map<String, String> platformSpecificInfo) {\n\t\tAssert.notNull(spiClass, \"spiClass is required\");\n\t\tAssert.notNull(implementationName, \"implementationName is required\");\n\t\tAssert.notNull(implementationVersion, \"implementationVersion is required\");\n\t\tAssert.notNull(platformType, \"platformType is required\");\n\t\tAssert.notNull(platformApiVersion, \"platformApiVersion is required\");\n\t\tAssert.notNull(platformClientVersion, \"platformClientVersion is required\");\n\t\tAssert.notNull(platformHostVersion, \"platformHostVersion is required\");\n\t\tthis.spiVersion = RuntimeVersionUtils.getVersion(spiClass);\n\t\tthis.implementationName = implementationName;\n\t\tthis.implementationVersion = implementationVersion;\n\t\tthis.platformType = platformType;\n\t\tthis.platformApiVersion = platformApiVersion;\n\t\tthis.platformClientVersion = platformClientVersion;\n\t\tthis.platformHostVersion = platformHostVersion;\n\t\tthis.javaVersion = System.getProperty(\"java.version\");\n\t\tthis.springVersion = SpringVersion.getVersion();\n\t\tthis.springBootVersion = RuntimeVersionUtils.getSpringBootVersion();\n\t\tthis.platformSpecificInfo.putAll(platformSpecificInfo);\n\t}\n\n\tpublic String getSpiVersion() {\n\t\treturn spiVersion;\n\t}\n\n\tpublic String getImplementationName() {\n\t\treturn implementationName;\n\t}\n\n\tpublic String getImplementationVersion() {\n\t\treturn implementationVersion;\n\t}\n\n\tpublic String getPlatformType() {\n\t\treturn platformType;\n\t}\n\n\tpublic String getPlatformApiVersion() {\n\t\treturn platformApiVersion;\n\t}\n\n\tpublic String getPlatformClientVersion() {\n\t\treturn platformClientVersion;\n\t}\n\n\tpublic String getPlatformHostVersion() {\n\t\treturn platformHostVersion;\n\t}\n\n\tpublic String getJavaVersion() {\n\t\treturn javaVersion;\n\t}\n\n\tpublic String getSpringVersion() {\n\t\treturn springVersion;\n\t}\n\n\tpublic String getSpringBootVersion() {\n\t\treturn springBootVersion;\n\t}\n\n\tpublic Map<String, String> getPlatformSpecificInfo() {\n\t\treturn platformSpecificInfo;\n\t}\n\n\tpublic static class Builder {\n\n\t\tprivate Class spiClass;\n\n\t\tprivate String implementationName;\n\n\t\tprivate String implementationVersion;\n\n\t\tprivate String platformType;\n\n\t\tprivate String platformApiVersion;\n\n\t\tprivate String platformClientVersion;\n\n\t\tprivate String platformHostVersion;\n\n\t\tprivate Map<String, String> platformSpecificInfo = new HashMap<>();\n\n\t\tpublic Builder() {\n\t\t}\n\n\t\tpublic Builder spiClass(Class spiClass) {\n\t\t\tthis.spiClass = spiClass;\n\t\t\treturn this;\n\t\t}\n\n\t\tpublic Builder implementationName(String implementationName) {\n\t\t\tthis.implementationName = implementationName;\n\t\t\treturn this;\n\t\t}\n\n\t\tpublic Builder implementationVersion(String implementationVersion) {\n\t\t\tthis.implementationVersion = implementationVersion;\n\t\t\treturn this;\n\t\t}\n\n\t\tpublic Builder platformType(String platformType) {\n\t\t\tthis.platformType = platformType;\n\t\t\treturn this;\n\t\t}\n\n\t\tpublic Builder platformApiVersion(String platformApiVersion) {\n\t\t\tthis.platformApiVersion = platformApiVersion;\n\t\t\treturn this;\n\t\t}\n\n\t\tpublic Builder platformClientVersion(String platformClientVersion) {\n\t\t\tthis.platformClientVersion = platformClientVersion;\n\t\t\treturn this;\n\t\t}\n\n\t\tpublic Builder platformHostVersion(String platformHostVersion) {\n\t\t\tthis.platformHostVersion = platformHostVersion;\n\t\t\treturn this;\n\t\t}\n\n\t\tpublic Builder addPlatformSpecificInfo(String key, String value) {\n\t\t\tthis.platformSpecificInfo.put(key, value);\n\t\t\treturn this;\n\t\t}\n\n\t\tpublic RuntimeEnvironmentInfo build() {\n\t\t\treturn new RuntimeEnvironmentInfo(spiClass, implementationName, implementationVersion, platformType,\n\t\t\t\t\tplatformApiVersion, platformClientVersion, platformHostVersion, platformSpecificInfo);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "spring-cloud-deployer-spi/src/main/java/org/springframework/cloud/deployer/spi/scheduler/CreateScheduleException.java",
    "content": "/*\n * Copyright 2018-2019 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.scheduler;\n\n/**\n * Thrown when a schedule fails to be created on the scheduler infrastructure.\n *\n * @author Glenn Renfro\n * @author Ilayaperumal Gopinathan\n */\npublic class CreateScheduleException extends SchedulerException{\n\n\tpublic CreateScheduleException(String scheduleName, Throwable t) {\n\t\tsuper(String.format(\"Failed to create schedule %s\",scheduleName), t);\n\t}\n}\n"
  },
  {
    "path": "spring-cloud-deployer-spi/src/main/java/org/springframework/cloud/deployer/spi/scheduler/ScheduleInfo.java",
    "content": "/*\n * Copyright 2018-2019 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.scheduler;\n\nimport java.util.Map;\n\n/**\n * A {@code Schedule} represents the association between the task definition and the\n * times it is to be executed. The application, in this case, is represented by\n * the taskDefinitionName. The schedulerProperties, contain the information to calculate\n * when the next time the application should run (cron expression, etc)\".\n *\n * @author Glenn Renfro\n * @author Ilayaperumal Gopinathan\n */\npublic class ScheduleInfo {\n\t/**\n\t * The name to be associated with the Schedule instance.\n\t */\n\tprivate String scheduleName;\n\n\t/**\n\t * The task definition name associated with Schedule instance.\n\t */\n\tprivate String taskDefinitionName;\n\n\t/**\n\t * Schedule specific information returned from the Scheduler implementation\n\t */\n\tprivate Map<String, String> scheduleProperties;\n\n\n\tpublic String getScheduleName() {\n\t\treturn scheduleName;\n\t}\n\n\tpublic void setScheduleName(String scheduleName) {\n\t\tthis.scheduleName = scheduleName;\n\t}\n\n\tpublic String getTaskDefinitionName() {\n\t\treturn taskDefinitionName;\n\t}\n\n\tpublic void setTaskDefinitionName(String taskDefinitionName) {\n\t\tthis.taskDefinitionName = taskDefinitionName;\n\t}\n\n\tpublic Map<String, String> getScheduleProperties() {\n\t\treturn scheduleProperties;\n\t}\n\n\tpublic void setScheduleProperties(Map<String, String> scheduleProperties) {\n\t\tthis.scheduleProperties = scheduleProperties;\n\t}\n\n\t@Override\n\tpublic boolean equals(Object o) {\n\t\tif (this == o) return true;\n\t\tif (!(o instanceof ScheduleInfo)) return false;\n\n\t\tScheduleInfo that = (ScheduleInfo) o;\n\n\t\tif (scheduleName != null ? !scheduleName.equals(that.scheduleName) : that.scheduleName != null)\n\t\t\treturn false;\n\n\t\treturn true;\n\t}\n\n\t@Override\n\tpublic String toString() {\n\t\treturn \"ScheduleInfo{\" +\n\t\t\t\t\"scheduleName='\" + scheduleName + '\\'' +\n\t\t\t\t\", taskDefinitionName='\" + taskDefinitionName + '\\'' +\n\t\t\t\t\", scheduleProperties=\" + scheduleProperties +\n\t\t\t\t'}';\n\t}\n}\n"
  },
  {
    "path": "spring-cloud-deployer-spi/src/main/java/org/springframework/cloud/deployer/spi/scheduler/ScheduleRequest.java",
    "content": "/*\n * Copyright 2018-2019 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.scheduler;\n\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Map;\n\nimport org.springframework.cloud.deployer.spi.core.AppDefinition;\nimport org.springframework.cloud.deployer.spi.core.AppDeploymentRequest;\nimport org.springframework.core.io.Resource;\nimport org.springframework.core.style.ToStringCreator;\n\n/**\n * Representation of a schedule request. This includes the {@link AppDefinition}\n * and any deployment properties.\n *\n * Deployment properties are related to a specific implementation of the SPI\n * and will never be passed into an app itself. For example, a runtime container\n * may allow the definition of various settings for a context where the actual\n * app is executed, such as allowed memory, cpu or simply a way to define\n * collocation like node labeling.\n *\n * For passing properties into the app itself, use {@link AppDefinition#getProperties()}.\n * Those could be passed as env vars, or whatever approach is best for the target\n * platform. Each deployer implementation should clearly document how it handles\n * these properties.\n *\n * For passing command line arguments into the app itself, use {@link #commandlineArguments}.\n *\n * @author Glenn Renfro\n * @author Ilayaperumal Gopinathan\n */\npublic class ScheduleRequest extends AppDeploymentRequest{\n\n\t/**\n\t * The name of the Schedule.\n\t */\n\tprivate final String scheduleName;\n\n\tprivate Map<String, String> schedulerProperties;\n\n\t/**\n\t * Construct an {@code AppDeploymentRequest}.\n\t *\n\t * @param definition app definition.\n\t * @param schedulerProperties properties that contain scheduler specific information.\n\t * @param deploymentProperties map of deployment properties; may be {@code null}.\n\t * @param scheduleName the name associated with the schedule.\n\t * @param resource the resource associated with the request.\n\t */\n\t@Deprecated\n\tpublic ScheduleRequest(AppDefinition definition, Map<String, String> schedulerProperties,\n\t\t\tMap<String, String> deploymentProperties, String scheduleName, Resource resource) {\n\t\tthis(definition, schedulerProperties, deploymentProperties, null, scheduleName, resource);\n\t}\n\n\t/**\n\t * Construct an {@code AppDeploymentRequest}.\n\t *\n\t * @param definition app definition\n\t * @param schedulerProperties properties that contain scheduler specific information.\n\t * @param deploymentProperties map of deployment properties; may be {@code null}\n\t * @param commandlineArguments set of command line arguments; may be {@code null}\n\t * @param scheduleName the name associated with the schedule.\n\t * @param resource the resource associated with the request.\n\t */\n\t@Deprecated\n\tpublic ScheduleRequest(AppDefinition definition,  Map<String, String> schedulerProperties,\n\t\t\tMap<String, String> deploymentProperties, List<String> commandlineArguments,\n\t\t\tString scheduleName, Resource resource) {\n\t\tsuper(definition, resource, deploymentProperties, commandlineArguments);\n\t\tthis.scheduleName = scheduleName;\n\t\tthis.schedulerProperties = schedulerProperties == null\n\t\t\t\t? Collections.emptyMap()\n\t\t\t\t: Collections.unmodifiableMap(schedulerProperties);\n\t}\n\n\t/**\n\t * Construct an {@code AppDeploymentRequest}.\n\t *\n\t * @param definition app definition.\n\t * @param deploymentProperties map of deployment properties; may be {@code null}.\n\t * @param scheduleName the name associated with the schedule.\n\t * @param resource the resource associated with the request.\n\t */\n\tpublic ScheduleRequest(AppDefinition definition,\n\t\t\tMap<String, String> deploymentProperties, String scheduleName, Resource resource) {\n\t\tthis(definition, deploymentProperties, (List<String>) null, scheduleName, resource);\n\t}\n\n\t/**\n\t * Construct an {@code AppDeploymentRequest}.\n\t *\n\t * @param definition app definition\n\t * @param deploymentProperties map of deployment properties; may be {@code null}\n\t * @param commandlineArguments set of command line arguments; may be {@code null}\n\t * @param scheduleName the name associated with the schedule.\n\t * @param resource the resource associated with the request.\n\t */\n\tpublic ScheduleRequest(AppDefinition definition,\n\t\t\tMap<String, String> deploymentProperties, List<String> commandlineArguments,\n\t\t\tString scheduleName, Resource resource) {\n\t\tsuper(definition, resource, deploymentProperties, commandlineArguments);\n\t\tthis.scheduleName = scheduleName;\n\t}\n\n\t/**\n\t * @see #scheduleName\n\t * @return the schedule name\n\t */\n\tpublic String getScheduleName() {\n\t\treturn scheduleName;\n\t}\n\n\t@Deprecated\n\tpublic Map<String, String> getSchedulerProperties() {\n\t\treturn schedulerProperties;\n\t}\n\n\t@Deprecated\n\tpublic void setSchedulerProperties(Map<String, String> schedulerProperties) {\n\t\tthis.schedulerProperties = schedulerProperties;\n\t}\n\n\t@Override\n\tpublic String toString(){\n\t\treturn new ToStringCreator(this)\n\t\t\t\t.append(\"scheduleName\", this.scheduleName)\n\t\t\t\t.append(\"schedulerProperties\", this.schedulerProperties)\n\t\t\t\t.toString();\n\t}\n}\n"
  },
  {
    "path": "spring-cloud-deployer-spi/src/main/java/org/springframework/cloud/deployer/spi/scheduler/Scheduler.java",
    "content": "/*\n * Copyright 2018-2019 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.scheduler;\n\nimport java.util.List;\n\nimport org.springframework.core.io.Resource;\n\n/**\n * A {@code Scheduler} is a component that provides a way to register the execution of a\n * {@link ScheduleRequest} with an underlying scheduler system (Quartz, etc).\n *\n * @author Glenn Renfro\n * @author Ilayaperumal Gopinathan\n *\n */\npublic interface Scheduler {\n\n\t/**\n\t * Registers the {@link ScheduleRequest} to be executed based on the\n\t * cron expression provided.  If an error occurs during schedule creation\n\t * then a {@link CreateScheduleException} should be thrown.\n\t *\n\t * @param scheduleRequest A request representing a sched-uable\n\t * artifact({@link org.springframework.cloud.deployer.spi.core.AppDefinition},\n\t * the {@link Resource}), schedule properties, and deployment properties.\n\t */\n\tvoid schedule(ScheduleRequest scheduleRequest);\n\n\t/**\n\t *  Deletes a schedule that has been created.  If an error occurs during\n\t *  un-scheduling then a {@link UnScheduleException} should be thrown.\n\t *\n\t * @param scheduleName the name of the schedule to be removed.\n\t */\n\tvoid unschedule(String scheduleName);\n\n\t/**\n\t * List all of the Schedules associated with the provided AppDefinition.\n\t * If an error occurs during list generation then a {@link SchedulerException}\n\t * should be thrown.\n\t *\n\t * @param taskDefinitionName to retrieve {@link ScheduleInfo}s for a specified taskDefinitionName.\n\t * @return A List of {@link ScheduleInfo}s configured for the provided taskDefinitionName.\n\t */\n\tList<ScheduleInfo> list(String taskDefinitionName) ;\n\n\t/**\n\t * List all of the {@link ScheduleInfo}s registered with the system.\n\t * If an error occurs during list generation then a {@link SchedulerException}\n\t * should be thrown.\n\t *\n\t * @return A List of {@link ScheduleInfo}s for the given system.\n\t */\n\tList<ScheduleInfo> list();\n}\n\n"
  },
  {
    "path": "spring-cloud-deployer-spi/src/main/java/org/springframework/cloud/deployer/spi/scheduler/SchedulerException.java",
    "content": "/*\n * Copyright 2018-2019 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.scheduler;\n\n/**\n * Base Exception class for Spring Cloud Scheduler.\n *\n * @author Glenn Renfro\n * @author Ilayaperumal Gopinathan\n */\npublic class SchedulerException extends RuntimeException{\n\n\tpublic SchedulerException(String exceptionMessage) {\n\t\tsuper(exceptionMessage);\n\t}\n\n\tpublic SchedulerException(String exceptionMessage, Throwable t) {\n\t\tsuper(exceptionMessage, t);\n\t}\n}\n"
  },
  {
    "path": "spring-cloud-deployer-spi/src/main/java/org/springframework/cloud/deployer/spi/scheduler/SchedulerPropertyKeys.java",
    "content": "/*\n * Copyright 2018-2019 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.scheduler;\n\n/**\n * Spring Cloud Scheduler property keys.\n *\n * @author Glenn Renfro\n * @author Ilayaperumal Gopinathan\n */\npublic class SchedulerPropertyKeys {\n\n\tpublic static final String PREFIX = \"spring.cloud.scheduler.\";\n\n\t/**\n\t * Scheduler cron property key prefix.\n\t */\n\tpublic static final String CRON_PREFIX = PREFIX + \"cron.\";\n\n\t/**\n\t * Scheduler cron expression property key.\n\t */\n\tpublic static final String CRON_EXPRESSION = CRON_PREFIX + \"expression\";\n\n}\n"
  },
  {
    "path": "spring-cloud-deployer-spi/src/main/java/org/springframework/cloud/deployer/spi/scheduler/UnScheduleException.java",
    "content": "/*\n * Copyright 2018-2019 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.scheduler;\n\n/**\n * Thrown when a schedule fails to be unscheduled on the scheduler infrastructure.\n *\n * @author Glenn Renfro\n * @author Ilayaperumal Gopinathan\n */\npublic class UnScheduleException extends SchedulerException {\n\tpublic UnScheduleException(String scheduleName) {\n\t\tsuper(String.format(\"Failed to unschedule %s\", scheduleName));\n\t}\n\n\tpublic UnScheduleException(String scheduleName, Throwable t) {\n\t\tsuper(String.format(\"Failed to unschedule %s\", scheduleName), t);\n\t}\n}\n"
  },
  {
    "path": "spring-cloud-deployer-spi/src/main/java/org/springframework/cloud/deployer/spi/task/LaunchState.java",
    "content": "/*\r\n * Copyright 2016 the original author or authors.\r\n *\r\n * Licensed under the Apache License, Version 2.0 (the \"License\");\r\n * you may not use this file except in compliance with the License.\r\n * You may obtain a copy of the License at\r\n *\r\n *      https://www.apache.org/licenses/LICENSE-2.0\r\n *\r\n * Unless required by applicable law or agreed to in writing, software\r\n * distributed under the License is distributed on an \"AS IS\" BASIS,\r\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n * See the License for the specific language governing permissions and\r\n * limitations under the License.\r\n */\r\n\r\npackage org.springframework.cloud.deployer.spi.task;\r\n\r\n/**\r\n * Launch states for a Task.\r\n *\r\n * @author Mark Fisher\r\n */\r\npublic enum LaunchState {\r\n\r\n\t/**\r\n\t * The task launch has been requested, but it is not yet known to be in a running state.\r\n\t */\r\n\tlaunching,\r\n\r\n\t/**\r\n\t * The task has been successfully launched, but has not yet completed.\r\n\t */\r\n\trunning,\r\n\r\n\t/**\r\n\t * The task has been cancelled.\r\n\t */\r\n\tcancelled,\r\n\r\n\t/**\r\n\t * The task completed execution.\r\n\t */\r\n\tcomplete,\r\n\r\n\t/**\r\n\t * The task failed to launch.\r\n\t */\r\n\tfailed,\r\n\r\n\t/**\r\n\t * A system error occurred trying to determine launch status.\r\n\t */\r\n\terror,\r\n\r\n\t/**\r\n\t * The task is not known to the system.\r\n\t */\r\n\tunknown;\r\n\r\n}\r\n"
  },
  {
    "path": "spring-cloud-deployer-spi/src/main/java/org/springframework/cloud/deployer/spi/task/TaskLauncher.java",
    "content": "/*\n * Copyright 2016-2019 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.task;\n\nimport org.springframework.cloud.deployer.spi.core.AppDeploymentRequest;\nimport org.springframework.cloud.deployer.spi.core.RuntimeEnvironmentInfo;\n\n/**\n * SPI defining a runtime environment capable of launching and managing the\n * lifecycle of tasks. The term 'task' in the context of a {@code TaskLauncher}\n * is merely a runtime representation of a container or application wherein the\n * task should be executed.\n *\n * The SPI itself doesn't expect the launcher to keep state of launched tasks,\n * meaning it doesn't need to reconstruct all existing {@link TaskStatus}es or\n * the IDs needed to resolve a {@link TaskStatus}. The responsibility for\n * keeping track of the state of all existing tasks lies to whomever is using\n * this SPI. It is unrealistic to expect the launcher to be able to store enough\n * information using the underlying infrastructure to reconstruct the full\n * history of task executions.\n *\n * @author Mark Fisher\n * @author Janne Valkealahti\n * @author David Turanski\n * @author Ilayaperumal Gopinathan\n */\npublic interface TaskLauncher {\n\n\t/**\n\t * Launch a task for the provided {@link AppDeploymentRequest}. The returned\n\t * id may later be used with {@link #cancel(String)} or\n\t * {@link #status(String)} to cancel a task or get its status,\n\t * respectively.\n\t *\n\t * Implementations may perform this operation asynchronously; therefore a\n\t * successful launch may not be assumed upon return. To determine the status\n\t * of a launch, invoke {@link #status(String)}.\n\t *\n\t * @param request the task launch request\n\t * @return the id for the launched task\n\t */\n\tString launch(AppDeploymentRequest request);\n\n\t/**\n\t * Cancel the task corresponding to the provided id.\n\t *\n\t * Implementations may perform this operation asynchronously; therefore a\n\t * successful cancellation may not be assumed upon return. To determine the\n\t * status of a cancellation, invoke {@link #status(String)}.\n\t *\n\t * @param id the task id, as returned by {@link #launch(AppDeploymentRequest)}\n\t */\n\tvoid cancel(String id);\n\n\t/**\n\t * Returns the {@link TaskStatus} for a task represented by the provided id.\n\t *\n\t * @param id the task id, as returned by {@link #launch(AppDeploymentRequest)}\n\t * @return the task status\n\t */\n\tTaskStatus status(String id);\n\n\t/**\n\t * Attempt to clean up any app execution resources that are associated with a task launch represented by the\n\t * provided task execution id.\n\t *\n\t * Implementations can choose to ignore this request if the underlying platform does not have any resources\n\t * associated with the task launch. Implementations may perform this operation asynchronously; therefore a\n\t * successful clean up may not be assumed upon return.\n\t *\n\t * @param id the task id, as returned by {@link #launch(AppDeploymentRequest)}\n\t */\n\tvoid cleanup(String id);\n\n\t/**\n\t * Attempt to clean up any app resources that are associated with a task app represented by the provided\n\t * appName. Any app execution resources from all task launches for this app should be cleaned up as well.\n\t *\n\t * Implementations can choose to ignore this request if the underlying platform does not have any resources\n\t * associated with the task app and if there are no task launch resources created for this app. Implementations\n\t * may perform this operation asynchronously; therefore a successful clean up may not be assumed upon return.\n\t *\n\t * @param appName the app name as specified in {@link org.springframework.cloud.deployer.spi.core.AppDefinition#name}\n\t *                from the {@link #launch(AppDeploymentRequest)}\n\t */\n\tvoid destroy(String appName);\n\n\t/**\n\t * Return the environment info for this launcher/deployer.\n\t *\n\t * @return the deployer environment info\n\t */\n\tRuntimeEnvironmentInfo environmentInfo();\n\n\t/**\n\t * Implementations may limit the number of concurrent task executions.\n\t *\n\t * @return the maximum concurrent task executions allowed.\n\t */\n\tdefault int getMaximumConcurrentTasks() {\n\t\tthrow new UnsupportedOperationException(\"'getMaximumConcurrentTasks' is not implemented.\");\n\t};\n\n\t/**\n\t *\n\t * @return the count of currently running task executions if known.\n\t */\n\tdefault int getRunningTaskExecutionCount() {\n\t\tthrow new UnsupportedOperationException(\"'getRunningTaskExecutionCount' is not implemented.\");\n\t}\n\n\t/**\n\t * Return the log of the application identified by the task ID.\n\t * The ID can be specific to the platform where the task is launched.\n\t * @param id the id of the task.\n\t * @return the task application log\n\t */\n\tdefault String getLog(String id) {\n\t\tthrow new UnsupportedOperationException(\"'getLog' is not implemented.\");\n\t}\n}\n"
  },
  {
    "path": "spring-cloud-deployer-spi/src/main/java/org/springframework/cloud/deployer/spi/task/TaskStatus.java",
    "content": "/*\n * Copyright 2016-2017 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.task;\n\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.Map;\n\n/**\n * Status of a task launch which is initially constructed from an\n * {@link org.springframework.cloud.deployer.spi.core.AppDeploymentRequest} and\n * runtime properties by a {@link TaskLauncher}.\n * <p>\n * Consumers of the SPI obtain task status via\n * {@link org.springframework.cloud.deployer.spi.task.TaskLauncher#status},\n * whereas SPI implementations create instances of this class via\n * {@link #TaskStatus(String, LaunchState, Map)}\n *\n * @author Patrick Peralta\n * @author Mark Fisher\n */\npublic class TaskStatus {\n\n\t/**\n\t * The id of the task this status is for.\n\t */\n\tprivate final String id;\n\n\t/**\n\t * The {@link LaunchState} of the task.\n\t */\n\tprivate final LaunchState state;\n\n\t/**\n\t * A map of attributes for the task.\n\t */\n\tprivate final Map<String, String> attributes;\n\n\t/**\n\t * Construct a new {@code TaskStatus}.\n\t * @param id the id of the task launch this status is for\n\t * @param state the {@link LaunchState} of the task\n\t * @param attributes map of attributes for the task\n\t */\n\tpublic TaskStatus(String id, LaunchState state, Map<String, String> attributes) {\n\t\tthis.id = id;\n\t\tthis.state = state;\n\t\tthis.attributes = attributes == null ? Collections.<String, String> emptyMap()\n\t\t\t\t: Collections.unmodifiableMap(new HashMap<>(attributes));\n\t}\n\n\t/**\n\t * Return the task launch id for the task.\n\t * @return task launch id\n\t */\n\tpublic String getTaskLaunchId() {\n\t\treturn id;\n\t}\n\n\t/**\n\t * Return the state for the the task.\n\t *\n\t * @return state for the task\n\t */\n\tpublic LaunchState getState() {\n\t\treturn this.state;\n\t}\n\n\t/**\n\t * Return a string representation of this status.\n\t *\n\t * @return string representation of this status\n\t */\n\tpublic String toString() {\n\t\treturn this.getState().name();\n\t}\n\n\t/**\n\t * Return a map of attributes for the launched task. The specific keys and\n\t * values returned are dependent on the runtime where the task has been\n\t * launched. This may include extra information such as execution location\n\t * or specific error messages in the case of failure.\n\t *\n\t * @return map of attributes for the task\n\t */\n\tpublic Map<String, String> getAttributes() {\n\t\treturn this.attributes;\n\t}\n}\n"
  },
  {
    "path": "spring-cloud-deployer-spi/src/main/java/org/springframework/cloud/deployer/spi/util/ByteSizeUtils.java",
    "content": "/*\n * Copyright 2016 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage org.springframework.cloud.deployer.spi.util;\n\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\n\n/**\n * Utility class for dealing with parseable byte sizes, such as memory and disk limits.\n *\n * @author Eric Bottard\n */\npublic class ByteSizeUtils {\n\n\tprivate ByteSizeUtils() {\n\n\t}\n\n\tprivate static final Pattern SIZE_PATTERN = Pattern.compile(\"(?<amount>\\\\d+)(?<unit>(m|g)?)\", Pattern.CASE_INSENSITIVE);\n\n\t/**\n\t * Return the number of mebibytes (1024*1024) denoted by the given text, where an optional case-insensitive unit of\n\t * 'm' or 'g' can be used to mean mebi- or gebi- bytes, respectively. Lack of unit assumes mebibytes.\n\t * @param text The text to parse\n\t * @return mebibytes\n\t */\n\tpublic  static long parseToMebibytes(String text) {\n\t\tMatcher matcher = SIZE_PATTERN.matcher(text);\n\t\tif (!matcher.matches()) {\n\t\t\tthrow new IllegalArgumentException(String.format(\"Could not parse '%s' as a byte size.\" +\n\t\t\t\t\" Expected a number with optional 'm' or 'g' suffix\", text));\n\t\t}\n\t\tlong size = Long.parseLong(matcher.group(\"amount\"));\n\t\tif (matcher.group(\"unit\").equalsIgnoreCase(\"g\")) {\n\t\t\tsize *= 1024L;\n\t\t}\n\t\treturn size;\n\t}\n}\n"
  },
  {
    "path": "spring-cloud-deployer-spi/src/main/java/org/springframework/cloud/deployer/spi/util/CommandLineTokenizer.java",
    "content": "/*\n * Copyright 2016 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.util;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\n\n/**\n * A general purpose tokenizer for \"command line\" arrays. Allows tokenizing a single String into an array of args,\n * splitting the array on space characters and trimming. Quoting using single and double quotes is supported, in which\n * case those quotes can be escaped using a backslash character.\n *\n * @author Eric Bottard\n */\npublic class CommandLineTokenizer {\n\n\n\tprivate final char[] buffer;\n\n\tprivate int pos;\n\n\tprivate static final char ESCAPE_CHAR = '\\\\';\n\n\tprivate final List<String> args = new ArrayList<>();\n\n\tpublic CommandLineTokenizer(String value) {\n\t\tthis.buffer = value.toCharArray();\n\t\ttokenize();\n\t}\n\n\tpublic List<String> getArgs() {\n\t\treturn Collections.unmodifiableList(args);\n\t}\n\n\tprivate void tokenize() {\n\t\twhile (pos < buffer.length) {\n\t\t\teatWhiteSpace();\n\t\t\tif (pos < buffer.length) {\n\t\t\t\teatArg();\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate void eatWhiteSpace() {\n\t\twhile (pos < buffer.length && buffer[pos] == ' ') {\n\t\t\tpos++;\n\t\t}\n\t}\n\n\tprivate void eatArg() {\n\t\tchar endDelimiter;\n\t\tif (buffer[pos] == '\\'' || buffer[pos] == '\"') {\n\t\t\tendDelimiter = buffer[pos++];\n\t\t}\n\t\telse {\n\t\t\tendDelimiter = ' ';\n\t\t}\n\n\t\tStringBuilder sb = new StringBuilder();\n\t\twhile (pos < buffer.length && buffer[pos] != endDelimiter) {\n\t\t\tif (buffer[pos] == ESCAPE_CHAR) {\n\t\t\t\tsb.append(processCharacterEscapeCodes(endDelimiter));\n\t\t\t}\n\t\t\telse {\n\t\t\t\tsb.append(buffer[pos++]);\n\t\t\t}\n\t\t}\n\t\tif (pos == buffer.length && endDelimiter != ' ') {\n\t\t\tthrow new IllegalStateException(String.format(\"Ran out of input in [%s], expected closing [%s]\", new String(buffer), endDelimiter));\n\t\t}\n\t\telse if (endDelimiter != ' ' && buffer[pos] == endDelimiter) {\n\t\t\tpos++;\n\t\t}\n\t\targs.add(sb.toString());\n\t}\n\n\t/**\n\t * When the escape character is encountered, consume and return the escaped sequence. Note that depending on which\n\t * end delimiter is currently in use, not all combinations need to be escaped\n\t * @param endDelimiter the current endDelimiter\n\t */\n\tprivate char processCharacterEscapeCodes(char endDelimiter) {\n\t\tpos++;\n\t\tif (pos >= buffer.length) {\n\t\t\tthrow new IllegalStateException(\"Ran out of input in escape sequence\");\n\t\t}\n\t\tif (buffer[pos] == ESCAPE_CHAR) {\n\t\t\tpos++; // consume the second escape char\n\t\t\treturn ESCAPE_CHAR;\n\t\t}\n\t\telse if (buffer[pos] == endDelimiter) {\n\t\t\tpos++;\n\t\t\treturn endDelimiter;\n\t\t}\n\t\telse {\n\t\t\t// Not an actual escape. Do not increment pos,\n\t\t\t// and return the \\ we consumed at the very beginning\n\t\t\treturn ESCAPE_CHAR;\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "spring-cloud-deployer-spi/src/main/java/org/springframework/cloud/deployer/spi/util/RuntimeVersionUtils.java",
    "content": "/*\n * Copyright 2017 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.util;\n\nimport org.springframework.util.StringUtils;\n\n/**\n * Utility class to be used for generating version info for various libraries.\n *\n * @author Thomas Risberg\n */\npublic class RuntimeVersionUtils {\n\n\tpublic static String getSpringBootVersion() {\n\t\tClass springApp;\n\t\ttry {\n\t\t\tspringApp = Class.forName(\"org.springframework.boot.SpringApplication\");\n\t\t} catch (ClassNotFoundException e) {\n\t\t\treturn \"unknown\";\n\t\t}\n\t\treturn getVersion(springApp);\n\t}\n\n\tpublic static String getVersion(final Class<?> source) {\n\t\tif (source == null) {\n\t\t\treturn \"null\";\n\t\t}\n\t\tPackage sourcePackage = source.getPackage();\n\t\tif (sourcePackage == null) {\n\t\t\treturn \"unknown\";\n\t\t}\n\t\tString version = source.getPackage().getImplementationVersion();\n\t\tif (!StringUtils.hasText(version)) {\n\t\t\treturn \"unknown\";\n\t\t}\n\t\treturn version;\n\t}\n}\n"
  },
  {
    "path": "spring-cloud-deployer-spi/src/test/java/org/springframework/cloud/deployer/spi/app/AppDeployerTests.java",
    "content": "/*\n * Copyright 2019 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.app;\n\nimport org.junit.jupiter.api.Test;\n\nimport org.springframework.cloud.deployer.spi.core.AppDeploymentRequest;\nimport org.springframework.cloud.deployer.spi.core.RuntimeEnvironmentInfo;\n\nimport static org.assertj.core.api.Assertions.fail;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\n/**\n * Tests for {@link AppDeployer}\n */\npublic class AppDeployerTests {\n\n\t@Test\n\tpublic void testAppDeployerGetLogDefaultMethod() {\n\t\tAppDeployer customAppDeployer = new AppDeployer() {\n\t\t\t@Override\n\t\t\tpublic String deploy(AppDeploymentRequest request) {\n\t\t\t\treturn \"Deployment request received.\";\n\t\t\t}\n\n\t\t\t@Override\n\t\t\tpublic void undeploy(String id) {\n\t\t\t}\n\n\t\t\t@Override\n\t\t\tpublic AppStatus status(String id) {\n\t\t\t\treturn AppStatus.of(\"id\").build();\n\t\t\t}\n\n\t\t\t@Override\n\t\t\tpublic RuntimeEnvironmentInfo environmentInfo() {\n\t\t\t\treturn new RuntimeEnvironmentInfo.Builder().build();\n\t\t\t}\n\n\t\t\t@Override\n\t\t\tpublic void scale(AppScaleRequest appScaleRequest) {\n\n\t\t\t}\n\t\t};\n\t\ttry {\n\t\t\tcustomAppDeployer.getLog(\"test\");\n\t\t\tfail(\"\");\n\t\t}\n\t\tcatch (UnsupportedOperationException e) {\n\t\t\tassertEquals(e.getMessage(), \"'getLog' is not implemented.\");\n\t\t}\n\n\t}\n}\n"
  },
  {
    "path": "spring-cloud-deployer-spi/src/test/java/org/springframework/cloud/deployer/spi/app/RuntimeEnvironmentInfoBuilderTests.java",
    "content": "/*\n * Copyright 2017 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.app;\n\nimport org.junit.jupiter.api.Test;\n\nimport org.springframework.cloud.deployer.spi.core.RuntimeEnvironmentInfo;\nimport org.springframework.cloud.deployer.spi.util.RuntimeVersionUtils;\nimport org.springframework.core.SpringVersion;\n\nimport static org.assertj.core.api.AssertionsForClassTypes.assertThat;\n\n\n/**\n * Tests for constructing a {@link RuntimeEnvironmentInfo}\n */\npublic class RuntimeEnvironmentInfoBuilderTests {\n\n\t@Test\n\tpublic void testCreatingRuntimeEnvironmentInfo() {\n\t\tRuntimeEnvironmentInfo rei = new RuntimeEnvironmentInfo.Builder()\n\t\t\t\t.spiClass(AppDeployer.class)\n\t\t\t\t.implementationName(\"TestDeployer\")\n\t\t\t\t.implementationVersion(\"1.0.0\")\n\t\t\t\t.platformClientVersion(\"1.2.0\")\n\t\t\t\t.platformHostVersion(\"1.1.0\")\n\t\t\t\t.platformType(\"Test\")\n\t\t\t\t.platformApiVersion(\"1\")\n\t\t\t\t.addPlatformSpecificInfo(\"foo\", \"bar\")\n\t\t\t\t.build();\n\t\tassertThat(rei.getSpiVersion()).isEqualTo(RuntimeVersionUtils.getVersion(AppDeployer.class));\n\t\tassertThat(rei.getImplementationName()).isEqualTo(\"TestDeployer\");\n\t\tassertThat(rei.getImplementationVersion()).isEqualTo(\"1.0.0\");\n\t\tassertThat(rei.getPlatformType()).isEqualTo(\"Test\");\n\t\tassertThat(rei.getPlatformApiVersion()).isEqualTo(\"1\");\n\t\tassertThat(rei.getPlatformClientVersion()).isEqualTo(\"1.2.0\");\n\t\tassertThat(rei.getPlatformHostVersion()).isEqualTo(\"1.1.0\");\n\t\tassertThat(rei.getJavaVersion()).isEqualTo(System.getProperty(\"java.version\"));\n\t\tassertThat(rei.getSpringVersion()).isEqualTo(SpringVersion.getVersion());\n\t\tassertThat(rei.getSpringBootVersion()).isEqualTo(RuntimeVersionUtils.getSpringBootVersion());\n\t\tassertThat(rei.getPlatformSpecificInfo().get(\"foo\")).isEqualTo(\"bar\");\n\t}\n}\n"
  },
  {
    "path": "spring-cloud-deployer-spi/src/test/java/org/springframework/cloud/deployer/spi/task/TaskLauncherTests.java",
    "content": "/*\n * Copyright 2019 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.task;\n\nimport org.junit.jupiter.api.Test;\n\nimport org.springframework.cloud.deployer.spi.core.AppDeploymentRequest;\nimport org.springframework.cloud.deployer.spi.core.RuntimeEnvironmentInfo;\n\nimport static org.assertj.core.api.Assertions.fail;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\n/**\n * Tests for {@link TaskLauncher}\n */\npublic class TaskLauncherTests {\n\n\t@Test\n\tpublic void testTaskLauncherDefaultMethods() {\n\n\t\tTaskLauncher taskLauncher = new TaskLauncher() {\n\t\t\t@Override\n\t\t\tpublic String launch(AppDeploymentRequest request) {\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\t@Override\n\t\t\tpublic void cancel(String id) {\n\n\t\t\t}\n\n\t\t\t@Override\n\t\t\tpublic TaskStatus status(String id) {\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\t@Override\n\t\t\tpublic void cleanup(String id) {\n\n\t\t\t}\n\n\t\t\t@Override\n\t\t\tpublic void destroy(String appName) {\n\n\t\t\t}\n\n\t\t\t@Override\n\t\t\tpublic RuntimeEnvironmentInfo environmentInfo() {\n\t\t\t\treturn null;\n\t\t\t}\n\t\t};\n\t\ttry {\n\t\t\ttaskLauncher.getLog(\"test\");\n\t\t\tfail(\"\");\n\t\t}\n\t\tcatch (UnsupportedOperationException e) {\n\t\t\tassertEquals(e.getMessage(), \"'getLog' is not implemented.\");\n\t\t}\n\t\ttry {\n\t\t\ttaskLauncher.getRunningTaskExecutionCount();\n\t\t\tfail(\"\");\n\t\t}\n\t\tcatch (UnsupportedOperationException e) {\n\t\t\tassertEquals(e.getMessage(), \"'getRunningTaskExecutionCount' is not implemented.\");\n\t\t}\n\t\ttry {\n\t\t\ttaskLauncher.getMaximumConcurrentTasks();\n\t\t\tfail(\"\");\n\t\t}\n\t\tcatch (UnsupportedOperationException e) {\n\t\t\tassertEquals(e.getMessage(), \"'getMaximumConcurrentTasks' is not implemented.\");\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "spring-cloud-deployer-spi/src/test/java/org/springframework/cloud/deployer/spi/util/ByteSizeUtilsTests.java",
    "content": "/*\n * Copyright 2016 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage org.springframework.cloud.deployer.spi.util;\n\nimport org.junit.jupiter.api.Test;\n\nimport static org.assertj.core.api.AssertionsForClassTypes.assertThat;\nimport static org.assertj.core.api.AssertionsForClassTypes.assertThatExceptionOfType;\n\n/**\n * Unit tests for {@link ByteSizeUtils}.\n *\n * @author Eric Bottard\n */\npublic class ByteSizeUtilsTests {\n\n\t@Test\n\tpublic void testParse() {\n\t\tassertThat(ByteSizeUtils.parseToMebibytes(\"1\")).isEqualTo(1L);\n\t\tassertThat(ByteSizeUtils.parseToMebibytes(\"2m\")).isEqualTo(2L);\n\t\tassertThat(ByteSizeUtils.parseToMebibytes(\"20M\")).isEqualTo(20L);\n\t\tassertThat(ByteSizeUtils.parseToMebibytes(\"1000g\")).isEqualTo(1024_000L);\n\t\tassertThat(ByteSizeUtils.parseToMebibytes(\"1G\")).isEqualTo(1024L);\n\t}\n\n\t@Test\n\tpublic void testNotANumber() {\n\t\tassertThatExceptionOfType(IllegalArgumentException.class).isThrownBy(() ->\n\t\t\t\tByteSizeUtils.parseToMebibytes(\"wat?124\"));\n\t}\n\n\t@Test\n\tpublic void testUnsupportedUnit() {\n\t\tassertThatExceptionOfType(IllegalArgumentException.class).isThrownBy(() ->\n\t\t\t\tByteSizeUtils.parseToMebibytes(\"1PB\"));\n\t}\n\n}\n"
  },
  {
    "path": "spring-cloud-deployer-spi/src/test/java/org/springframework/cloud/deployer/spi/util/CommandLineTokenizerTests.java",
    "content": "/*\n * Copyright 2016 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.util;\n\nimport java.util.Arrays;\n\nimport org.junit.jupiter.api.Assertions;\nimport org.junit.jupiter.api.Test;\n\nimport static org.assertj.core.api.AssertionsForClassTypes.assertThatExceptionOfType;\n\n/**\n * Unit tests for {@link CommandLineTokenizer}.\n *\n * @author Eric Bottard\n */\npublic class CommandLineTokenizerTests {\n\n\t@Test\n\tpublic void testSimple() {\n\t\tCommandLineTokenizer tokenizer = new CommandLineTokenizer(\"a b cdef\");\n\t\tAssertions.assertEquals(Arrays.asList(\"a\", \"b\", \"cdef\"), tokenizer.getArgs());\n\t\ttokenizer = new CommandLineTokenizer(\"  a   b cdef  \");\n\t\tAssertions.assertEquals(Arrays.asList(\"a\", \"b\", \"cdef\"), tokenizer.getArgs());\n\t}\n\n\t@Test\n\tpublic void testQuotes() {\n\t\tCommandLineTokenizer tokenizer = new CommandLineTokenizer(\"  'a   b' cdef gh \\\"i j\\\"\");\n\t\tAssertions.assertEquals(Arrays.asList(\"a   b\", \"cdef\", \"gh\", \"i j\"), tokenizer.getArgs());\n\t}\n\n\t@Test\n\tpublic void testEscapes() {\n\t\tCommandLineTokenizer tokenizer = new CommandLineTokenizer(\"  'a \\\\' \\\\\\\" b' cdef gh \\\"i \\\\\\\"j\\\"\");\n\t\tAssertions.assertEquals(Arrays.asList(\"a ' \\\\\\\" b\", \"cdef\", \"gh\", \"i \\\"j\"), tokenizer.getArgs());\n\t}\n\n\t@Test\n\tpublic void testUnbalancedQuotes() {\n\t\tassertThatExceptionOfType(IllegalStateException.class).isThrownBy(() -> {\n\t\t\tCommandLineTokenizer tokenizer = new CommandLineTokenizer(\" 'ab cd' 'ef gh\");\n\t\t});\n\t}\n\n\n}\n"
  },
  {
    "path": "spring-cloud-deployer-spi-scheduler-test-app/Dockerfile",
    "content": "FROM java:8-alpine\n\nARG JAR_FILE\n\nADD target/${JAR_FILE} spring-cloud-deployer-spi-scheduler-test-app.jar\n\nENTRYPOINT [\"java\", \"-jar\", \"/spring-cloud-deployer-spi-scheduler-test-app.jar\"]\n"
  },
  {
    "path": "spring-cloud-deployer-spi-scheduler-test-app/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd\">\n\t<modelVersion>4.0.0</modelVersion>\n\t<parent>\n\t\t<groupId>org.springframework.cloud</groupId>\n\t\t<artifactId>spring-cloud-deployer-parent</artifactId>\n\t\t<version>3.0.0-SNAPSHOT</version>\n\t\t<relativePath>..</relativePath>\n\t</parent>\n\t<artifactId>spring-cloud-deployer-spi-scheduler-test-app</artifactId>\n\t<properties>\n\t\t<start-class>org.springframework.cloud.deployer.spi.scheduler.test.app.SchedulerIntegrationTestApplication</start-class>\n\t\t<java.version>17</java.version>\n\t\t<docker.image.prefix>springcloud</docker.image.prefix>\n\t\t<dockerfile-maven-plugin.version>1.3.6</dockerfile-maven-plugin.version>\n\t</properties>\n\t<dependencies>\n\t\t<!--suppress VulnerableLibrariesLocal -->\n\t\t<dependency>\n\t\t\t<groupId>org.springframework.boot</groupId>\n\t\t\t<artifactId>spring-boot-starter-web</artifactId>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.springframework.boot</groupId>\n\t\t\t<artifactId>spring-boot-starter-actuator</artifactId>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.springframework.boot</groupId>\n\t\t\t<artifactId>spring-boot-configuration-processor</artifactId>\n\t\t\t<!--optional so that it does its job while compiling, but is not included in\n\t\t\tthe resulting artifact -->\n\t\t\t<optional>true</optional>\n\t\t</dependency>\n\t</dependencies>\n\t<build>\n\t\t<plugins>\n\t\t\t<plugin>\n\t\t\t\t<groupId>org.springframework.boot</groupId>\n\t\t\t\t<artifactId>spring-boot-maven-plugin</artifactId>\n\t\t\t\t<version>${spring-boot.version}</version>\n\t\t\t\t<executions>\n\t\t\t\t\t<execution>\n\t\t\t\t\t\t<goals>\n\t\t\t\t\t\t\t<goal>repackage</goal>\n\t\t\t\t\t\t</goals>\n\t\t\t\t\t\t<configuration>\n\t\t\t\t\t\t\t<classifier>exec</classifier>\n\t\t\t\t\t\t</configuration>\n\t\t\t\t\t</execution>\n\t\t\t\t</executions>\n\t\t\t</plugin>\n\t\t\t<plugin>\n\t\t\t\t<groupId>com.spotify</groupId>\n\t\t\t\t<artifactId>dockerfile-maven-plugin</artifactId>\n\t\t\t\t<version>${dockerfile-maven-plugin.version}</version>\n\t\t\t\t<configuration>\n\t\t\t\t\t<repository>${docker.image.prefix}/${project.artifactId}</repository>\n\t\t\t\t\t<tag>${project.version}</tag>\n\t\t\t\t\t<buildArgs>\n\t\t\t\t\t\t<JAR_FILE>${project.build.finalName}-exec.jar</JAR_FILE>\n\t\t\t\t\t</buildArgs>\n\t\t\t\t</configuration>\n\t\t\t</plugin>\n\t\t</plugins>\n\t</build>\n</project>\n"
  },
  {
    "path": "spring-cloud-deployer-spi-scheduler-test-app/src/main/java/org/springframework/cloud/deployer/spi/scheduler/test/app/SchedulerIntegrationTest.java",
    "content": "/*\r\n * Copyright 2018-2019 the original author or authors.\r\n *\r\n * Licensed under the Apache License, Version 2.0 (the \"License\");\r\n * you may not use this file except in compliance with the License.\r\n * You may obtain a copy of the License at\r\n *\r\n *      https://www.apache.org/licenses/LICENSE-2.0\r\n *\r\n * Unless required by applicable law or agreed to in writing, software\r\n * distributed under the License is distributed on an \"AS IS\" BASIS,\r\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n * See the License for the specific language governing permissions and\r\n * limitations under the License.\r\n */\r\n\r\npackage org.springframework.cloud.deployer.spi.scheduler.test.app;\r\n\r\nimport jakarta.annotation.PostConstruct;\r\n\r\nimport org.slf4j.Logger;\r\nimport org.slf4j.LoggerFactory;\r\n\r\nimport org.springframework.beans.factory.annotation.Autowired;\r\nimport org.springframework.boot.context.properties.EnableConfigurationProperties;\r\nimport org.springframework.context.annotation.Configuration;\r\nimport org.springframework.util.Assert;\r\n\r\n/**\r\n * An app that can misbehave, useful for integration testing of app deployers.\r\n *\r\n * @author Glenn Renfro\r\n * @author Ilayaperumal Gopinathan\r\n */\r\n@EnableConfigurationProperties(SchedulerIntegrationTestProperties.class)\r\n@Configuration\r\npublic class SchedulerIntegrationTest {\r\n\r\n\tprotected final Logger logger = LoggerFactory.getLogger(this.getClass());\r\n\r\n\t@Autowired\r\n\tprivate SchedulerIntegrationTestProperties properties;\r\n\r\n\t@PostConstruct\r\n\tpublic void init() throws InterruptedException {\r\n\t\tString parameterThatMayNeedEscaping = properties.getParameterThatMayNeedEscaping();\r\n\t\tif (parameterThatMayNeedEscaping != null && !SchedulerIntegrationTestProperties.FUNNY_CHARACTERS.equals(parameterThatMayNeedEscaping)) {\r\n\t\t\tthrow new IllegalArgumentException(String.format(\"Expected 'parameterThatMayNeedEscaping' value to be equal to '%s', but was '%s'\", SchedulerIntegrationTestProperties.FUNNY_CHARACTERS, parameterThatMayNeedEscaping));\r\n\t\t}\r\n\r\n\t\tString commandLineArgValueThatMayNeedEscaping = properties.getCommandLineArgValueThatMayNeedEscaping();\r\n\t\tif (commandLineArgValueThatMayNeedEscaping != null && !SchedulerIntegrationTestProperties.FUNNY_CHARACTERS.equals(commandLineArgValueThatMayNeedEscaping)) {\r\n\t\t\tthrow new IllegalArgumentException(String.format(\"Expected 'commandLineArgValueThatMayNeedEscaping' value to be equal to '%s', but was '%s'\", SchedulerIntegrationTestProperties.FUNNY_CHARACTERS, commandLineArgValueThatMayNeedEscaping));\r\n\t\t}\r\n\r\n\t\tAssert.notNull(properties.getInstanceIndex(), \"instanceIndex should have been set by deployer or runtime\");\r\n\r\n\t\tif (properties.getMatchInstances().isEmpty() || properties.getMatchInstances().contains(properties.getInstanceIndex())) {\r\n\t\t\tlogger.info(\"Waiting for %dms before allowing further initialization and actuator startup...\", properties.getInitDelay());\r\n\t\t\tThread.sleep(properties.getInitDelay());\r\n\t\t\tlogger.info(\"... done\");\r\n\t\t\tif (properties.getKillDelay() >= 0) {\r\n\t\t\t\tlogger.info(\"Will kill this process in %dms%n\", properties.getKillDelay());\r\n\t\t\t\tnew Thread() {\r\n\r\n\t\t\t\t\t@Override\r\n\t\t\t\t\tpublic void run() {\r\n\t\t\t\t\t\ttry {\r\n\t\t\t\t\t\t\tThread.sleep(properties.getKillDelay());\r\n\t\t\t\t\t\t\tSystem.exit(properties.getExitCode());\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\tcatch (InterruptedException e) {\r\n\t\t\t\t\t\t\tthrow new RuntimeException(e);\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t}.start();\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "spring-cloud-deployer-spi-scheduler-test-app/src/main/java/org/springframework/cloud/deployer/spi/scheduler/test/app/SchedulerIntegrationTestApplication.java",
    "content": "/*\r\n * Copyright 2018-2019 the original author or authors.\r\n *\r\n * Licensed under the Apache License, Version 2.0 (the \"License\");\r\n * you may not use this file except in compliance with the License.\r\n * You may obtain a copy of the License at\r\n *\r\n *      https://www.apache.org/licenses/LICENSE-2.0\r\n *\r\n * Unless required by applicable law or agreed to in writing, software\r\n * distributed under the License is distributed on an \"AS IS\" BASIS,\r\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n * See the License for the specific language governing permissions and\r\n * limitations under the License.\r\n */\r\n\r\npackage org.springframework.cloud.deployer.spi.scheduler.test.app;\r\n\r\nimport org.springframework.boot.SpringApplication;\r\nimport org.springframework.boot.autoconfigure.SpringBootApplication;\r\n\r\n/**\r\n * Main application runner. Will keep the main method running, to simulate a long running process.\r\n *\r\n * @author Glenn Renfro\r\n * @author Ilayaperumal Gopinathan\r\n */\r\n@SpringBootApplication\r\npublic class SchedulerIntegrationTestApplication {\r\n\r\n\tpublic static void main(String[] args) {\r\n\t\tSpringApplication.run(SchedulerIntegrationTestApplication.class, args);\r\n\t}\r\n}\r\n"
  },
  {
    "path": "spring-cloud-deployer-spi-scheduler-test-app/src/main/java/org/springframework/cloud/deployer/spi/scheduler/test/app/SchedulerIntegrationTestProperties.java",
    "content": "/*\r\n * Copyright 2018-2019 the original author or authors.\r\n *\r\n * Licensed under the Apache License, Version 2.0 (the \"License\");\r\n * you may not use this file except in compliance with the License.\r\n * You may obtain a copy of the License at\r\n *\r\n *      https://www.apache.org/licenses/LICENSE-2.0\r\n *\r\n * Unless required by applicable law or agreed to in writing, software\r\n * distributed under the License is distributed on an \"AS IS\" BASIS,\r\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n * See the License for the specific language governing permissions and\r\n * limitations under the License.\r\n */\r\n\r\npackage org.springframework.cloud.deployer.spi.scheduler.test.app;\r\n\r\nimport java.util.HashSet;\r\nimport java.util.Set;\r\n\r\nimport org.springframework.beans.factory.annotation.Value;\r\nimport org.springframework.boot.context.properties.ConfigurationProperties;\r\n\r\n/**\r\n * Configuration properties for the IntegrationTestProcessor app.\r\n *\r\n * @author Glenn Renfro\r\n * @author Ilayaperumal Gopinathan\r\n */\r\n@ConfigurationProperties\r\npublic class SchedulerIntegrationTestProperties {\r\n\r\n\tpublic static final String FUNNY_CHARACTERS = \"&'\\\"|< é\\\\(\";\r\n\r\n\t/**\r\n\t * The delay in milliseconds to stall the initialization of this app.\r\n\t * Useful for testing the 'deploying' state of a app.\r\n\t */\r\n\tprivate int initDelay = 0;\r\n\r\n\t/**\r\n\t * The delay in milliseconds after which this app will kill itself.\r\n\t * <p>-1 means don't kill</p>\r\n\t */\r\n\tprivate int killDelay = -1;\r\n\r\n\t/**\r\n\t * The exit code used when this app will kill itself.\r\n\t * <p>Set this to 0 for normal exit</p>\r\n\t */\r\n\tprivate int exitCode = 1;\r\n\r\n\t/**\r\n\t * If not empty, only the app intances whose number(s) are contained in this set\r\n\t * will behave according to the other configuration parameters.\r\n\t */\r\n\tprivate Set<Integer> matchInstances = new HashSet<>();\r\n\r\n\t/**\r\n\t * If not null, this property will be tested against {@link #FUNNY_CHARACTERS}.\r\n\t * This makes sure that a deployer knows how to properly propagate application properties, including\r\n\t * those that contain chars that often require some form of escaping.\r\n\t */\r\n\tprivate String parameterThatMayNeedEscaping;\r\n\r\n\t/**\r\n\t * If not null, this property will be tested against {@link #FUNNY_CHARACTERS}.\r\n\t * This makes sure that a deployer knows how to properly propagate deployment properties, including\r\n\t * those that contain chars that often require some form of escaping.\r\n\t */\r\n\tprivate String commandLineArgValueThatMayNeedEscaping;\r\n\r\n\t@Value(\"${INSTANCE_INDEX:${CF_INSTANCE_INDEX:0}}\")\r\n\tprivate Integer instanceIndex;\r\n\r\n\tpublic int getInitDelay() {\r\n\t\treturn initDelay;\r\n\t}\r\n\r\n\tpublic void setInitDelay(int initDelay) {\r\n\t\tthis.initDelay = initDelay;\r\n\t}\r\n\r\n\tpublic int getKillDelay() {\r\n\t\treturn killDelay;\r\n\t}\r\n\r\n\tpublic void setKillDelay(int killDelay) {\r\n\t\tthis.killDelay = killDelay;\r\n\t}\r\n\r\n\tpublic int getExitCode() {\r\n\t\treturn exitCode;\r\n\t}\r\n\r\n\tpublic void setExitCode(int exitCode) {\r\n\t\tthis.exitCode = exitCode;\r\n\t}\r\n\r\n\tpublic Set<Integer> getMatchInstances() {\r\n\t\treturn matchInstances;\r\n\t}\r\n\r\n\tpublic void setMatchInstances(Set<Integer> matchInstances) {\r\n\t\tthis.matchInstances = matchInstances;\r\n\t}\r\n\r\n\tpublic String getParameterThatMayNeedEscaping() {\r\n\t\treturn parameterThatMayNeedEscaping;\r\n\t}\r\n\r\n\tpublic void setParameterThatMayNeedEscaping(String parameterThatMayNeedEscaping) {\r\n\t\tthis.parameterThatMayNeedEscaping = parameterThatMayNeedEscaping;\r\n\t}\r\n\r\n\tpublic Integer getInstanceIndex() {\r\n\t\treturn instanceIndex;\r\n\t}\r\n\r\n\tpublic void setInstanceIndex(Integer instanceIndex) {\r\n\t\tthis.instanceIndex = instanceIndex;\r\n\t}\r\n\r\n\tpublic String getCommandLineArgValueThatMayNeedEscaping() {\r\n\t\treturn commandLineArgValueThatMayNeedEscaping;\r\n\t}\r\n\r\n\tpublic void setCommandLineArgValueThatMayNeedEscaping(String commandLineArgValueThatMayNeedEscaping) {\r\n\t\tthis.commandLineArgValueThatMayNeedEscaping = commandLineArgValueThatMayNeedEscaping;\r\n\t}\r\n}\r\n"
  },
  {
    "path": "spring-cloud-deployer-spi-scheduler-test-app/src/main/resources/application.properties",
    "content": "management.endpoint.shutdown.enabled=true\nmanagement.endpoints.web.exposure.include=*\n"
  },
  {
    "path": "spring-cloud-deployer-spi-test/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd\">\n\t<modelVersion>4.0.0</modelVersion>\n\n\t<artifactId>spring-cloud-deployer-spi-test</artifactId>\n\t<packaging>jar</packaging>\n\t<name>spring-cloud-deployer-spi-test</name>\n\t<description>Base Tests for Spring Cloud Deployer SPI Implementations</description>\n\n\t<parent>\n\t\t<groupId>org.springframework.cloud</groupId>\n\t\t<artifactId>spring-cloud-deployer-parent</artifactId>\n\t\t<version>3.0.0-SNAPSHOT</version>\n\t\t<relativePath>..</relativePath>\n\t</parent>\n\n\t<properties>\n\t\t<integration-test-app.version>${project.version}</integration-test-app.version>\n\t</properties>\n\n\t<build>\n\t\t<resources>\n\t\t\t<resource>\n\t\t\t\t<directory>src/main/resources</directory>\n\t\t\t\t<filtering>true</filtering>\n\t\t\t\t<includes>\n\t\t\t\t\t<include>integration-test-app.properties</include>\n\t\t\t\t</includes>\n\t\t\t</resource>\n\t\t</resources>\n\t</build>\n\t<dependencies>\n\t\t<dependency>\n\t\t\t<groupId>org.springframework.cloud</groupId>\n\t\t\t<artifactId>spring-cloud-deployer-spi</artifactId>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.springframework.cloud</groupId>\n\t\t\t<artifactId>spring-cloud-deployer-resource-maven</artifactId>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.springframework.cloud</groupId>\n\t\t\t<artifactId>spring-cloud-deployer-spi-test-app</artifactId>\n\t\t\t<version>3.0.0-SNAPSHOT</version>\n\t\t</dependency>\n\t\t<!--suppress VulnerableLibrariesLocal -->\n\t\t<dependency>\n\t\t\t<groupId>org.springframework.boot</groupId>\n\t\t\t<artifactId>spring-boot-starter-test</artifactId>\n\t\t\t<!-- Note: this is compile, as this project is a test framework itself-->\n\t\t\t<scope>compile</scope>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.awaitility</groupId>\n\t\t\t<artifactId>awaitility</artifactId>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.assertj</groupId>\n\t\t\t<artifactId>assertj-core</artifactId>\n\t\t</dependency>\n\t</dependencies>\n\n</project>\n"
  },
  {
    "path": "spring-cloud-deployer-spi-test/src/main/java/org/springframework/cloud/deployer/spi/scheduler/test/AbstractSchedulerIntegrationJUnit5Tests.java",
    "content": "/*\n * Copyright 2018-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage org.springframework.cloud.deployer.spi.scheduler.test;\n\nimport java.io.IOException;\nimport java.lang.reflect.Method;\nimport java.time.Duration;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.Properties;\nimport java.util.UUID;\n\nimport org.assertj.core.api.AbstractAssert;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.TestInfo;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.boot.context.properties.ConfigurationProperties;\nimport org.springframework.boot.test.context.SpringBootTest;\nimport org.springframework.boot.test.context.SpringBootTest.WebEnvironment;\nimport org.springframework.cloud.deployer.resource.maven.MavenProperties;\nimport org.springframework.cloud.deployer.resource.maven.MavenResource;\nimport org.springframework.cloud.deployer.spi.core.AppDefinition;\nimport org.springframework.cloud.deployer.spi.scheduler.CreateScheduleException;\nimport org.springframework.cloud.deployer.spi.scheduler.ScheduleInfo;\nimport org.springframework.cloud.deployer.spi.scheduler.ScheduleRequest;\nimport org.springframework.cloud.deployer.spi.scheduler.Scheduler;\nimport org.springframework.cloud.deployer.spi.scheduler.SchedulerException;\nimport org.springframework.cloud.deployer.spi.scheduler.SchedulerPropertyKeys;\nimport org.springframework.cloud.deployer.spi.test.Timeout;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.core.io.ClassPathResource;\nimport org.springframework.core.io.Resource;\nimport org.springframework.test.context.ContextConfiguration;\nimport org.springframework.test.context.junit.jupiter.SpringExtension;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.assertThatThrownBy;\nimport static org.awaitility.Awaitility.await;\n\n/**\n * Contains base set of tests that are required for each implementation of\n * Spring Cloud Scheduler to pass.\n *\n * @author Glenn Renfro\n * @author Ilayaperumal Gopinathan\n * @author Corneil du Plessis\n */\n@ExtendWith(SpringExtension.class)\n@SpringBootTest(webEnvironment = WebEnvironment.NONE)\n@ContextConfiguration(classes = AbstractSchedulerIntegrationJUnit5Tests.Config.class)\npublic abstract class AbstractSchedulerIntegrationJUnit5Tests {\n\n\t@Autowired\n\tprotected MavenProperties mavenProperties;\n\n\tprivate SchedulerWrapper schedulerWrapper;\n\n\t/**\n\t * Return the timeout to use for repeatedly querying that a task has been scheduled.\n\t * Default value is one minute, being queried every 5 seconds.\n\t */\n\tprivate Timeout scheduleTimeout = new Timeout(30, 5000);\n\n\t/**\n\t * Return the timeout to use for repeatedly querying whether a task has been unscheduled.\n\t * Default value is one minute, being queried every 5 seconds.\n\t */\n\tprivate Timeout unScheduleTimeout = new Timeout(12, 5000);\n\n\tprotected final Logger log = LoggerFactory.getLogger(this.getClass());\n\n    private String testName;\n\n\t@AfterEach\n\tpublic void tearDown() {\n\t\tList<ScheduleRequest> scheduleRequests = new ArrayList<>(schedulerWrapper.getScheduledTasks().values());\n\n\t\tfor (ScheduleRequest scheduleRequest : scheduleRequests) {\n\t\t\tunscheduleTestSchedule(scheduleRequest.getScheduleName());\n\t\t}\n\n\t}\n\n\t/**\n\t * Subclasses should call this method to interact with the Scheduler under test.\n\t * Returns a wrapper around the scheduler returned by {@link #provideScheduler()}, that keeps\n\t * track of which tasks have been scheduled and unscheduled.\n\t */\n\tprotected Scheduler taskScheduler() {\n\t\treturn this.schedulerWrapper;\n\t}\n\n\t/**\n\t * To be implemented by subclasses, which should return the instance of Scheduler that needs\n\t * to be tested. Can be used if subclasses decide to add additional implementation-specific tests.\n\t * @return the scheduler\n\t */\n\tprotected abstract Scheduler provideScheduler();\n\n\t/**\n\t * To be implemented by subclasses, which should return the commandLineArgs that\n\t * will be used for the tests.\n\t * @return the command line arguments\n\t */\n\tprotected abstract List<String> getCommandLineArgs();\n\n\t/**\n\t * To be implemented by subclasses, which should return the schedulerProperties that\n\t * will be used for the tests.\n\t * @return the scheduler properties\n\t */\n\tprotected abstract Map<String, String> getSchedulerProperties();\n\n\t/**\n\t * To be implemented by subclasses, which should return the deploymentProperties that\n\t * will be used for the tests.\n\t * @return the deployment properties\n\t */\n\tprotected abstract Map<String, String> getDeploymentProperties();\n\n\t/**\n\t * To be implemented by subclasses, which should return the appProperties that\n\t * will be used for the tests.\n\t * @return the application properties\n\t */\n\tprotected abstract Map<String, String> getAppProperties();\n\n\t@BeforeEach\n\tpublic void wrapScheduler(TestInfo testInfo) {\n\t\tthis.schedulerWrapper = new SchedulerWrapper(provideScheduler());\n        Optional<Method> testMethod = testInfo.getTestMethod();\n        if (testMethod.isPresent()) {\n            this.testName = testMethod.get().getName();\n        }\n\t}\n\n\t@Test\n\tpublic void testSimpleSchedule() {\n\t\tcreateAndVerifySchedule();\n\t}\n\n\t@Test\n\tpublic void testUnschedule() {\n\t\tint initialSize = taskScheduler().list().size();\n\t\tScheduleInfo scheduleInfo = createAndVerifySchedule();\n\t\tunscheduleTestSchedule(scheduleInfo.getScheduleName());\n\t\tassertThat(taskScheduler().list().size() - initialSize).isEqualTo(0);\n\t}\n\n\t@Test\n\tpublic void testDuplicateSchedule() {\n\t\tScheduleRequest request = createScheduleRequest();\n\t\ttaskScheduler().schedule(request);\n\t\tScheduleInfo scheduleInfo = new ScheduleInfo();\n\t\tscheduleInfo.setScheduleName(request.getScheduleName());\n\n\t\tverifySchedule(scheduleInfo);\n        assertThatThrownBy(() -> {\n            taskScheduler().schedule(request);\n        }).isInstanceOf(CreateScheduleException.class).hasMessageContaining(\"Failed to create schedule %s\",\n                request.getScheduleName());\n\t}\n\n\t@Test\n\tpublic void testUnScheduleNoEntry() {\n\t\tString definitionName = randomName();\n\t\tString scheduleName = scheduleName() + definitionName;\n\n        assertThatThrownBy(() -> {\n            unscheduleTestSchedule(scheduleName);\n\t\t}).isInstanceOf(SchedulerException.class).hasMessage(\"Failed to unschedule %s\",\n                scheduleName);\n\t}\n\n\t@Test\n\tpublic void testInvalidCronExpression() {\n\t\tfinal String INVALID_EXPRESSION = \"BAD\";\n\t\tString definitionName = randomName();\n\t\tString scheduleName = scheduleName() + definitionName;\n\t\tMap<String, String> properties = new HashMap<>(getDeploymentProperties());\n\t\tproperties.put(SchedulerPropertyKeys.CRON_EXPRESSION, INVALID_EXPRESSION);\n\t\tAppDefinition definition = new AppDefinition(definitionName, properties);\n\t\tScheduleRequest request = new ScheduleRequest(definition, properties, getCommandLineArgs(), scheduleName, testApplication());\n        assertThatThrownBy(() -> {\n            taskScheduler().schedule(request);\n        }).isInstanceOf(CreateScheduleException.class);\n\t}\n\n\t@Test\n\tpublic void testMultipleSchedule() {\n\t\tString definitionName = randomName();\n\t\tString scheduleName = scheduleName() + definitionName;\n\t\tfor (int i = 0; i < 4; i++) {\n\t\t\tScheduleRequest request = createScheduleRequest(scheduleName + i, definitionName + i);\n\t\t\ttaskScheduler().schedule(request);\n\t\t}\n\t\tList<ScheduleInfo> scheduleInfos = taskScheduler().list();\n\n\t\tfor (ScheduleInfo scheduleInfo : scheduleInfos) {\n\t\t\tverifySchedule(scheduleInfo);\n\t\t}\n\t}\n\n\t@Test\n\tpublic void testListFilter() {\n\t\tString definitionName = randomName();\n\t\tString scheduleName = scheduleName() + definitionName;\n\t\tfor (int i = 0; i < 4; i++) {\n\t\t\tScheduleRequest request = createScheduleRequest(scheduleName + i, definitionName + i%2);\n\t\t\ttaskScheduler().schedule(request);\n\t\t}\n\t\tScheduleInfo scheduleInfo = new ScheduleInfo();\n\t\tscheduleInfo.setScheduleName(scheduleName + 0);\n\t\tscheduleInfo.setTaskDefinitionName(definitionName + 0);\n        await().pollInterval(Duration.ofMillis(this.scheduleTimeout.pause))\n                .atMost(Duration.ofMillis(this.scheduleTimeout.totalTime))\n                .untilAsserted(() -> {\n                    ListScheduleInfoAssert.assertThat(taskScheduler().list(definitionName + 0))\n                            .hasExpectedScheduleCount(definitionName + 0, 2);\n                });\n\t}\n\n\tpublic Timeout getScheduleTimeout() {\n\t\treturn scheduleTimeout;\n\t}\n\n\tpublic void setScheduleTimeout(Timeout scheduleTimeout) {\n\t\tthis.scheduleTimeout = scheduleTimeout;\n\t}\n\n\tpublic Timeout getUnScheduleTimeout() {\n\t\treturn unScheduleTimeout;\n\t}\n\n\tpublic void setUnScheduleTimeout(Timeout unScheduleTimeout) {\n\t\tthis.unScheduleTimeout = unScheduleTimeout;\n\t}\n\n\tprivate ScheduleInfo createAndVerifySchedule() {\n\t\tScheduleRequest request = createScheduleRequest();\n\t\ttaskScheduler().schedule(request);\n\t\tScheduleInfo scheduleInfo = new ScheduleInfo();\n\t\tscheduleInfo.setScheduleName(request.getScheduleName());\n\t\tverifySchedule(scheduleInfo);\n\t\treturn scheduleInfo;\n\t}\n\n\tprivate ScheduleRequest createScheduleRequest() {\n\t\tString definitionName = randomName();\n\t\tString scheduleName = scheduleName() + definitionName;\n\t\treturn createScheduleRequest(scheduleName, definitionName);\n\t}\n\n\tprivate ScheduleRequest createScheduleRequest(String scheduleName, String definitionName) {\n\t\tAppDefinition definition = new AppDefinition(definitionName, getAppProperties());\n\t\treturn new ScheduleRequest(definition, getDeploymentProperties(), getCommandLineArgs(), scheduleName, testApplication());\n\t}\n\n\tprivate void verifySchedule(ScheduleInfo scheduleInfo) {\n        await().pollInterval(Duration.ofMillis(this.scheduleTimeout.pause))\n                .atMost(Duration.ofMillis(this.scheduleTimeout.totalTime))\n                .untilAsserted(() -> {\n            ListScheduleInfoAssert.assertThat(taskScheduler().list()).hasSchedule(scheduleInfo.getScheduleName());\n        });\n\t}\n\n\tprivate void unscheduleTestSchedule(String scheduleName) {\n\t\tlog.info(\"unscheduling {}...\", scheduleName);\n\n\t\ttaskScheduler().unschedule(scheduleName);\n\n\t\tScheduleInfo scheduleInfo = new ScheduleInfo();\n\t\tscheduleInfo.setScheduleName(scheduleName);\n        await().pollInterval(Duration.ofMillis(this.unScheduleTimeout.pause))\n                .atMost(Duration.ofMillis(this.unScheduleTimeout.totalTime))\n                .untilAsserted(() -> {\n            ListScheduleInfoAssert.assertThat(taskScheduler().list()).hasNotSchedule(scheduleName);\n        });\n\t}\n\n\tprotected String randomName() {\n\t\treturn this.testName + \"-\" + UUID.randomUUID();\n\t}\n\n\tprotected String scheduleName() {\n\t\treturn \"ScheduleName_\";\n\t}\n\n    protected static class ListScheduleInfoAssert extends AbstractAssert<ListScheduleInfoAssert, List<ScheduleInfo>> {\n\n        public ListScheduleInfoAssert(List<ScheduleInfo> scheduleInfos) {\n            super(scheduleInfos, ListScheduleInfoAssert.class);\n        }\n\n        public static ListScheduleInfoAssert assertThat(List<ScheduleInfo> scheduleInfos) {\n            return new ListScheduleInfoAssert(scheduleInfos);\n        }\n\n        public ListScheduleInfoAssert hasSchedule(String scheduleName) {\n            isNotNull();\n            if (!actual.stream().map(si -> si.getScheduleName()).anyMatch(sc -> sc.equals(scheduleName))) {\n                failWithMessage(\"unable to find specified scheduleName <%s> \", scheduleName);\n            }\n            return this;\n        }\n\n        public ListScheduleInfoAssert hasNotSchedule(String scheduleName) {\n            isNotNull();\n            if (actual.stream().map(si -> si.getScheduleName()).anyMatch(sc -> sc.equals(scheduleName))) {\n                failWithMessage(\"found specified scheduleName <%s> \", scheduleName);\n            }\n            return this;\n        }\n\n        public ListScheduleInfoAssert hasExpectedScheduleCount(String taskDefinitionName, int expectedScheduleCount) {\n            this.isNotNull();\n\t\t\tif (actual.size() != expectedScheduleCount) {\n\t\t\t\tfailWithMessage(\"given schedule info list doesn't match expected count <%s>, was <%s>\",\n\t\t\t\t\t\texpectedScheduleCount, actual.size());\n\t\t\t}\n            if (!actual.stream().map(si -> si.getTaskDefinitionName()).anyMatch(sc -> sc.equals(taskDefinitionName))) {\n                failWithMessage(\"found specified scheduleName <%s> \", taskDefinitionName);\n            }\n            return this;\n        }\n    }\n\n\t/**\n\t * Return a resource corresponding to the spring-cloud-deployer-spi-scheduler-test-app app suitable for the target runtime.\n\t *\n\t * The default implementation returns an uber-jar fetched via Maven. Subclasses may override.\n\t * @return the resource of the test application.\n\t */\n\tprotected Resource testApplication() {\n\t\tProperties properties = new Properties();\n\t\ttry {\n\t\t\tproperties.load(new ClassPathResource(\"integration-test-app.properties\").getInputStream());\n\t\t}\n\t\tcatch (IOException e) {\n\t\t\tthrow new RuntimeException(\"Failed to determine which version of spring-cloud-deployer-spi-scheduler-test-app to use\", e);\n\t\t}\n\t\treturn new MavenResource.Builder(mavenProperties)\n\t\t\t\t.groupId(\"org.springframework.cloud\")\n\t\t\t\t.artifactId(\"spring-cloud-deployer-spi-scheduler-test-app\")\n\t\t\t\t.classifier(\"exec\")\n\t\t\t\t.version(properties.getProperty(\"version\"))\n\t\t\t\t.extension(\"jar\")\n\t\t\t\t.build();\n\t}\n\n\t/**\n\t * A decorator for Scheduler that keeps track of scheduled/unscheduled tasks.\n\t *\n\t * @author Glenn Renfro\n\t */\n\tprotected static class SchedulerWrapper implements Scheduler {\n\t\tprivate final Scheduler wrapped;\n\n\t\tprivate final Map<String,ScheduleRequest> scheduledTasks = new HashMap<>();\n\n\n\t\tpublic SchedulerWrapper(Scheduler wrapped) {\n\t\t\tthis.wrapped = wrapped;\n\t\t}\n\n\t\t@Override\n\t\tpublic void schedule(ScheduleRequest scheduleRequest) {\n\t\t\twrapped.schedule(scheduleRequest);\n\t\t\tscheduledTasks.put(scheduleRequest.getScheduleName(), scheduleRequest);\n\t\t}\n\n\t\t@Override\n\t\tpublic void unschedule(String scheduleName) {\n\t\t\twrapped.unschedule(scheduleName);\n\t\t\tscheduledTasks.remove(scheduleName);\n\t\t}\n\n\t\t@Override\n\t\tpublic List<ScheduleInfo> list(String taskDefinitionName) {\n\t\t\treturn wrapped.list(taskDefinitionName);\n\t\t}\n\n\t\t@Override\n\t\tpublic List<ScheduleInfo> list() {\n\t\t\treturn wrapped.list();\n\t\t}\n\n\t\tpublic Map<String, ScheduleRequest> getScheduledTasks() {\n\t\t\treturn  Collections.unmodifiableMap(scheduledTasks);\n\t\t}\n\t}\n\n\t@Configuration\n\tpublic static class Config {\n\t\t@Bean\n\t\t@ConfigurationProperties(\"maven\")\n\t\tpublic MavenProperties mavenProperties() {\n\t\t\treturn new MavenProperties();\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "spring-cloud-deployer-spi-test/src/main/java/org/springframework/cloud/deployer/spi/test/AbstractAppDeployerIntegrationJUnit5Tests.java",
    "content": "/*\n * Copyright 2016-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.test;\n\nimport java.time.Duration;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.LinkedHashSet;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\n\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Disabled;\nimport org.junit.jupiter.api.Test;\n\nimport org.springframework.cloud.deployer.spi.app.AppDeployer;\nimport org.springframework.cloud.deployer.spi.app.AppInstanceStatus;\nimport org.springframework.cloud.deployer.spi.app.AppScaleRequest;\nimport org.springframework.cloud.deployer.spi.app.AppStatus;\nimport org.springframework.cloud.deployer.spi.app.DeploymentState;\nimport org.springframework.cloud.deployer.spi.core.AppDefinition;\nimport org.springframework.cloud.deployer.spi.core.AppDeploymentRequest;\nimport org.springframework.cloud.deployer.spi.core.RuntimeEnvironmentInfo;\nimport org.springframework.cloud.deployer.spi.test.app.DeployerIntegrationTestProperties;\nimport org.springframework.core.io.Resource;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.assertThatThrownBy;\nimport static org.awaitility.Awaitility.await;\n\n/**\n * Abstract base class for integration tests of\n * {@link org.springframework.cloud.deployer.spi.app.AppDeployer}\n * implementations.\n * <p>\n * Inheritors should setup an environment with a newly created\n * {@link org.springframework.cloud.deployer.spi.app.AppDeployer}.\n * Tests in this class are independent and leave the\n * deployer in a clean state after they successfully run.\n * </p>\n * <p>\n * As deploying an application is often quite time consuming, some tests assert\n * various aspects of deployment in a row, to avoid re-deploying apps over and\n * over again.\n * </p>\n *\n * @author Eric Bottard\n * @author Mark Fisher\n * @author Greg Turnquist\n * @author David Turanski\n * @author Corneil du Plessis\n */\npublic abstract class AbstractAppDeployerIntegrationJUnit5Tests extends AbstractIntegrationJUnit5Tests {\n\tfinal static int DESIRED_COUNT = 3;\n\tprivate AppDeployerWrapper deployerWrapper;\n\n\t/**\n\t * To be implemented by subclasses, which should return the instance of AppDeployer that needs\n\t * to be tested. If subclasses decide to add additional implementation-specific tests, they should\n\t * interact with the deployer through {@link #appDeployer()}, and not directly via a field or a call\n\t * to this method.\n\t * @return the app deployer\n\t */\n\tprotected abstract AppDeployer provideAppDeployer();\n\n\t/**\n\t * Subclasses should call this method to interact with the AppDeployer under test.\n\t * Returns a wrapper around the deployer returned by {@link #provideAppDeployer()}, that keeps\n\t * track of which apps have been deployed and undeployed.\n\t * @return the app deployer\n\t */\n\tprotected AppDeployer appDeployer() {\n\t\treturn deployerWrapper;\n\t}\n\n\t@BeforeEach\n\tpublic void wrapDeployer() {\n\t\tdeployerWrapper = new AppDeployerWrapper(provideAppDeployer());\n\t}\n\n\t@AfterEach\n\tpublic void cleanupLingeringApps() {\n\t\tfor (String id : deployerWrapper.deployments) {\n\t\t\ttry {\n\t\t\t\tlog.warn(\"Test named {} left behind an app for deploymentId '{}', trying to cleanup\", this.testName, id);\n\t\t\t\tdeployerWrapper.wrapped.undeploy(id);\n\t\t\t} catch (Exception e) {\n\t\t\t\tlog.warn(\"Exception caught while trying to cleanup '{}'. Moving on...\", id);\n\t\t\t}\n\t\t}\n\t}\n\n\t@Test\n\tpublic void testUnknownDeployment() {\n\t\tString unknownId = randomName();\n\t\tAppStatus status = appDeployer().status(unknownId);\n\n\t\tassertThat(status.getDeploymentId()).isEqualTo(unknownId);\n\t\tassertThat(status.getInstances()).as(\"The map was not empty: \" + status.getInstances()).isEmpty();\n\t\tassertThat(status.getState()).isEqualTo(DeploymentState.unknown);\n\t}\n\n\t/**\n\t * Tests a simple deploy-undeploy cycle.\n\t */\n\t@Test\n\tpublic void testSimpleDeployment() {\n\t\tAppDefinition definition = new AppDefinition(randomName(), null);\n\t\tResource resource = testApplication();\n\t\tAppDeploymentRequest request = new AppDeploymentRequest(definition, resource);\n\n\t\tlog.info(\"Deploying {}...\", request.getDefinition().getName());\n\n\t\tAppDeployer appDeployer = appDeployer();\n\t\tString deploymentId = appDeployer.deploy(request);\n\t\tTimeout timeout = deploymentTimeout();\n\t\tawait()\n\t\t\t.pollInterval(Duration.ofMillis(timeout.pause))\n\t\t\t.atMost(Duration.ofMillis(timeout.totalTime))\n\t\t\t.untilAsserted(() ->\n\t\t\t\tassertThat(appDeployer.status(deploymentId).getState()).isEqualTo(DeploymentState.deployed)\n\t\t\t);\n\n\n\t\tlog.info(\"Deploying {} again...\", request.getDefinition().getName());\n\n\t\tassertThatThrownBy(() ->\n\t\t\tappDeployer.deploy(request)\n\t\t).isInstanceOf(IllegalStateException.class);\n\n\t\tlog.info(\"Undeploying {}...\", deploymentId);\n\n\t\ttimeout = undeploymentTimeout();\n\t\tappDeployer.undeploy(deploymentId);\n\t\tawait().pollInterval(Duration.ofMillis(timeout.pause))\n\t\t\t.atMost(Duration.ofMillis(timeout.totalTime))\n\t\t\t.untilAsserted(() ->\n\t\t\t\tassertThat(appDeployer.status(deploymentId).getState()).isEqualTo(DeploymentState.unknown)\n\t\t\t);\n\n\t\tassertThatThrownBy(() ->\n\t\t\tappDeployer.undeploy(deploymentId)\n\t\t).isInstanceOf(IllegalStateException.class);\n\t}\n\n\t/**\n\t * An app deployer should be able to re-deploy an application after it has been un-deployed.\n\t * This test makes sure the deployer does not leave things lying around for example.\n\t */\n\t@Test\n\tpublic void testRedeploy() {\n\t\tAppDefinition definition = new AppDefinition(randomName(), null);\n\t\tResource resource = testApplication();\n\t\tAppDeploymentRequest request = new AppDeploymentRequest(definition, resource);\n\n\t\tlog.info(\"Deploying {}...\", request.getDefinition().getName());\n\n\t\tAppDeployer appDeployer = appDeployer();\n\t\tString deploymentId = appDeployer.deploy(request);\n\t\tTimeout timeout = deploymentTimeout();\n\t\tawait()\n\t\t\t.pollInterval(Duration.ofMillis(timeout.pause))\n\t\t\t.atMost(Duration.ofMillis(timeout.totalTime))\n\t\t\t.untilAsserted(() ->\n\t\t\t\tassertThat(appDeployer.status(deploymentId).getState()).isEqualTo(DeploymentState.deployed)\n\t\t\t);\n\n\t\tlog.info(\"Undeploying {}...\", deploymentId);\n\n\t\ttimeout = undeploymentTimeout();\n\t\tappDeployer.undeploy(deploymentId);\n\t\tawait()\n\t\t\t.pollInterval(Duration.ofMillis(timeout.pause))\n\t\t\t.atMost(Duration.ofMillis(timeout.totalTime))\n\t\t\t.untilAsserted(() ->\n\t\t\t\tassertThat(appDeployer.status(deploymentId).getState()).isEqualTo(DeploymentState.unknown)\n\t\t\t);\n\n\t\t// Optionally pause before re-using request\n\t\ttry {\n\t\t\tThread.sleep(redeploymentPause());\n\t\t} catch (InterruptedException e) {\n\t\t\tThread.currentThread().interrupt();\n\t\t}\n\n\t\tlog.info(\"Deploying {} again...\", request.getDefinition().getName());\n\n\t\t// Attempt re-deploy of SAME request\n\t\tString deploymentId2 = appDeployer.deploy(request);\n\t\ttimeout = deploymentTimeout();\n\t\tawait().pollInterval(Duration.ofMillis(timeout.pause))\n\t\t\t.atMost(Duration.ofMillis(timeout.totalTime))\n\t\t\t.untilAsserted(() ->\n\t\t\t\tassertThat(appDeployer.status(deploymentId2).getState()).isEqualTo(DeploymentState.deployed)\n\t\t\t);\n\n\t\tlog.info(\"Undeploying {}...\", deploymentId2);\n\n\t\ttimeout = undeploymentTimeout();\n\t\tappDeployer.undeploy(deploymentId2);\n\t\tawait().pollInterval(Duration.ofMillis(timeout.pause))\n\t\t\t.atMost(Duration.ofMillis(timeout.totalTime))\n\t\t\t.untilAsserted(() ->\n\t\t\t\tassertThat(appDeployer.status(deploymentId2).getState()).isEqualTo(DeploymentState.unknown)\n\t\t\t);\n\t}\n\n\t/**\n\t * Tests that an app which takes a long time to deploy is correctly reported as deploying.\n\t * Test that such an app can be undeployed.\n\t */\n\t@Test\n\tpublic void testDeployingStateCalculationAndCancel() {\n\t\tMap<String, String> properties = new HashMap<>();\n\t\tproperties.put(\"initDelay\", \"\" + 1000 * 60 * 60); // 1hr\n\t\tAppDefinition definition = new AppDefinition(randomName(), properties);\n\t\tResource resource = testApplication();\n\t\tAppDeploymentRequest request = new AppDeploymentRequest(definition, resource, properties);\n\n\t\tlog.info(\"Deploying {}...\", request.getDefinition().getName());\n\n\t\tAppDeployer appDeployer = appDeployer();\n\t\tString deploymentId = appDeployer.deploy(request);\n\t\tTimeout timeout = deploymentTimeout();\n\t\tawait().pollInterval(Duration.ofMillis(timeout.pause))\n\t\t\t.atMost(Duration.ofMillis(timeout.totalTime))\n\t\t\t.untilAsserted(() ->\n\t\t\t\tassertThat(appDeployer.status(deploymentId).getState()).isEqualTo(DeploymentState.deploying)\n\t\t\t);\n\n\t\tlog.info(\"Undeploying {}...\", deploymentId);\n\n\t\ttimeout = undeploymentTimeout();\n\t\tappDeployer.undeploy(deploymentId);\n\t\tawait().pollInterval(Duration.ofMillis(timeout.pause))\n\t\t\t.atMost(Duration.ofMillis(timeout.totalTime))\n\t\t\t.untilAsserted(() ->\n\t\t\t\tassertThat(appDeployer.status(deploymentId).getState()).isEqualTo(DeploymentState.unknown)\n\t\t\t);\n\t}\n\n\t@Test\n\tpublic void testFailedDeployment() {\n\t\tMap<String, String> properties = new HashMap<>();\n\t\tproperties.put(\"killDelay\", \"0\");\n\t\tAppDefinition definition = new AppDefinition(randomName(), properties);\n\t\tResource resource = testApplication();\n\t\tAppDeploymentRequest request = new AppDeploymentRequest(definition, resource, properties);\n\n\t\tlog.info(\"Deploying {}...\", request.getDefinition().getName());\n\n\t\tString deploymentId = appDeployer().deploy(request);\n\t\tTimeout timeout = deploymentTimeout();\n\t\tawait().pollInterval(Duration.ofMillis(timeout.pause))\n\t\t\t.atMost(Duration.ofMillis(timeout.totalTime))\n\t\t\t.untilAsserted(() ->\n\t\t\t\tassertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.failed)\n\t\t\t);\n\n\t\tlog.info(\"Undeploying {}...\", deploymentId);\n\n\t\ttimeout = undeploymentTimeout();\n\t\tappDeployer().undeploy(deploymentId);\n\t\tawait().pollInterval(Duration.ofMillis(timeout.pause))\n\t\t\t.atMost(Duration.ofMillis(timeout.totalTime))\n\t\t\t.untilAsserted(() ->\n\t\t\t\tassertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.unknown)\n\t\t\t);\n\t}\n\n\t/**\n\t * Tests that properties (key-value mappings) can be passed to a deployed app,\n\t * including values that typically require special handling.\n\t */\n\t@Test\n\tpublic void testApplicationPropertiesPassing() {\n\t\tMap<String, String> properties = new HashMap<>();\n\t\tproperties.put(\"parameterThatMayNeedEscaping\", DeployerIntegrationTestProperties.FUNNY_CHARACTERS);\n\t\tAppDefinition definition = new AppDefinition(randomName(), properties);\n\t\tMap<String, String> deploymentProperties = new HashMap<>();\n\t\t// This makes sure that deploymentProperties are not passed to the deployed app itself\n\t\tdeploymentProperties.put(\"killDelay\", \"0\");\n\n\t\tAppDeploymentRequest request = new AppDeploymentRequest(definition, testApplication(), deploymentProperties);\n\n\t\tlog.info(\"Deploying {}...\", request.getDefinition().getName());\n\n\t\tString deploymentId = appDeployer().deploy(request);\n\t\tTimeout timeout = deploymentTimeout();\n\t\tawait().pollInterval(Duration.ofMillis(timeout.pause))\n\t\t\t.atMost(Duration.ofMillis(timeout.totalTime))\n\t\t\t.untilAsserted(() ->\n\t\t\t\tassertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.deployed)\n\t\t\t);\n\n\t\tlog.info(\"Undeploying {}...\", deploymentId);\n\n\t\ttimeout = undeploymentTimeout();\n\t\tappDeployer().undeploy(deploymentId);\n\t\tawait().pollInterval(Duration.ofMillis(timeout.pause))\n\t\t\t.atMost(Duration.ofMillis(timeout.totalTime))\n\t\t\t.untilAsserted(() ->\n\t\t\t\tassertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.unknown)\n\t\t\t);\n\n\n\t\t// This second pass makes sure that properties are indeed passed\n\n\t\tproperties.put(\"parameterThatMayNeedEscaping\", \"notWhatIsExpected\");\n\t\tdefinition = new AppDefinition(randomName(), properties);\n\n\t\trequest = new AppDeploymentRequest(definition, testApplication(), deploymentProperties);\n\n\t\tlog.info(\"Deploying {}, expecting it to fail...\", request.getDefinition().getName());\n\n\t\tString deploymentId2 = appDeployer().deploy(request);\n\t\ttimeout = deploymentTimeout();\n\t\tawait().pollInterval(Duration.ofMillis(timeout.pause))\n\t\t\t.atMost(Duration.ofMillis(timeout.totalTime))\n\t\t\t.untilAsserted(() ->\n\t\t\t\tassertThat(appDeployer().status(deploymentId2).getState()).isEqualTo(DeploymentState.failed)\n\t\t\t);\n\n\t\tlog.info(\"Undeploying {}...\", deploymentId2);\n\n\t\ttimeout = undeploymentTimeout();\n\t\tappDeployer().undeploy(deploymentId2);\n\t\tawait().pollInterval(Duration.ofMillis(timeout.pause))\n\t\t\t.atMost(Duration.ofMillis(timeout.totalTime))\n\t\t\t.untilAsserted(() ->\n\t\t\t\tassertThat(appDeployer().status(deploymentId2).getState()).isEqualTo(DeploymentState.unknown)\n\t\t\t);\n\t}\n\n\t/**\n\t * Tests that command line arguments (ordered strings) can be passed to a deployed app,\n\t * including values that typically require special handling.\n\t */\n\t@Test\n\tpublic void testCommandLineArgumentsPassing() {\n\t\tMap<String, String> properties = new HashMap<>();\n\t\tAppDefinition definition = new AppDefinition(randomName(), properties);\n\t\tMap<String, String> deploymentProperties = new HashMap<>();\n\n\t\tList<String> cmdLineArgs = Collections.singletonList(\"--commandLineArgValueThatMayNeedEscaping=\" + DeployerIntegrationTestProperties.FUNNY_CHARACTERS);\n\t\tAppDeploymentRequest request =\n\t\t\tnew AppDeploymentRequest(definition, testApplication(), deploymentProperties, cmdLineArgs);\n\n\t\tlog.info(\"Deploying {}...\", request.getDefinition().getName());\n\n\t\tAppDeployer appDeployer = appDeployer();\n\t\tString deploymentId = appDeployer.deploy(request);\n\t\tTimeout timeout = deploymentTimeout();\n\t\tawait().pollInterval(Duration.ofMillis(timeout.pause))\n\t\t\t.atMost(Duration.ofMillis(timeout.totalTime))\n\t\t\t.untilAsserted(() ->\n\t\t\t\tassertThat(appDeployer.status(deploymentId).getState()).isEqualTo(DeploymentState.deployed)\n\t\t\t);\n\n\t\tlog.info(\"Undeploying {}...\", deploymentId);\n\n\t\ttimeout = undeploymentTimeout();\n\t\tappDeployer.undeploy(deploymentId);\n\t\tawait().pollInterval(Duration.ofMillis(timeout.pause))\n\t\t\t.atMost(Duration.ofMillis(timeout.totalTime))\n\t\t\t.untilAsserted(() ->\n\t\t\t\tassertThat(appDeployer.status(deploymentId).getState()).isEqualTo(DeploymentState.unknown)\n\t\t\t);\n\n\t\t// This second pass makes sure that commandLine args are indeed understood\n\t\tproperties = new HashMap<>();\n\t\tdefinition = new AppDefinition(randomName(), properties);\n\t\tdeploymentProperties = new HashMap<>();\n\n\t\tcmdLineArgs = Collections.singletonList(\"--commandLineArgValueThatMayNeedEscaping=notWhatIsExpected\");\n\t\trequest = new AppDeploymentRequest(definition, testApplication(), deploymentProperties, cmdLineArgs);\n\n\t\tlog.info(\"Deploying {}, expecting it to fail...\", request.getDefinition().getName());\n\n\t\tString deploymentId2 = appDeployer.deploy(request);\n\t\ttimeout = deploymentTimeout();\n\t\tawait().pollInterval(Duration.ofMillis(timeout.pause))\n\t\t\t.atMost(Duration.ofMillis(timeout.totalTime))\n\t\t\t.untilAsserted(() ->\n\t\t\t\tassertThat(appDeployer.status(deploymentId2).getState()).isEqualTo(DeploymentState.failed)\n\t\t\t);\n\n\t\tlog.info(\"Undeploying {}...\", deploymentId2);\n\n\t\ttimeout = undeploymentTimeout();\n\t\tappDeployer.undeploy(deploymentId2);\n\t\tawait().pollInterval(Duration.ofMillis(timeout.pause))\n\t\t\t.atMost(Duration.ofMillis(timeout.totalTime))\n\t\t\t.untilAsserted(() ->\n\t\t\t\tassertThat(appDeployer.status(deploymentId2).getState()).isEqualTo(DeploymentState.unknown)\n\t\t\t);\n\t}\n\n\n\t/**\n\t * Tests support for instance count support and individual instance status report.\n\t */\n\t@Test\n\tpublic void testMultipleInstancesDeploymentAndPartialState() {\n\t\tMap<String, String> appProperties = new HashMap<>();\n\t\tappProperties.put(\"matchInstances\", \"1\"); // Only instance n°1 will kill itself\n\t\tappProperties.put(\"killDelay\", \"0\");\n\t\tAppDefinition definition = new AppDefinition(randomName(), appProperties);\n\t\tResource resource = testApplication();\n\n\t\tMap<String, String> deploymentProperties = new HashMap<>();\n\t\tdeploymentProperties.put(AppDeployer.COUNT_PROPERTY_KEY, \"3\");\n\t\tdeploymentProperties.put(AppDeployer.INDEXED_PROPERTY_KEY, \"true\");\n\t\tAppDeploymentRequest request = new AppDeploymentRequest(definition, resource, deploymentProperties);\n\n\t\tlog.info(\"Deploying {}...\", request.getDefinition().getName());\n\n\t\tAppDeployer appDeployer = appDeployer();\n\t\tString deploymentId = appDeployer.deploy(request);\n\t\tTimeout timeout = deploymentTimeout();\n\t\tawait().pollInterval(Duration.ofMillis(timeout.pause))\n\t\t\t.atMost(Duration.ofMillis(timeout.totalTime))\n\t\t\t.untilAsserted(() ->\n\t\t\t\tassertThat(appDeployer.status(deploymentId).getState()).isEqualTo(DeploymentState.partial)\n\t\t\t);\n\n\t\t// Assert individual instance state\n\t\t// Note we can't rely on instances order, neither on their id indicating their ordinal number\n\t\tList<DeploymentState> individualStates = new ArrayList<>();\n\t\tfor (AppInstanceStatus status : appDeployer.status(deploymentId).getInstances().values()) {\n\t\t\tindividualStates.add(status.getState());\n\t\t}\n\n\t\tassertThat(individualStates).containsExactlyInAnyOrder(DeploymentState.deployed, DeploymentState.deployed,\n\t\t\tDeploymentState.failed);\n\n\t\tlog.info(\"Undeploying {}...\", deploymentId);\n\n\t\ttimeout = undeploymentTimeout();\n\t\tappDeployer.undeploy(deploymentId);\n\t\tawait().pollInterval(Duration.ofMillis(timeout.pause))\n\t\t\t.atMost(Duration.ofMillis(timeout.totalTime))\n\t\t\t.untilAsserted(() ->\n\t\t\t\tassertThat(appDeployer.status(deploymentId).getState()).isEqualTo(DeploymentState.unknown)\n\t\t\t);\n\t}\n\n\t/**\n\t * Tests support for DeployerEnvironmentInfo is implemented.\n\t */\n\t@Test\n\tpublic void testEnvironmentInfo() {\n\t\tRuntimeEnvironmentInfo info = appDeployer().environmentInfo();\n\t\tassertThat(info.getImplementationVersion()).isNotNull();\n\t\tassertThat(info.getPlatformType()).isNotNull();\n\t\tassertThat(info.getPlatformClientVersion()).isNotNull();\n\t\tassertThat(info.getPlatformHostVersion()).isNotNull();\n\t}\n\n\t@Test\n\t@Disabled(\"Disabled pending the implementation of this feature.\")\n\tpublic void testScale() {\n\t\tdoTestScale(false);\n\t}\n\n\t@Test\n\t@Disabled(\"Disabled pending the implementation of this feature.\")\n\tpublic void testScaleWithIndex() {\n\t\tdoTestScale(true);\n\t}\n\n\tprotected void doTestScale(Boolean indexed) {\n\n\n\t\tMap<String, String> deploymentProperties =\n\t\t\tCollections.singletonMap(AppDeployer.INDEXED_PROPERTY_KEY, indexed.toString());\n\n\t\tAppDefinition definition = new AppDefinition(randomName(), null);\n\t\tResource resource = testApplication();\n\t\tAppDeploymentRequest request = new AppDeploymentRequest(definition, resource, deploymentProperties);\n\n\t\tlog.info(\"Deploying {} index={}...\", request.getDefinition().getName(), indexed);\n\n\t\tAppDeployer appDeployer = appDeployer();\n\t\tString deploymentId = appDeployer.deploy(request);\n\n\t\tTimeout timeout = deploymentTimeout();\n\t\tlog.info(\"DeploymentTimeout:{}\", timeout);\n\t\tawait().pollInterval(Duration.ofMillis(timeout.pause))\n\t\t\t.atMost(Duration.ofMillis(timeout.totalTime))\n\t\t\t.untilAsserted(() ->\n\t\t\t\tassertThat(appDeployer.status(deploymentId).getState()).isEqualTo(DeploymentState.deployed)\n\t\t\t);\n\n\t\tassertThat(appDeployer.status(deploymentId).getState()).isEqualTo(DeploymentState.deployed);\n\n\t\tlog.info(\"Scaling {} to {} instances...\", request.getDefinition().getName(), DESIRED_COUNT);\n\n\t\tappDeployer.scale(new AppScaleRequest(deploymentId, DESIRED_COUNT));\n\n\t\tawait().pollInterval(Duration.ofMillis(timeout.pause))\n\t\t\t.atMost(Duration.ofMillis(timeout.totalTime))\n\t\t\t.untilAsserted(() -> {\n\t\t\t\tDeploymentState state = appDeployer.status(deploymentId).getState();\n\t\t\t\tlog.info(\"Awaiting deployed. State={}\", state);\n\t\t\t\tassertThat(state).isEqualTo(DeploymentState.deployed);\n\t\t\t});\n\n\t\tawait().pollInterval(Duration.ofMillis(timeout.pause))\n\t\t\t.atMost(Duration.ofMillis(timeout.totalTime))\n\t\t\t.untilAsserted(() -> {\n\t\t\t\tMap<String, AppInstanceStatus> instances = appDeployer.status(deploymentId).getInstances();\n\t\t\t\tlog.info(\"Awaiting {} instances. Instances={}\", DESIRED_COUNT, instances.size());\n\t\t\t\tassertThat(instances).hasSize(DESIRED_COUNT);\n\t\t\t});\n\n\t\tList<DeploymentState> individualStates = new ArrayList<>();\n\t\tfor (AppInstanceStatus status : appDeployer.status(deploymentId).getInstances().values()) {\n\t\t\tindividualStates.add(status.getState());\n\t\t}\n\n\t\tassertThat(individualStates).allMatch(is -> is == DeploymentState.deployed);\n\n\t\tlog.info(\"Scaling {} from {} to 1 instance...\", request.getDefinition().getName(), DESIRED_COUNT);\n\n\t\tappDeployer.scale(new AppScaleRequest(deploymentId, 1));\n\n\t\tawait().pollInterval(Duration.ofMillis(timeout.pause))\n\t\t\t.atMost(Duration.ofMillis(timeout.totalTime))\n\t\t\t.untilAsserted(() -> {\n\t\t\t\tDeploymentState state = appDeployer.status(deploymentId).getState();\n\t\t\t\tlog.info(\"Awaiting deployed. State={}\", state);\n\t\t\t\tassertThat(state).isEqualTo(DeploymentState.deployed);\n\t\t\t});\n\n\t\tawait().pollInterval(Duration.ofMillis(timeout.pause))\n\t\t\t.atMost(Duration.ofMillis(timeout.totalTime))\n\t\t\t.untilAsserted(() ->\n\t\t\t\tassertThat(appDeployer.status(deploymentId).getInstances()).hasSize(1)\n\t\t\t);\n\n\t\tlog.info(\"Undeploying {}...\", deploymentId);\n\n\t\ttimeout = undeploymentTimeout();\n\t\tappDeployer.undeploy(deploymentId);\n\t\tawait().pollInterval(Duration.ofMillis(timeout.pause))\n\t\t\t.atMost(Duration.ofMillis(timeout.totalTime))\n\t\t\t.untilAsserted(() -> {\n\t\t\t\tDeploymentState state = appDeployer.status(deploymentId).getState();\n\t\t\t\tlog.info(\"Awaiting unknown. State={}\", state);\n\t\t\t\tassertThat(state).isEqualTo(DeploymentState.unknown);\n\t\t\t});\n\n\t}\n\n\t/**\n\t * A decorator for AppDeployer that keeps track of deployed/undeployed apps.\n\t *\n\t * @author Eric Bottard\n\t */\n\tprotected static class AppDeployerWrapper implements AppDeployer {\n\n\t\tprivate final AppDeployer wrapped;\n\n\t\tprivate final Set<String> deployments = new LinkedHashSet<>();\n\n\t\tpublic AppDeployerWrapper(AppDeployer wrapped) {\n\t\t\tthis.wrapped = wrapped;\n\t\t}\n\n\t\t@Override\n\t\tpublic String deploy(AppDeploymentRequest request) {\n\t\t\tString deploymentId = wrapped.deploy(request);\n\t\t\tdeployments.add(deploymentId);\n\t\t\treturn deploymentId;\n\t\t}\n\n\t\t@Override\n\t\tpublic void undeploy(String id) {\n\t\t\twrapped.undeploy(id);\n\t\t\tdeployments.remove(id);\n\t\t}\n\n\t\t@Override\n\t\tpublic AppStatus status(String id) {\n\t\t\treturn wrapped.status(id);\n\t\t}\n\n\t\t@Override\n\t\tpublic RuntimeEnvironmentInfo environmentInfo() {\n\t\t\treturn wrapped.environmentInfo();\n\t\t}\n\n\t\t@Override\n\t\tpublic String getLog(String id) {\n\t\t\treturn wrapped.getLog(id);\n\t\t}\n\n\t\t@Override\n\t\tpublic void scale(AppScaleRequest appScaleRequest) {\n\t\t\twrapped.scale(appScaleRequest);\n\t\t}\n\n\t}\n}\n"
  },
  {
    "path": "spring-cloud-deployer-spi-test/src/main/java/org/springframework/cloud/deployer/spi/test/AbstractIntegrationJUnit5Tests.java",
    "content": "/*\n * Copyright 2016-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.test;\n\nimport java.io.IOException;\nimport java.lang.reflect.Method;\nimport java.util.Optional;\nimport java.util.Properties;\nimport java.util.UUID;\n\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.TestInfo;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.boot.context.properties.ConfigurationProperties;\nimport org.springframework.boot.test.context.SpringBootTest;\nimport org.springframework.boot.test.context.SpringBootTest.WebEnvironment;\nimport org.springframework.cloud.deployer.resource.maven.MavenProperties;\nimport org.springframework.cloud.deployer.resource.maven.MavenResource;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.core.io.ClassPathResource;\nimport org.springframework.core.io.Resource;\nimport org.springframework.test.context.ContextConfiguration;\nimport org.springframework.test.context.junit.jupiter.SpringExtension;\n\n/**\n * Abstract base class containing infrastructure for the TCK, common to both\n * {@link org.springframework.cloud.deployer.spi.app.AppDeployer} and\n * {@link org.springframework.cloud.deployer.spi.task.TaskLauncher} tests.\n *\n * <p>Subclasses should explicitly declare additional config that should be used via the use of\n * {@link ContextConfiguration}.</p>\n *\n * @author Eric Bottard\n */\n\n@ExtendWith(SpringExtension.class)\n@SpringBootTest(webEnvironment= WebEnvironment.NONE)\n@ContextConfiguration(classes = AbstractIntegrationJUnit5Tests.Config.class)\npublic abstract class AbstractIntegrationJUnit5Tests {\n\n\tprotected final Logger log = LoggerFactory.getLogger(this.getClass());\n\n    protected String testName;\n\n\t@Autowired\n\tprotected MavenProperties mavenProperties;\n\n\t@BeforeEach\n\tpublic void setup(TestInfo testInfo) {\n        Optional<Method> testMethod = testInfo.getTestMethod();\n\t\ttestMethod.ifPresent(method -> this.testName = method.getName());\n\t}\n\n\tprotected String randomName() {\n\t\treturn this.testName + \"-\" + UUID.randomUUID().toString();\n\t}\n\n\t/**\n\t * Return the timeout to use for repeatedly querying app status while it is being deployed.\n\t * Default value is one minute, being queried every 5 seconds.\n\t * @return the timeout\n\t */\n\tprotected Timeout deploymentTimeout() {\n\t\treturn new Timeout(30, 5000);\n\t}\n\n\t/**\n\t * Return the timeout to use for repeatedly querying app status while it is being un-deployed.\n\t * Default value is one minute, being queried every 5 seconds.\n\t * @return the timeout\n\t */\n\tprotected Timeout undeploymentTimeout() {\n\t\treturn new Timeout(20, 5000);\n\t}\n\n\t/**\n\t * Return the time to wait between reusing deployment requests. This could be necessary to give\n\t * some platforms time to clean up after undeployment.\n\t * @return redeployment pause\n\t */\n\tprotected int redeploymentPause() {\n\t\treturn 0;\n\t}\n\n\t/**\n\t * Return a resource corresponding to the spring-cloud-deployer-spi-test-app app suitable for the target runtime.\n\t *\n\t * The default implementation returns an uber-jar fetched via Maven. Subclasses may override.\n\t * @return the resource\n\t */\n\tprotected Resource testApplication() {\n\t\tProperties properties = new Properties();\n\t\ttry {\n\t\t\tproperties.load(new ClassPathResource(\"integration-test-app.properties\").getInputStream());\n\t\t}\n\t\tcatch (IOException e) {\n\t\t\tthrow new RuntimeException(\"Failed to determine which version of spring-cloud-deployer-spi-test-app to use\", e);\n\t\t}\n\t\treturn new MavenResource.Builder(mavenProperties)\n\t\t\t\t.groupId(\"org.springframework.cloud\")\n\t\t\t\t.artifactId(\"spring-cloud-deployer-spi-test-app\")\n\t\t\t\t.classifier(\"exec\")\n\t\t\t\t.version(properties.getProperty(\"version\"))\n\t\t\t\t.extension(\"jar\")\n\t\t\t\t.build();\n\t}\n\n\t@Configuration\n\tpublic static class Config {\n\t\t@Bean\n\t\t@ConfigurationProperties(\"maven\")\n\t\tpublic MavenProperties mavenProperties() {\n\t\t\treturn new MavenProperties();\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "spring-cloud-deployer-spi-test/src/main/java/org/springframework/cloud/deployer/spi/test/AbstractTaskLauncherIntegrationJUnit5Tests.java",
    "content": "/*\n * Copyright 2016-2019 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.test;\n\nimport java.time.Duration;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.LinkedHashSet;\nimport java.util.Map;\nimport java.util.Set;\n\nimport org.assertj.core.api.AbstractAssert;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\n\nimport org.springframework.cloud.deployer.spi.core.AppDefinition;\nimport org.springframework.cloud.deployer.spi.core.AppDeploymentRequest;\nimport org.springframework.cloud.deployer.spi.core.RuntimeEnvironmentInfo;\nimport org.springframework.cloud.deployer.spi.task.LaunchState;\nimport org.springframework.cloud.deployer.spi.task.TaskLauncher;\nimport org.springframework.cloud.deployer.spi.task.TaskStatus;\nimport org.springframework.core.io.Resource;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.awaitility.Awaitility.await;\n\n/**\n * Abstract base class for integration tests of\n * {@link org.springframework.cloud.deployer.spi.task.TaskLauncher} implementations.\n * <p>\n * Inheritors should setup an environment with a newly created\n * {@link org.springframework.cloud.deployer.spi.task.TaskLauncher}.\n *\n * Tests in this class are independent and leave the\n * launcher in a clean state after they successfully run.\n * </p>\n * <p>\n * As deploying a task is often quite time consuming, some tests assert\n * various aspects of deployment in a row, to avoid re-deploying apps over and\n * over again.\n * </p>\n *\n * @author Eric Bottard\n * @author Ilayaperumal Gopinathan\n */\npublic abstract class AbstractTaskLauncherIntegrationJUnit5Tests extends AbstractIntegrationJUnit5Tests {\n\n\n\tprivate TaskLauncherWrapper launcherWrapper;\n\n\t/**\n\t * To be implemented by subclasses, which should return the instance of TaskLauncher that needs\n\t * to be tested. If subclasses decide to add additional implementation-specific tests, they should\n\t * interact with the task launcher through {@link #taskLauncher()}, and not directly via a field or a call\n\t * to this method.\n\t * @return the task launcher\n\t */\n\tprotected abstract TaskLauncher provideTaskLauncher();\n\n\t/**\n\t * Subclasses should call this method to interact with the AppDeployer under test.\n\t * Returns a wrapper around the deployer returned by {@link #provideTaskLauncher()}, that keeps\n\t * track of which apps have been deployed and undeployed.\n\t * @return the task launcher\n\t */\n\tprotected TaskLauncher taskLauncher() {\n\t\treturn launcherWrapper;\n\t}\n\n\n\t@BeforeEach\n\tpublic void wrapDeployer() {\n\t\tlauncherWrapper = new TaskLauncherWrapper(provideTaskLauncher());\n\t}\n\n\t@AfterEach\n\tpublic void cleanupLingeringApps() {\n\t\tfor (String id : launcherWrapper.launchedTasks) {\n\t\t\ttry {\n\t\t\t\tlauncherWrapper.wrapped.cleanup(id);\n\t\t\t}\n\t\t\tcatch (Exception e) {\n\t\t\t\tlog.warn(\"Exception caught while trying to cleanup '{}'. Moving on...\", id);\n\t\t\t}\n\t\t}\n\t\tfor (String appName : launcherWrapper.deployedApps) {\n\t\t\ttry {\n\t\t\t\tlog.warn(\"Test named '{}' left behind an app for ''. Trying to destroy.\", this.testName, appName);\n\t\t\t\tlauncherWrapper.wrapped.destroy(appName);\n\t\t\t}\n\t\t\tcatch (Exception e) {\n\t\t\t\tlog.warn(\"Exception caught while trying to destroy '{}'. Moving on...\", appName);\n\t\t\t}\n\t\t}\n\t}\n\n\t@Test\n\tpublic void testNonExistentAppsStatus() {\n\t\tassertThat(taskLauncher().status(randomName()).getState()).isEqualTo(LaunchState.unknown);\n\t}\n\n\t@Test\n\tpublic void testSimpleLaunch() throws InterruptedException {\n\t\tMap<String, String> appProperties = new HashMap<>();\n\t\tappProperties.put(\"killDelay\", \"0\");\n\t\tappProperties.put(\"exitCode\", \"0\");\n\t\tAppDefinition definition = new AppDefinition(randomName(), appProperties);\n\t\tResource resource = testApplication();\n\t\tAppDeploymentRequest request = new AppDeploymentRequest(definition, resource);\n\n\t\tlog.info(\"Launching {}...\", request.getDefinition().getName());\n\t\tString launchId = taskLauncher().launch(request);\n\n\t\tTimeout timeout = deploymentTimeout();\n\t\tawait().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> {\n\t\t\tassertThat(taskLauncher().status(launchId).getState()).isEqualTo(LaunchState.complete);\n        });\n\n\t\ttaskLauncher().destroy(definition.getName());\n\t}\n\n\t@Test\n\tpublic void testReLaunch() throws InterruptedException {\n\t\tMap<String, String> appProperties = new HashMap<>();\n\t\tappProperties.put(\"killDelay\", \"0\");\n\t\tappProperties.put(\"exitCode\", \"0\");\n\t\tAppDefinition definition = new AppDefinition(randomName(), appProperties);\n\t\tResource resource = testApplication();\n\t\tAppDeploymentRequest request = new AppDeploymentRequest(definition, resource);\n\n\t\tlog.info(\"Launching {}...\", request.getDefinition().getName());\n\t\tString launchId = taskLauncher().launch(request);\n\n\t\tTimeout timeout = deploymentTimeout();\n\t\tawait().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> {\n\t\t\tassertThat(taskLauncher().status(launchId).getState()).isEqualTo(LaunchState.complete);\n        });\n\n\t\tlog.info(\"Re-Launching {}...\", request.getDefinition().getName());\n\t\tString newLaunchId = taskLauncher().launch(request);\n\n\t\tassertThat(newLaunchId).isNotEqualTo(launchId);\n\n\t\ttimeout = deploymentTimeout();\n\t\tawait().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> {\n\t\t\tassertThat(taskLauncher().status(newLaunchId).getState()).isEqualTo(LaunchState.complete);\n        });\n\n\t\ttaskLauncher().destroy(definition.getName());\n\t}\n\n\t@Test\n\tpublic void testErrorExit() throws InterruptedException {\n\t\tMap<String, String> appProperties = new HashMap<>();\n\t\tappProperties.put(\"killDelay\", \"0\");\n\t\tappProperties.put(\"exitCode\", \"1\");\n\t\tAppDefinition definition = new AppDefinition(randomName(), appProperties);\n\t\tResource resource = testApplication();\n\t\tAppDeploymentRequest request = new AppDeploymentRequest(definition, resource);\n\n\t\tlog.info(\"Launching {}...\", request.getDefinition().getName());\n\t\tString launchId = taskLauncher().launch(request);\n\n\t\tTimeout timeout = deploymentTimeout();\n\t\tawait().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> {\n\t\t\tassertThat(taskLauncher().status(launchId).getState()).isEqualTo(LaunchState.failed);\n        });\n\n\t\ttaskLauncher().destroy(definition.getName());\n\t}\n\n\t@Test\n\tpublic void testSimpleCancel() throws InterruptedException {\n\t\tMap<String, String> appProperties = new HashMap<>();\n\t\tappProperties.put(\"killDelay\", \"-1\");\n\t\tappProperties.put(\"exitCode\", \"0\");\n\t\tAppDefinition definition = new AppDefinition(randomName(), appProperties);\n\t\tResource resource = testApplication();\n\t\tAppDeploymentRequest request = new AppDeploymentRequest(definition, resource);\n\n\t\tlog.info(\"Launching {}...\", request.getDefinition().getName());\n\t\tString launchId = taskLauncher().launch(request);\n\n\t\tTimeout timeout = deploymentTimeout();\n\t\tawait().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> {\n\t\t\tassertThat(taskLauncher().status(launchId).getState()).isEqualTo(LaunchState.running);\n        });\n\n\t\tlog.info(\"Cancelling {}...\", request.getDefinition().getName());\n\t\ttaskLauncher().cancel(launchId);\n\n\t\ttimeout = undeploymentTimeout();\n\t\tawait().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> {\n\t\t\tassertThat(taskLauncher().status(launchId).getState()).isEqualTo(LaunchState.cancelled);\n        });\n\n\t\ttaskLauncher().destroy(definition.getName());\n\t}\n\n\t/**\n\t * Tests that command line args can be passed in.\n\t */\n\t@Test\n\tpublic void testCommandLineArgs() {\n\t\tMap<String, String> properties = new HashMap<>();\n\t\tproperties.put(\"killDelay\", \"1000\");\n\t\tAppDefinition definition = new AppDefinition(randomName(), properties);\n\t\tResource resource = testApplication();\n\t\tAppDeploymentRequest request = new AppDeploymentRequest(definition, resource, Collections.<String, String>emptyMap(),\n\t\t\t\tCollections.singletonList(\"--exitCode=0\"));\n\t\tlog.info(\"Launching {}...\", request.getDefinition().getName());\n\t\tString deploymentId = taskLauncher().launch(request);\n\n\t\tTimeout timeout = deploymentTimeout();\n\t\tawait().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> {\n\t\t\tassertThat(taskLauncher().status(deploymentId).getState()).isEqualTo(LaunchState.complete);\n        });\n\t\ttaskLauncher().destroy(definition.getName());\n\t}\n\n\t/**\n\t * Tests support for DeployerEnvironmentInfo is implemented.\n\t */\n\t@Test\n\tpublic void testEnvironmentInfo() {\n\t\tRuntimeEnvironmentInfo info = taskLauncher().environmentInfo();\n\t\tassertThat(info.getImplementationVersion()).isNotNull();\n\t\tassertThat(info.getPlatformType()).isNotNull();\n\t\tassertThat(info.getPlatformClientVersion()).isNotNull();\n\t\tassertThat(info.getPlatformHostVersion()).isNotNull();\n\t}\n\n    protected static class TaskLauncherAssert extends AbstractAssert<TaskLauncherAssert, TaskLauncher> {\n\n\t\tpublic TaskLauncherAssert(TaskLauncher launcher) {\n\t\t\tsuper(launcher, TaskLauncherAssert.class);\n\t\t}\n\n\t}\n\n\t/**\n\t * A decorator for TaskLauncher that keeps track of deployed/undeployed apps.\n\t *\n\t * @author Eric Bottard\n\t */\n\tprotected static class TaskLauncherWrapper implements TaskLauncher {\n\t\tprivate final TaskLauncher wrapped;\n\n\t\tprivate final Set<String> deployedApps = new LinkedHashSet<>();\n\n\t\tprivate final Set<String> launchedTasks = new LinkedHashSet<>();\n\n\t\tpublic TaskLauncherWrapper(TaskLauncher wrapped) {\n\t\t\tthis.wrapped = wrapped;\n\t\t}\n\n\t\t@Override\n\t\tpublic String launch(AppDeploymentRequest request) {\n\t\t\tString launchId = wrapped.launch(request);\n\t\t\tdeployedApps.add(request.getDefinition().getName());\n\t\t\tlaunchedTasks.add(launchId);\n\t\t\treturn launchId;\n\t\t}\n\n\t\t@Override\n\t\tpublic void cancel(String id) {\n\t\t\twrapped.cancel(id);\n\t\t}\n\n\t\t@Override\n\t\tpublic TaskStatus status(String id) {\n\t\t\treturn wrapped.status(id);\n\t\t}\n\n\t\t@Override\n\t\tpublic void cleanup(String id) {\n\t\t\twrapped.cleanup(id);\n\t\t\tlaunchedTasks.remove(id);\n\t\t}\n\n\t\t@Override\n\t\tpublic void destroy(String appName) {\n\t\t\twrapped.destroy(appName);\n\t\t\tdeployedApps.remove(appName);\n\t\t}\n\n\t\t@Override\n\t\tpublic RuntimeEnvironmentInfo environmentInfo() {\n\t\t\treturn wrapped.environmentInfo();\n\t\t}\n\t\t@Override\n\t\tpublic int getMaximumConcurrentTasks() {\n\t\t\treturn wrapped.getMaximumConcurrentTasks();\n\t\t}\n\t\t@Override\n\t\tpublic int getRunningTaskExecutionCount() {\n\t\t\treturn wrapped.getRunningTaskExecutionCount();\n\t\t}\n\n\t\t@Override\n\t\tpublic String getLog(String id) {\n\t\t\treturn wrapped.getLog(id);\n\t\t}\n\n\t}\n}\n"
  },
  {
    "path": "spring-cloud-deployer-spi-test/src/main/java/org/springframework/cloud/deployer/spi/test/Timeout.java",
    "content": "/*\n * Copyright 2016 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.test;\n\n/**\n * Represents a timeout for querying status, with repetitive queries until a certain number have been made.\n *\n * @author Eric Bottard\n */\npublic class Timeout {\n\n\tpublic final int maxAttempts;\n\n\tpublic final int pause;\n\n\tpublic final long totalTime;\n\n\tpublic Timeout(int maxAttempts, int pause) {\n\t\tthis.maxAttempts = maxAttempts;\n\t\tthis.pause = pause;\n\t\tthis.totalTime = (long) this.maxAttempts * (long) this.pause;\n\t}\n}\n"
  },
  {
    "path": "spring-cloud-deployer-spi-test/src/main/resources/integration-test-app.properties",
    "content": "version=@integration-test-app.version@\n"
  },
  {
    "path": "spring-cloud-deployer-spi-test-app/Dockerfile",
    "content": "FROM java:8-alpine\n\nARG JAR_FILE\n\nADD target/${JAR_FILE} spring-cloud-deployer-spi-test-app.jar\n\nENTRYPOINT [\"java\", \"-jar\", \"/spring-cloud-deployer-spi-test-app.jar\"]\n"
  },
  {
    "path": "spring-cloud-deployer-spi-test-app/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd\">\n\t<modelVersion>4.0.0</modelVersion>\n\t<parent>\n\t\t<groupId>org.springframework.cloud</groupId>\n\t\t<artifactId>spring-cloud-deployer-parent</artifactId>\n\t\t<version>3.0.0-SNAPSHOT</version>\n\t\t<relativePath>..</relativePath>\n\t</parent>\n\t<artifactId>spring-cloud-deployer-spi-test-app</artifactId>\n\t<properties>\n\t\t<start-class>org.springframework.cloud.deployer.spi.test.app.DeployerIntegrationTestApplication</start-class>\n\t\t<java.version>17</java.version>\n\t\t<docker.image.prefix>springcloud</docker.image.prefix>\n\t\t<dockerfile-maven-plugin.version>1.3.6</dockerfile-maven-plugin.version>\n\t</properties>\n\t<dependencies>\n\t\t<!--suppress VulnerableLibrariesLocal -->\n\t\t<dependency>\n\t\t\t<groupId>org.springframework.boot</groupId>\n\t\t\t<artifactId>spring-boot-starter-web</artifactId>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.springframework.boot</groupId>\n\t\t\t<artifactId>spring-boot-starter-actuator</artifactId>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.springframework.boot</groupId>\n\t\t\t<artifactId>spring-boot-configuration-processor</artifactId>\n\t\t\t<!--optional so that it does its job while compiling, but is not included in\n\t\t\tthe resulting artifact -->\n\t\t\t<optional>true</optional>\n\t\t</dependency>\n\t</dependencies>\n\t<build>\n\t\t<plugins>\n\t\t\t<plugin>\n\t\t\t\t<groupId>org.springframework.boot</groupId>\n\t\t\t\t<artifactId>spring-boot-maven-plugin</artifactId>\n\t\t\t\t<version>${spring-boot.version}</version>\n\t\t\t\t<configuration>\n\t\t\t\t\t<image>\n\t\t\t\t\t\t<pullPolicy>IF_NOT_PRESENT</pullPolicy>\n\t\t\t\t\t\t<name>springcloud/spring-cloud-deployer-spi-test-app:latest</name>\n\t\t\t\t\t</image>\n\t\t\t\t</configuration>\n\t\t\t</plugin>\n\t\t</plugins>\n\t</build>\n</project>\n"
  },
  {
    "path": "spring-cloud-deployer-spi-test-app/src/main/java/org/springframework/cloud/deployer/spi/test/app/DeployerIntegrationTest.java",
    "content": "/*\r\n * Copyright 2016 the original author or authors.\r\n *\r\n * Licensed under the Apache License, Version 2.0 (the \"License\");\r\n * you may not use this file except in compliance with the License.\r\n * You may obtain a copy of the License at\r\n *\r\n *      https://www.apache.org/licenses/LICENSE-2.0\r\n *\r\n * Unless required by applicable law or agreed to in writing, software\r\n * distributed under the License is distributed on an \"AS IS\" BASIS,\r\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n * See the License for the specific language governing permissions and\r\n * limitations under the License.\r\n */\r\n\r\npackage org.springframework.cloud.deployer.spi.test.app;\r\n\r\nimport jakarta.annotation.PostConstruct;\r\n\r\nimport org.springframework.beans.factory.annotation.Autowired;\r\nimport org.springframework.boot.context.properties.EnableConfigurationProperties;\r\nimport org.springframework.context.annotation.Configuration;\r\nimport org.springframework.util.Assert;\r\n\r\n/**\r\n * An app that can misbehave, useful for integration testing of app deployers.\r\n *\r\n * @author Eric Bottard\r\n */\r\n@EnableConfigurationProperties(DeployerIntegrationTestProperties.class)\r\n@Configuration\r\npublic class DeployerIntegrationTest {\r\n\r\n\t@Autowired\r\n\tprivate DeployerIntegrationTestProperties properties;\r\n\r\n\t@PostConstruct\r\n\tpublic void init() throws InterruptedException {\r\n\t\tString parameterThatMayNeedEscaping = properties.getParameterThatMayNeedEscaping();\r\n\t\tif (parameterThatMayNeedEscaping != null && !DeployerIntegrationTestProperties.FUNNY_CHARACTERS.equals(parameterThatMayNeedEscaping)) {\r\n\t\t\tthrow new IllegalArgumentException(\r\n\t\t\t\t\tString.format(\"Expected 'parameterThatMayNeedEscaping' value to be equal to '%s', but was '%s'\",\r\n\t\t\t\t\t\t\tDeployerIntegrationTestProperties.FUNNY_CHARACTERS, parameterThatMayNeedEscaping));\r\n\t\t}\r\n\r\n\t\tString commandLineArgValueThatMayNeedEscaping = properties.getCommandLineArgValueThatMayNeedEscaping();\r\n\t\tif (commandLineArgValueThatMayNeedEscaping != null && !DeployerIntegrationTestProperties.FUNNY_CHARACTERS.equals(commandLineArgValueThatMayNeedEscaping)) {\r\n\t\t\tthrow new IllegalArgumentException(String.format(\r\n\t\t\t\t\t\"Expected 'commandLineArgValueThatMayNeedEscaping' value to be equal to '%s', but was '%s'\",\r\n\t\t\t\t\tDeployerIntegrationTestProperties.FUNNY_CHARACTERS, commandLineArgValueThatMayNeedEscaping));\r\n\t\t}\r\n\r\n\t\tAssert.notNull(properties.getInstanceIndex(), \"instanceIndex should have been set by deployer or runtime\");\r\n\r\n\t\tif (properties.getMatchInstances().isEmpty() || properties.getMatchInstances().contains(properties.getInstanceIndex())) {\r\n\t\t\tSystem.out.format(\"Waiting for %dms before allowing further initialization and actuator startup...\", properties.getInitDelay());\r\n\t\t\tThread.sleep(properties.getInitDelay());\r\n\t\t\tSystem.out.println(\"... done\");\r\n\t\t\tif (properties.getKillDelay() >= 0) {\r\n\t\t\t\tSystem.out.format(\"Will kill this process in %dms%n\", properties.getKillDelay());\r\n\t\t\t\tnew Thread() {\r\n\r\n\t\t\t\t\t@Override\r\n\t\t\t\t\tpublic void run() {\r\n\t\t\t\t\t\ttry {\r\n\t\t\t\t\t\t\tThread.sleep(properties.getKillDelay());\r\n\t\t\t\t\t\t\tSystem.exit(properties.getExitCode());\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\tcatch (InterruptedException e) {\r\n\t\t\t\t\t\t\tthrow new RuntimeException(e);\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t}.start();\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "spring-cloud-deployer-spi-test-app/src/main/java/org/springframework/cloud/deployer/spi/test/app/DeployerIntegrationTestApplication.java",
    "content": "/*\r\n * Copyright 2016 the original author or authors.\r\n *\r\n * Licensed under the Apache License, Version 2.0 (the \"License\");\r\n * you may not use this file except in compliance with the License.\r\n * You may obtain a copy of the License at\r\n *\r\n *      https://www.apache.org/licenses/LICENSE-2.0\r\n *\r\n * Unless required by applicable law or agreed to in writing, software\r\n * distributed under the License is distributed on an \"AS IS\" BASIS,\r\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n * See the License for the specific language governing permissions and\r\n * limitations under the License.\r\n */\r\n\r\npackage org.springframework.cloud.deployer.spi.test.app;\r\n\r\nimport org.springframework.boot.SpringApplication;\r\nimport org.springframework.boot.autoconfigure.SpringBootApplication;\r\n\r\n/**\r\n * Main application runner. Will keep the main method running, to simulate a long running process.\r\n *\r\n * @author Eric Bottard\r\n */\r\n@SpringBootApplication\r\npublic class DeployerIntegrationTestApplication {\r\n\r\n\tpublic static void main(String[] args) {\r\n\t\tSpringApplication.run(DeployerIntegrationTestApplication.class, args);\r\n\t}\r\n}\r\n"
  },
  {
    "path": "spring-cloud-deployer-spi-test-app/src/main/java/org/springframework/cloud/deployer/spi/test/app/DeployerIntegrationTestProperties.java",
    "content": "/*\r\n * Copyright 2016 the original author or authors.\r\n *\r\n * Licensed under the Apache License, Version 2.0 (the \"License\");\r\n * you may not use this file except in compliance with the License.\r\n * You may obtain a copy of the License at\r\n *\r\n *      https://www.apache.org/licenses/LICENSE-2.0\r\n *\r\n * Unless required by applicable law or agreed to in writing, software\r\n * distributed under the License is distributed on an \"AS IS\" BASIS,\r\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n * See the License for the specific language governing permissions and\r\n * limitations under the License.\r\n */\r\n\r\npackage org.springframework.cloud.deployer.spi.test.app;\r\n\r\nimport java.util.HashSet;\r\nimport java.util.Set;\r\n\r\nimport org.springframework.beans.factory.annotation.Value;\r\nimport org.springframework.boot.context.properties.ConfigurationProperties;\r\n\r\n/**\r\n * Configuration properties for the IntegrationTestProcessor app.\r\n *\r\n * @author Eric Bottard\r\n */\r\n@ConfigurationProperties\r\npublic class DeployerIntegrationTestProperties {\r\n\r\n\tpublic static final String FUNNY_CHARACTERS = \"&'\\\"|< é\\\\(\";\r\n\r\n\t/**\r\n\t * The delay in milliseconds to stall the initialization of this app.\r\n\t * Useful for testing the 'deploying' state of a app.\r\n\t */\r\n\tprivate int initDelay = 0;\r\n\r\n\t/**\r\n\t * The delay in milliseconds after which this app will kill itself.\r\n\t * <p>-1 means don't kill</p>\r\n\t */\r\n\tprivate int killDelay = -1;\r\n\r\n\t/**\r\n\t * The exit code used when this app will kill itself.\r\n\t * <p>Set this to 0 for normal exit</p>\r\n\t */\r\n\tprivate int exitCode = 1;\r\n\r\n\t/**\r\n\t * If not empty, only the app intances whose number(s) are contained in this set\r\n\t * will behave according to the other configuration parameters.\r\n\t */\r\n\tprivate Set<Integer> matchInstances = new HashSet<>();\r\n\r\n\t/**\r\n\t * If not null, this property will be tested against {@link #FUNNY_CHARACTERS}.\r\n\t * This makes sure that a deployer knows how to properly propagate application properties, including\r\n\t * those that contain chars that often require some form of escaping.\r\n\t */\r\n\tprivate String parameterThatMayNeedEscaping;\r\n\r\n\t/**\r\n\t * If not null, this property will be tested against {@link #FUNNY_CHARACTERS}.\r\n\t * This makes sure that a deployer knows how to properly propagate deployment properties, including\r\n\t * those that contain chars that often require some form of escaping.\r\n\t */\r\n\tprivate String commandLineArgValueThatMayNeedEscaping;\r\n\r\n\t@Value(\"${INSTANCE_INDEX:${instance.index:${CF_INSTANCE_INDEX:0}}}\")\r\n\tprivate Integer instanceIndex;\r\n\r\n\tpublic int getInitDelay() {\r\n\t\treturn initDelay;\r\n\t}\r\n\r\n\tpublic void setInitDelay(int initDelay) {\r\n\t\tthis.initDelay = initDelay;\r\n\t}\r\n\r\n\tpublic int getKillDelay() {\r\n\t\treturn killDelay;\r\n\t}\r\n\r\n\tpublic void setKillDelay(int killDelay) {\r\n\t\tthis.killDelay = killDelay;\r\n\t}\r\n\r\n\tpublic int getExitCode() {\r\n\t\treturn exitCode;\r\n\t}\r\n\r\n\tpublic void setExitCode(int exitCode) {\r\n\t\tthis.exitCode = exitCode;\r\n\t}\r\n\r\n\tpublic Set<Integer> getMatchInstances() {\r\n\t\treturn matchInstances;\r\n\t}\r\n\r\n\tpublic void setMatchInstances(Set<Integer> matchInstances) {\r\n\t\tthis.matchInstances = matchInstances;\r\n\t}\r\n\r\n\tpublic String getParameterThatMayNeedEscaping() {\r\n\t\treturn parameterThatMayNeedEscaping;\r\n\t}\r\n\r\n\tpublic void setParameterThatMayNeedEscaping(String parameterThatMayNeedEscaping) {\r\n\t\tthis.parameterThatMayNeedEscaping = parameterThatMayNeedEscaping;\r\n\t}\r\n\r\n\tpublic Integer getInstanceIndex() {\r\n\t\treturn instanceIndex;\r\n\t}\r\n\r\n\tpublic void setInstanceIndex(Integer instanceIndex) {\r\n\t\tthis.instanceIndex = instanceIndex;\r\n\t}\r\n\r\n\tpublic String getCommandLineArgValueThatMayNeedEscaping() {\r\n\t\treturn commandLineArgValueThatMayNeedEscaping;\r\n\t}\r\n\r\n\tpublic void setCommandLineArgValueThatMayNeedEscaping(String commandLineArgValueThatMayNeedEscaping) {\r\n\t\tthis.commandLineArgValueThatMayNeedEscaping = commandLineArgValueThatMayNeedEscaping;\r\n\t}\r\n}\r\n"
  },
  {
    "path": "spring-cloud-deployer-spi-test-app/src/main/resources/application.properties",
    "content": "management.endpoint.shutdown.enabled=true\nmanagement.endpoints.web.exposure.include=*\n"
  },
  {
    "path": "src/main/java/org/springframework/cloud/deployer/spi/kubernetes/DefaultContainerFactory.java",
    "content": "/*\n * Copyright 2015-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.kubernetes;\n\nimport java.io.IOException;\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.LinkedList;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.stream.Collectors;\n\nimport com.fasterxml.jackson.core.JsonProcessingException;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport io.fabric8.kubernetes.api.model.Container;\nimport io.fabric8.kubernetes.api.model.ContainerBuilder;\nimport io.fabric8.kubernetes.api.model.EnvFromSource;\nimport io.fabric8.kubernetes.api.model.EnvVar;\nimport io.fabric8.kubernetes.api.model.EnvVarSource;\nimport io.fabric8.kubernetes.api.model.ObjectFieldSelector;\nimport io.fabric8.kubernetes.api.model.Probe;\nimport org.apache.commons.logging.Log;\nimport org.apache.commons.logging.LogFactory;\n\nimport org.springframework.cloud.deployer.spi.app.AppDeployer;\nimport org.springframework.cloud.deployer.spi.core.AppDeploymentRequest;\nimport org.springframework.cloud.deployer.spi.scheduler.ScheduleRequest;\nimport org.springframework.util.Assert;\nimport org.springframework.util.StringUtils;\n\n/**\n * Create a Kubernetes {@link Container} that will be started as part of a\n * Kubernetes Pod by launching the specified Docker image.\n *\n * @author Florian Rosenberg\n * @author Thomas Risberg\n * @author Donovan Muller\n * @author David Turanski\n * @author Chris Schaefer\n * @author Ilayaperumal Gopinathan\n * @author Glenn Renfro\n * @author Corneil du Plessis\n */\npublic class DefaultContainerFactory implements ContainerFactory {\n    private static Log logger = LogFactory.getLog(DefaultContainerFactory.class);\n    private static final String SPRING_APPLICATION_JSON = \"SPRING_APPLICATION_JSON\";\n    private static final String SPRING_CLOUD_APPLICATION_GUID = \"SPRING_CLOUD_APPLICATION_GUID\";\n\n    private final KubernetesDeployerProperties properties;\n\n    public DefaultContainerFactory(KubernetesDeployerProperties properties) {\n        this.properties = properties;\n    }\n\n    @Override\n    public Container create(ContainerConfiguration containerConfiguration) {\n        AppDeploymentRequest request = containerConfiguration.getAppDeploymentRequest();\n        Map<String, String> deploymentProperties = getDeploymentProperties(request);\n        DeploymentPropertiesResolver deploymentPropertiesResolver = getDeploymentPropertiesResolver(request);\n\n        String image;\n        try {\n            image = request.getResource().getURI().getSchemeSpecificPart();\n        } catch (IOException e) {\n            throw new IllegalArgumentException(\"Unable to get URI for \" + request.getResource(), e);\n        }\n        logger.info(\"Using Docker image: \" + image);\n\n        EntryPointStyle entryPointStyle = deploymentPropertiesResolver.determineEntryPointStyle(deploymentProperties);\n        logger.info(\"Using Docker entry point style: \" + entryPointStyle);\n\n        Map<String, String> envVarsMap = new HashMap<>();\n        for (String envVar : this.properties.getEnvironmentVariables()) {\n            String[] strings = envVar.split(\"=\", 2);\n            Assert.isTrue(strings.length == 2, \"Invalid environment variable declared: \" + envVar);\n            envVarsMap.put(strings[0], strings[1]);\n        }\n        //Create EnvVar entries for additional variables set at the app level\n        //For instance, this may be used to set JAVA_OPTS independently for each app if the base container\n        //image supports it.\n        envVarsMap.putAll(deploymentPropertiesResolver.getAppEnvironmentVariables(deploymentProperties));\n\n        List<String> appArgs = new ArrayList<>();\n\n        Map<String, String> appAdminCredentials = new HashMap<>();\n        properties.getAppAdmin().addCredentialsToAppEnvironmentAsProperties(appAdminCredentials);\n\n        switch (entryPointStyle) {\n            case exec:\n                appArgs = createCommandArgs(request);\n                List<String> finalAppArgs = appArgs;\n                appAdminCredentials.forEach((k, v) -> finalAppArgs.add(String.format(\"--%s=%s\", k, v)));\n\n\n                break;\n            case boot:\n                if (envVarsMap.containsKey(SPRING_APPLICATION_JSON)) {\n                    throw new IllegalStateException(\n                            \"You can't use boot entry point style and also set SPRING_APPLICATION_JSON for the app\");\n                }\n                try {\n                    envVarsMap.put(SPRING_APPLICATION_JSON,\n                            new ObjectMapper().writeValueAsString(request.getDefinition().getProperties()));\n                } catch (JsonProcessingException e) {\n                    throw new IllegalStateException(\"Unable to create SPRING_APPLICATION_JSON\", e);\n                }\n\n                appArgs = request.getCommandlineArguments();\n\n                break;\n            case shell:\n                for (String key : request.getDefinition().getProperties().keySet()) {\n                    String envVar = key.replace('.', '_').toUpperCase(Locale.ROOT);\n                    envVarsMap.put(envVar, request.getDefinition().getProperties().get(key));\n                    envVarsMap.putAll(appAdminCredentials);\n\n                }\n                // Push all the command line arguments as environment properties\n                // The task app name(in case of Composed Task), platform_name and executionId are expected to be updated.\n                // This will also override any of the existing app properties that match the provided cmdline args.\n                for (String cmdLineArg : request.getCommandlineArguments()) {\n                    String cmdLineArgKey;\n\n                    if (cmdLineArg.startsWith(\"--\")) {\n                        cmdLineArgKey = cmdLineArg.substring(2, cmdLineArg.indexOf(\"=\"));\n                    } else {\n                        cmdLineArgKey = cmdLineArg.substring(0, cmdLineArg.indexOf(\"=\"));\n                    }\n\n                    String cmdLineArgValue = cmdLineArg.substring(cmdLineArg.indexOf(\"=\") + 1);\n                    envVarsMap.put(cmdLineArgKey.replace('.', '_').toUpperCase(Locale.ROOT), cmdLineArgValue);\n                }\n                break;\n        }\n\n        List<EnvVar> envVars = new ArrayList<>();\n        for (Map.Entry<String, String> e : envVarsMap.entrySet()) {\n            envVars.add(new EnvVar(e.getKey(), e.getValue(), null));\n        }\n\n        envVars.addAll(deploymentPropertiesResolver.getSecretKeyRefs(deploymentProperties));\n        envVars.addAll(deploymentPropertiesResolver.getConfigMapKeyRefs(deploymentProperties));\n        envVars.add(getGUIDEnvVar());\n\n        if (request.getDeploymentProperties().get(AppDeployer.GROUP_PROPERTY_KEY) != null) {\n            envVars.add(new EnvVar(\"SPRING_CLOUD_APPLICATION_GROUP\",\n                    request.getDeploymentProperties().get(AppDeployer.GROUP_PROPERTY_KEY), null));\n        }\n\n        List<EnvFromSource> envFromSources = new ArrayList<>();\n        envFromSources.addAll(deploymentPropertiesResolver.getConfigMapRefs(deploymentProperties));\n        envFromSources.addAll(deploymentPropertiesResolver.getSecretRefs(deploymentProperties));\n\n        ContainerBuilder container = new ContainerBuilder();\n        container.withName(containerConfiguration.getAppId()).withImage(image).withEnv(envVars).withEnvFrom(envFromSources)\n                .withArgs(appArgs).withVolumeMounts(deploymentPropertiesResolver.getVolumeMounts(deploymentProperties));\n\n        Set<Integer> ports = new HashSet<>();\n\n        Integer defaultPort = containerConfiguration.getExternalPort();\n\n        if (defaultPort != null) {\n            ports.add(defaultPort);\n        }\n\n        ports.addAll(deploymentPropertiesResolver.getContainerPorts(deploymentProperties));\n        configureStartupProbe(containerConfiguration, container, ports);\n        configureReadinessProbe(containerConfiguration, container, ports);\n        configureLivenessProbe(containerConfiguration, container, ports);\n\n        if (!ports.isEmpty()) {\n            for (Integer containerPort : ports) {\n                if (containerConfiguration.isHostNetwork()) {\n                    container.addNewPort().withContainerPort(containerPort).withHostPort(containerPort).endPort();\n                } else {\n                    container.addNewPort().withContainerPort(containerPort).endPort();\n                }\n            }\n        }\n\n        //Override the containers default entry point with one specified during the app deployment\n        List<String> containerCommand = deploymentPropertiesResolver.getContainerCommand(deploymentProperties);\n        if (!containerCommand.isEmpty()) {\n            container.withCommand(containerCommand);\n        }\n        return container.build();\n    }\n\n    private EnvVar getGUIDEnvVar() {\n        ObjectFieldSelector objectFieldSelector = new ObjectFieldSelector();\n        objectFieldSelector.setFieldPath(\"metadata.uid\");\n\n        EnvVarSource envVarSource = new EnvVarSource();\n        envVarSource.setFieldRef(objectFieldSelector);\n\n        EnvVar guidEnvVar = new EnvVar();\n        guidEnvVar.setValueFrom(envVarSource);\n        guidEnvVar.setName(SPRING_CLOUD_APPLICATION_GUID);\n\n        return guidEnvVar;\n    }\n\n    private void configureReadinessProbe(ContainerConfiguration containerConfiguration,\n                                         ContainerBuilder containerBuilder, Set<Integer> ports) {\n        Probe readinessProbe = ProbeCreatorFactory.createReadinessProbe(containerConfiguration, properties,\n                getProbeType(containerConfiguration));\n\n        Integer probePort = null;\n\n        if (readinessProbe.getHttpGet() != null) {\n            probePort = readinessProbe.getHttpGet().getPort().getIntVal();\n        }\n\n        if (readinessProbe.getTcpSocket() != null) {\n            probePort = readinessProbe.getTcpSocket().getPort().getIntVal();\n        }\n\n        if (probePort != null || (containerConfiguration.getExternalPort() != null && readinessProbe.getExec() != null)) {\n            containerBuilder.withReadinessProbe(readinessProbe);\n        }\n\n        if (probePort != null) {\n            ports.add(probePort);\n        }\n    }\n\n    private void configureStartupProbe(ContainerConfiguration containerConfiguration,\n                                       ContainerBuilder containerBuilder, Set<Integer> ports) {\n        Probe startupProbe = ProbeCreatorFactory.createStartupProbe(containerConfiguration, properties,\n                getProbeType(containerConfiguration));\n\n        Integer probePort = null;\n\n        if (startupProbe.getHttpGet() != null) {\n            probePort = startupProbe.getHttpGet().getPort().getIntVal();\n        }\n\n        if (startupProbe.getTcpSocket() != null) {\n            probePort = startupProbe.getTcpSocket().getPort().getIntVal();\n        }\n\n        if (probePort != null || (containerConfiguration.getExternalPort() != null && startupProbe.getExec() != null)) {\n            containerBuilder.withStartupProbe(startupProbe);\n        }\n\n        if (probePort != null) {\n            ports.add(probePort);\n        }\n    }\n\n    private void configureLivenessProbe(ContainerConfiguration containerConfiguration,\n                                        ContainerBuilder containerBuilder, Set<Integer> ports) {\n        Probe livenessProbe = ProbeCreatorFactory.createLivenessProbe(containerConfiguration, properties,\n                getProbeType(containerConfiguration));\n\n        Integer probePort = null;\n\n        if (livenessProbe.getHttpGet() != null) {\n            probePort = livenessProbe.getHttpGet().getPort().getIntVal();\n        }\n\n        if (livenessProbe.getTcpSocket() != null) {\n            probePort = livenessProbe.getTcpSocket().getPort().getIntVal();\n        }\n\n        if (probePort != null || (containerConfiguration.getExternalPort() != null && livenessProbe.getExec() != null)) {\n            containerBuilder.withLivenessProbe(livenessProbe);\n        }\n\n        if (probePort != null) {\n            ports.add(probePort);\n        }\n    }\n\n    private ProbeType getProbeType(ContainerConfiguration containerConfiguration) {\n        AppDeploymentRequest appDeploymentRequest = containerConfiguration.getAppDeploymentRequest();\n        Map<String, String> deploymentProperties = getDeploymentProperties(appDeploymentRequest);\n        DeploymentPropertiesResolver deploymentPropertiesResolver = getDeploymentPropertiesResolver(appDeploymentRequest);\n\n        return deploymentPropertiesResolver.determineProbeType(deploymentProperties);\n    }\n\n    /**\n     * Create command arguments\n     *\n     * @param request the {@link AppDeploymentRequest}\n     * @return the command line arguments to use\n     */\n    List<String> createCommandArgs(AppDeploymentRequest request) {\n        List<String> cmdArgs = new LinkedList<>();\n\n        List<String> commandArgOptions = request.getCommandlineArguments().stream()\n                .map(this::getArgOption)\n                .collect(Collectors.toList());\n\n        // add properties from deployment request\n        Map<String, String> args = request.getDefinition().getProperties();\n        for (Map.Entry<String, String> entry : args.entrySet()) {\n            if (!StringUtils.hasText(entry.getValue())) {\n                logger.warn(\n                        \"Excluding request property with missing value from command args: \" + entry.getKey());\n            } else if (commandArgOptions.contains(entry.getKey())) {\n                logger.warn(\n                        String.format(\n                                \"Excluding request property [--%s=%s] as a command arg. Existing command line argument takes precedence.\"\n                                , entry.getKey(), entry.getValue()));\n            } else {\n                cmdArgs.add(String.format(\"--%s=%s\", entry.getKey(), entry.getValue()));\n            }\n        }\n        // add provided command line args\n        cmdArgs.addAll(request.getCommandlineArguments());\n        logger.debug(\"Using command args: \" + cmdArgs);\n        return cmdArgs;\n    }\n\n    private String getArgOption(String arg) {\n        int indexOfAssignment = arg.indexOf(\"=\");\n        String argOption = (indexOfAssignment < 0) ? arg : arg.substring(0, indexOfAssignment);\n        return argOption.trim().replaceAll(\"^--\", \"\");\n    }\n\n    private DeploymentPropertiesResolver getDeploymentPropertiesResolver(AppDeploymentRequest request) {\n        String propertiesPrefix = (request instanceof ScheduleRequest &&\n                ((ScheduleRequest) request).getSchedulerProperties() != null &&\n                ((ScheduleRequest) request).getSchedulerProperties().size() > 0) ? KubernetesSchedulerProperties.KUBERNETES_SCHEDULER_PROPERTIES_PREFIX :\n                KubernetesDeployerProperties.KUBERNETES_DEPLOYER_PROPERTIES_PREFIX;\n        return new DeploymentPropertiesResolver(propertiesPrefix, this.properties);\n    }\n\n    private Map<String, String> getDeploymentProperties(AppDeploymentRequest request) {\n        return request.getDeploymentProperties();\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/springframework/cloud/deployer/spi/kubernetes/HttpProbeCreator.java",
    "content": "/*\n * Copyright 2020 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.kubernetes;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport io.fabric8.kubernetes.api.model.HTTPGetActionBuilder;\nimport io.fabric8.kubernetes.api.model.HTTPHeader;\nimport io.fabric8.kubernetes.api.model.Probe;\nimport io.fabric8.kubernetes.api.model.ProbeBuilder;\nimport io.fabric8.kubernetes.api.model.Secret;\n\nimport org.springframework.util.Assert;\nimport org.springframework.util.StringUtils;\n\n/**\n * Base class for HTTP based probe creators\n *\n * @author Chris Schaefer\n * @author Corneil du Plessis\n * @since 2.5\n */\npublic abstract class HttpProbeCreator extends ProbeCreator {\n    private static final int BOOT_1_MAJOR_VERSION = 1;\n\n    static final String AUTHORIZATION_HEADER_NAME = \"Authorization\";\n    static final String PROBE_CREDENTIALS_SECRET_KEY_NAME = \"credentials\";\n    static final String BOOT_1_READINESS_PROBE_PATH = \"/info\";\n    static final String BOOT_1_LIVENESS_PROBE_PATH = \"/health\";\n    static final String BOOT_2_READINESS_PROBE_PATH = \"/actuator\" + BOOT_1_READINESS_PROBE_PATH;\n    static final String BOOT_2_LIVENESS_PROBE_PATH = \"/actuator\" + BOOT_1_LIVENESS_PROBE_PATH;\n    static final String DEFAULT_PROBE_SCHEME = \"HTTP\";\n\n    HttpProbeCreator(KubernetesDeployerProperties kubernetesDeployerProperties,\n                     ContainerConfiguration containerConfiguration) {\n        super(kubernetesDeployerProperties, containerConfiguration);\n    }\n\n    protected abstract String getProbePath();\n\n    protected abstract Integer getPort();\n\n    protected abstract String getScheme();\n\n    protected abstract int getTimeout();\n\n    protected Probe create() {\n        HTTPGetActionBuilder httpGetActionBuilder = new HTTPGetActionBuilder()\n                .withPath(getProbePath())\n                .withNewPort(getPort())\n                .withScheme(getScheme());\n\n        List<HTTPHeader> httpHeaders = getHttpHeaders();\n\n        if (!httpHeaders.isEmpty()) {\n            httpGetActionBuilder.withHttpHeaders(httpHeaders);\n        }\n\n        return new ProbeBuilder()\n                .withHttpGet(httpGetActionBuilder.build())\n                .withTimeoutSeconds(getTimeout())\n                .withInitialDelaySeconds(getInitialDelay())\n                .withPeriodSeconds(getPeriod())\n                .withFailureThreshold(getFailure())\n                .withSuccessThreshold(getSuccess())\n                .build();\n\n    }\n\n    private List<HTTPHeader> getHttpHeaders() {\n        List<HTTPHeader> httpHeaders = new ArrayList<>();\n\n        HTTPHeader authenticationHeader = getAuthorizationHeader();\n\n        if (authenticationHeader != null) {\n            httpHeaders.add(authenticationHeader);\n        }\n\n        return httpHeaders;\n    }\n\n    private HTTPHeader getAuthorizationHeader() {\n        HTTPHeader httpHeader = null;\n\n        Secret probeCredentialsSecret = getContainerConfiguration().getProbeCredentialsSecret();\n\n        if (probeCredentialsSecret != null) {\n            Assert.isTrue(probeCredentialsSecret.getData().containsKey(PROBE_CREDENTIALS_SECRET_KEY_NAME),\n                    \"Secret does not contain a key by the name of \" + PROBE_CREDENTIALS_SECRET_KEY_NAME);\n\n            httpHeader = new HTTPHeader();\n            httpHeader.setName(AUTHORIZATION_HEADER_NAME);\n\n            // only Basic auth is supported currently\n            httpHeader.setValue(ProbeAuthenticationType.Basic.name() + \" \" +\n                    probeCredentialsSecret.getData().get(PROBE_CREDENTIALS_SECRET_KEY_NAME));\n        }\n\n        return httpHeader;\n    }\n\n    Integer getDefaultPort() {\n        return getContainerConfiguration().getExternalPort();\n    }\n\n    boolean useBoot1ProbePath() {\n        String bootMajorVersionProperty = KUBERNETES_DEPLOYER_PREFIX + \".bootMajorVersion\";\n        String bootMajorVersionValue = getDeploymentPropertyValue(bootMajorVersionProperty);\n\n        if (StringUtils.hasText(bootMajorVersionValue)) {\n            return Integer.parseInt(bootMajorVersionValue) == BOOT_1_MAJOR_VERSION;\n        }\n\n        return false;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/org/springframework/cloud/deployer/spi/kubernetes/KubernetesAppDeployer.java",
    "content": "/*\n * Copyright 2015-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.kubernetes;\n\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.LinkedList;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\n\nimport io.fabric8.kubernetes.api.model.Container;\nimport io.fabric8.kubernetes.api.model.ContainerBuilder;\nimport io.fabric8.kubernetes.api.model.PersistentVolumeClaim;\nimport io.fabric8.kubernetes.api.model.PersistentVolumeClaimBuilder;\nimport io.fabric8.kubernetes.api.model.PersistentVolumeClaimList;\nimport io.fabric8.kubernetes.api.model.Pod;\nimport io.fabric8.kubernetes.api.model.PodList;\nimport io.fabric8.kubernetes.api.model.PodSpec;\nimport io.fabric8.kubernetes.api.model.Quantity;\nimport io.fabric8.kubernetes.api.model.SecurityContext;\nimport io.fabric8.kubernetes.api.model.Service;\nimport io.fabric8.kubernetes.api.model.ServiceBuilder;\nimport io.fabric8.kubernetes.api.model.ServiceList;\nimport io.fabric8.kubernetes.api.model.ServicePort;\nimport io.fabric8.kubernetes.api.model.ServiceSpecBuilder;\nimport io.fabric8.kubernetes.api.model.VolumeBuilder;\nimport io.fabric8.kubernetes.api.model.VolumeMountBuilder;\nimport io.fabric8.kubernetes.api.model.apps.Deployment;\nimport io.fabric8.kubernetes.api.model.apps.DeploymentBuilder;\nimport io.fabric8.kubernetes.api.model.apps.DeploymentList;\nimport io.fabric8.kubernetes.api.model.apps.StatefulSet;\nimport io.fabric8.kubernetes.api.model.apps.StatefulSetBuilder;\nimport io.fabric8.kubernetes.api.model.apps.StatefulSetList;\nimport io.fabric8.kubernetes.api.model.apps.StatefulSetSpec;\nimport io.fabric8.kubernetes.api.model.apps.StatefulSetSpecBuilder;\nimport io.fabric8.kubernetes.client.KubernetesClient;\nimport io.fabric8.kubernetes.client.dsl.FilterWatchListDeletable;\nimport io.fabric8.kubernetes.client.dsl.ScalableResource;\nimport org.apache.commons.logging.Log;\nimport org.apache.commons.logging.LogFactory;\n\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.cloud.deployer.spi.app.AppDeployer;\nimport org.springframework.cloud.deployer.spi.app.AppInstanceStatus;\nimport org.springframework.cloud.deployer.spi.app.AppScaleRequest;\nimport org.springframework.cloud.deployer.spi.app.AppStatus;\nimport org.springframework.cloud.deployer.spi.app.DeploymentState;\nimport org.springframework.cloud.deployer.spi.core.AppDeploymentRequest;\nimport org.springframework.cloud.deployer.spi.core.RuntimeEnvironmentInfo;\nimport org.springframework.cloud.deployer.spi.kubernetes.support.ArgumentSanitizer;\nimport org.springframework.cloud.deployer.spi.kubernetes.support.PropertyParserUtils;\nimport org.springframework.util.CollectionUtils;\nimport org.springframework.util.StringUtils;\n\n/**\n * A deployer that targets Kubernetes.\n *\n * @author Florian Rosenberg\n * @author Thomas Risberg\n * @author Mark Fisher\n * @author Donovan Muller\n * @author David Turanski\n * @author Ilayaperumal Gopinathan\n * @author Chris Schaefer\n * @author Christian Tzolov\n * @author Omar Gonzalez\n * @author Chris Bono\n */\npublic class KubernetesAppDeployer extends AbstractKubernetesDeployer implements AppDeployer {\n\n    protected final Log logger = LogFactory.getLog(getClass().getName());\n\n    @Autowired\n    public KubernetesAppDeployer(KubernetesDeployerProperties properties, KubernetesClient client) {\n        this(properties, client, new DefaultContainerFactory(properties));\n    }\n\n    @Autowired\n    public KubernetesAppDeployer(KubernetesDeployerProperties properties, KubernetesClient client,\n                                 ContainerFactory containerFactory) {\n        this.properties = properties;\n        this.client = client;\n        this.containerFactory = containerFactory;\n        this.deploymentPropertiesResolver = new DeploymentPropertiesResolver(\n                KubernetesDeployerProperties.KUBERNETES_DEPLOYER_PROPERTIES_PREFIX, properties);\n    }\n\n    @Override\n    public String deploy(AppDeploymentRequest request) {\n        String appId = createDeploymentId(request);\n        if (logger.isDebugEnabled()) {\n            ArgumentSanitizer sanitizer = new ArgumentSanitizer();\n            Map<String,String> sanitized = sanitizer.sanitizeProperties(request.getDeploymentProperties());\n            List<String> sanitizedCommandlineArguments = sanitizer.sanitizeArguments(request.getCommandlineArguments());\n            logger.debug(String.format(\"Deploying app: %s, request: commandlineArguments=%s, deploymentProperties=%s, definition=%s, resource=%s\", appId, sanitizedCommandlineArguments, sanitized, request.getDefinition(), request.getResource()));\n        }\n\n        try {\n            AppStatus status = status(appId);\n\n            if (!status.getState().equals(DeploymentState.unknown)) {\n                throw new IllegalStateException(String.format(\"App '%s' is already deployed\", appId));\n            }\n\n            String indexedProperty = request.getDeploymentProperties().get(INDEXED_PROPERTY_KEY);\n            boolean indexed = (indexedProperty != null) ? Boolean.valueOf(indexedProperty) : false;\n            logPossibleDownloadResourceMessage(request.getResource());\n\n            createService(request);\n            if (indexed) {\n                createStatefulSet(request);\n            } else {\n                createDeployment(request);\n            }\n            return appId;\n        } catch (RuntimeException e) {\n            logger.error(e.getMessage(), e);\n            throw e;\n        }\n    }\n\n    @Override\n    public void undeploy(String appId) {\n        if (logger.isDebugEnabled()) {\n            logger.debug(String.format(\"Undeploying app: %s\", appId));\n        }\n        AppStatus status = status(appId);\n        if (status.getState().equals(DeploymentState.unknown)) {\n            // ensure objects for this appId are deleted in the event a previous deployment failed.\n            // allows for log inspection prior to making an undeploy request.\n            deleteAllObjects(appId);\n\n            throw new IllegalStateException(String.format(\"App '%s' is not deployed\", appId));\n        }\n\n        try {\n            deleteAllObjects(appId);\n        } catch (RuntimeException e) {\n            logger.error(e.getMessage(), e);\n            throw e;\n        }\n    }\n\n    @Override\n    public AppStatus status(String appId) {\n        Map<String, String> selector = new HashMap<>();\n        ServiceList services = client.services().withLabel(SPRING_APP_KEY, appId).list();\n        selector.put(SPRING_APP_KEY, appId);\n        PodList podList = client.pods().withLabels(selector).list();\n        if (logger.isDebugEnabled()) {\n            logger.debug(String.format(\"Building AppStatus for app: %s\", appId));\n            if (podList != null && podList.getItems() != null) {\n                logger.debug(String.format(\"Pods for appId %s: %d\", appId, podList.getItems().size()));\n                for (Pod pod : podList.getItems()) {\n                    logger.debug(String.format(\"Pod: %s\", pod.getMetadata().getName()));\n                }\n            }\n        }\n        AppStatus status = buildAppStatus(appId, podList, services);\n        if (logger.isDebugEnabled()) {\n            logger.debug(String.format(\"status:%s = %s\", appId, status.getDeploymentId()));\n            for (AppInstanceStatus instanceStatus : status.getInstances().values()) {\n                logger.debug(String.format(\"status:%s:%s:%s\", instanceStatus.getId(), instanceStatus.getState(), instanceStatus.getAttributes()));\n            }\n        }\n\n        return status;\n    }\n\n    @Override\n    public String getLog(String appId) {\n        Map<String, String> selector = new HashMap<>();\n        selector.put(SPRING_APP_KEY, appId);\n        PodList podList = client.pods().withLabels(selector).list();\n        StringBuilder logAppender = new StringBuilder();\n        for (Pod pod : podList.getItems()) {\n\n            if (pod.getSpec().getContainers().size() > 1) {\n                for (Container container : pod.getSpec().getContainers()) {\n                    if (container.getEnv().stream().anyMatch(envVar -> \"SPRING_CLOUD_APPLICATION_GUID\".equals(envVar.getName()))) {\n                        //find log for this container\n                        logAppender.append(this.client.pods()\n                                .withName(pod.getMetadata().getName())\n                                .inContainer(container.getName())\n                                .tailingLines(500).getLog());\n                        break;\n                    }\n\n                }\n            } else {\n                logAppender.append(this.client.pods().withName(pod.getMetadata().getName()).tailingLines(500).getLog());\n            }\n        }\n\n        return logAppender.toString();\n    }\n\n    @Override\n    public void scale(AppScaleRequest appScaleRequest) {\n        String deploymentId = appScaleRequest.getDeploymentId();\n        if (logger.isDebugEnabled()) {\n            logger.debug(String.format(\"Scale app: %s to: %s\", deploymentId, appScaleRequest.getCount()));\n        }\n\n        ScalableResource scalableResource = this.client.apps().deployments().withName(deploymentId);\n        if (scalableResource.get() == null) {\n            scalableResource = this.client.apps().statefulSets().withName(deploymentId);\n        }\n        if (scalableResource.get() == null) {\n            throw new IllegalStateException(String.format(\"App '%s' is not deployed\", deploymentId));\n        }\n        scalableResource.scale(appScaleRequest.getCount(), true);\n    }\n\n    @Override\n    public RuntimeEnvironmentInfo environmentInfo() {\n        return super.createRuntimeEnvironmentInfo(AppDeployer.class, this.getClass());\n    }\n\n    private Deployment createDeployment(AppDeploymentRequest request) {\n\n        String appId = createDeploymentId(request);\n        if (logger.isDebugEnabled()) {\n            logger.debug(String.format(\"Creating Deployment: %s\", appId));\n        }\n\n        int replicas = getCountFromRequest(request);\n\n        Map<String, String> idMap = createIdMap(appId, request);\n\n        Map<String, String> kubernetesDeployerProperties = request.getDeploymentProperties();\n\n        Map<String, String> annotations = this.deploymentPropertiesResolver.getPodAnnotations(kubernetesDeployerProperties);\n        Map<String, String> deploymentLabels = this.deploymentPropertiesResolver.getDeploymentLabels(kubernetesDeployerProperties);\n\n        PodSpec podSpec = createPodSpec(request);\n\n        Deployment d = new DeploymentBuilder().withNewMetadata().withName(appId).withLabels(idMap)\n                .addToLabels(SPRING_MARKER_KEY, SPRING_MARKER_VALUE).addToLabels(deploymentLabels).endMetadata()\n                .withNewSpec().withNewSelector().addToMatchLabels(idMap).endSelector().withReplicas(replicas)\n                .withNewTemplate().withNewMetadata().withLabels(idMap).addToLabels(SPRING_MARKER_KEY, SPRING_MARKER_VALUE)\n                .addToLabels(deploymentLabels).withAnnotations(annotations).endMetadata().withSpec(podSpec).endTemplate()\n                .endSpec().build();\n\n        d = client.apps().deployments().create(d);\n        if (logger.isDebugEnabled()) {\n            logger.debug(\"created:\" + d.getFullResourceName() + \":\" + d.getStatus());\n        }\n        return d;\n    }\n\n    private int getCountFromRequest(AppDeploymentRequest request) {\n        String countProperty = request.getDeploymentProperties().get(COUNT_PROPERTY_KEY);\n        return (countProperty != null) ? Integer.parseInt(countProperty) : 1;\n    }\n\n    /**\n     * Create a StatefulSet\n     *\n     * @param request the {@link AppDeploymentRequest}\n     */\n    protected void createStatefulSet(AppDeploymentRequest request) {\n\n        String appId = createDeploymentId(request);\n\n        int externalPort = getExternalPort(request);\n\n        Map<String, String> idMap = createIdMap(appId, request);\n\n        int replicas = getCountFromRequest(request);\n\n        Map<String, String> kubernetesDeployerProperties = request.getDeploymentProperties();\n        if (logger.isDebugEnabled()) {\n            logger.debug(String.format(\"Creating StatefulSet: %s on %d with %d replicas\", appId, externalPort, replicas));\n        }\n\n        Map<String, Quantity> storageResource = Collections.singletonMap(\"storage\",\n                new Quantity(this.deploymentPropertiesResolver.getStatefulSetStorage(kubernetesDeployerProperties)));\n\n        String storageClassName = this.deploymentPropertiesResolver.getStatefulSetStorageClassName(kubernetesDeployerProperties);\n\n        String volumeClaimTemplateName = this.deploymentPropertiesResolver.getStatefulSetVolumeClaimTemplateName(kubernetesDeployerProperties);\n\n        volumeClaimTemplateName = StringUtils.hasText(volumeClaimTemplateName) ? volumeClaimTemplateName : appId;\n\n        PersistentVolumeClaimBuilder persistentVolumeClaimBuilder = new PersistentVolumeClaimBuilder().withNewSpec().\n                withStorageClassName(storageClassName).withAccessModes(Collections.singletonList(\"ReadWriteOnce\"))\n                .withNewResources().addToLimits(storageResource).addToRequests(storageResource).endResources()\n                .endSpec().withNewMetadata().withName(volumeClaimTemplateName).withLabels(idMap)\n                .addToLabels(SPRING_MARKER_KEY, SPRING_MARKER_VALUE).endMetadata();\n\n        PodSpec podSpec = createPodSpec(request);\n\n        podSpec.getVolumes().add(new VolumeBuilder().withName(\"config\").withNewEmptyDir().endEmptyDir().build());\n\n        podSpec.getContainers().get(0).getVolumeMounts()\n                .add(new VolumeMountBuilder().withName(\"config\").withMountPath(\"/config\").build());\n\n        String statefulSetInitContainerImageName = this.deploymentPropertiesResolver.getStatefulSetInitContainerImageName(kubernetesDeployerProperties);\n\n        podSpec.getInitContainers().add(createStatefulSetInitContainer(podSpec, statefulSetInitContainerImageName));\n\n        Map<String, String> deploymentLabels = this.deploymentPropertiesResolver.getDeploymentLabels(request.getDeploymentProperties());\n        Map<String, String> annotations = this.deploymentPropertiesResolver.getPodAnnotations(kubernetesDeployerProperties);\n\n        StatefulSetSpec spec = new StatefulSetSpecBuilder().withNewSelector().addToMatchLabels(idMap)\n                .addToMatchLabels(SPRING_MARKER_KEY, SPRING_MARKER_VALUE).endSelector()\n                .withVolumeClaimTemplates(persistentVolumeClaimBuilder.build()).withServiceName(appId)\n                .withPodManagementPolicy(\"Parallel\").withReplicas(replicas).withNewTemplate().withNewMetadata()\n                .withLabels(idMap).addToLabels(SPRING_MARKER_KEY, SPRING_MARKER_VALUE).addToLabels(deploymentLabels)\n                .addToAnnotations(annotations).endMetadata().withSpec(podSpec).endTemplate().build();\n\n        StatefulSet statefulSet = new StatefulSetBuilder().withNewMetadata().withName(appId).withLabels(idMap)\n                .addToLabels(SPRING_MARKER_KEY, SPRING_MARKER_VALUE).addToLabels(deploymentLabels).endMetadata().withSpec(spec).build();\n\n        statefulSet = client.apps().statefulSets().create(statefulSet);\n        if (logger.isDebugEnabled()) {\n            logger.debug(\"created:\" + statefulSet.getFullResourceName() + \":\" + statefulSet.getStatus());\n        }\n    }\n\n    protected void createService(AppDeploymentRequest request) {\n\n        String appId = createDeploymentId(request);\n\n        int externalPort = getExternalPort(request);\n        if (logger.isDebugEnabled()) {\n            ArgumentSanitizer sanitizer = new ArgumentSanitizer();\n            Map<String, String> sanitized = sanitizer.sanitizeProperties(request.getDeploymentProperties());\n            logger.debug(String.format(\"Creating Service: %s on %d using %s\", appId, externalPort, sanitized));\n        }\n\n        Map<String, String> idMap = createIdMap(appId, request);\n\n        ServiceSpecBuilder spec = new ServiceSpecBuilder();\n        boolean isCreateLoadBalancer = false;\n        String createLoadBalancer = PropertyParserUtils.getDeploymentPropertyValue(request.getDeploymentProperties(),\n                \"spring.cloud.deployer.kubernetes.createLoadBalancer\");\n        String createNodePort = PropertyParserUtils.getDeploymentPropertyValue(request.getDeploymentProperties(),\n                \"spring.cloud.deployer.kubernetes.createNodePort\");\n        String additionalServicePorts = PropertyParserUtils.getDeploymentPropertyValue(request.getDeploymentProperties(),\n                \"spring.cloud.deployer.kubernetes.servicePorts\");\n\n        if (createLoadBalancer != null && createNodePort != null) {\n            throw new IllegalArgumentException(\"Cannot create NodePort and LoadBalancer at the same time.\");\n        }\n\n        if (createLoadBalancer == null) {\n            isCreateLoadBalancer = properties.isCreateLoadBalancer();\n        } else {\n            if (\"true\".equals(createLoadBalancer.toLowerCase(Locale.ROOT))) {\n                isCreateLoadBalancer = true;\n            }\n        }\n\n        if (isCreateLoadBalancer) {\n            spec.withType(\"LoadBalancer\");\n        }\n\n        ServicePort servicePort = new ServicePort();\n        servicePort.setPort(externalPort);\n        servicePort.setName(\"port-\" + externalPort);\n\n        if (createNodePort != null) {\n            spec.withType(\"NodePort\");\n            if (!\"true\".equals(createNodePort.toLowerCase(Locale.ROOT))) {\n                try {\n                    Integer nodePort = Integer.valueOf(createNodePort);\n                    servicePort.setNodePort(nodePort);\n                } catch (NumberFormatException e) {\n                    throw new IllegalArgumentException(\n                            String.format(\"Invalid value: %s: provided port is not valid.\", createNodePort));\n                }\n            }\n        }\n\n        Set<ServicePort> servicePorts = new HashSet<>();\n        servicePorts.add(servicePort);\n\n        if (StringUtils.hasText(additionalServicePorts)) {\n            servicePorts.addAll(addAdditionalServicePorts(additionalServicePorts));\n        }\n\n        spec.addAllToPorts(servicePorts);\n\n        Map<String, String> annotations = this.deploymentPropertiesResolver.getServiceAnnotations(request.getDeploymentProperties());\n\n        String serviceName = getServiceName(request, appId);\n\n        // if called from skipper, use unique selectors to maintain connectivity\n        // between service and pods that are being brought up/down\n        if (request.getDeploymentProperties().containsKey(APP_NAME_PROPERTY_KEY)) {\n            spec.withSelector(Collections.singletonMap(APP_NAME_KEY,\n                    request.getDeploymentProperties().get(APP_NAME_PROPERTY_KEY)));\n\n            String groupId = request.getDeploymentProperties().get(AppDeployer.GROUP_PROPERTY_KEY);\n\n            if (groupId != null) {\n                spec.addToSelector(SPRING_GROUP_KEY, groupId);\n            }\n        } else {\n            spec.withSelector(idMap);\n        }\n\n        Service service = client.services().createOrReplace(\n                new ServiceBuilder().withNewMetadata().withName(serviceName)\n                        .withLabels(idMap).withAnnotations(annotations).addToLabels(SPRING_MARKER_KEY, SPRING_MARKER_VALUE)\n                        .endMetadata().withSpec(spec.build()).build()\n        );\n        if (logger.isDebugEnabled()) {\n            logger.debug(\"created:\" + service.getFullResourceName() + \":\" + service.getStatus());\n        }\n    }\n\n    // logic to support using un-versioned service names when called from skipper\n    private String getServiceName(AppDeploymentRequest request, String appId) {\n        String appName = request.getDeploymentProperties().get(APP_NAME_PROPERTY_KEY);\n\n        // if we have an un-versioned app name from skipper\n        if (StringUtils.hasText(appName)) {\n            String serviceName = formatServiceName(request, appName);\n\n            // need to check if a versioned service exists to maintain backwards compat..\n            // version number itself isn't checked on as it could be different if create or upgrade\n            // which we don't know at runtime....\n            List<Service> services = client.services().withLabel(SPRING_DEPLOYMENT_KEY).list().getItems();\n\n            for (Service service : services) {\n                String serviceMetadataName = service.getMetadata().getName();\n\n                if (serviceMetadataName.startsWith(serviceName + \"-v\")) {\n                    return appId;\n                }\n            }\n\n            return serviceName;\n        }\n\n        // no un-versioned app name provided, maybe not called from skipper, return whatever is in appId\n        return appId;\n    }\n\n    private String formatServiceName(AppDeploymentRequest request, String appName) {\n        String groupId = request.getDeploymentProperties().get(AppDeployer.GROUP_PROPERTY_KEY);\n\n        String serviceName = groupId == null ? String.format(\"%s\", appName)\n                : String.format(\"%s-%s\", groupId, appName);\n\n        return serviceName.replace('.', '-').toLowerCase(Locale.ROOT);\n    }\n\n    private Set<ServicePort> addAdditionalServicePorts(String additionalServicePorts) {\n        Set<ServicePort> ports = new HashSet<>();\n\n        String[] servicePorts = additionalServicePorts.split(\",\");\n        for (String servicePort : servicePorts) {\n            Integer port = Integer.parseInt(servicePort.trim());\n            ServicePort extraServicePort = new ServicePort();\n            extraServicePort.setPort(port);\n            extraServicePort.setName(\"port-\" + port);\n\n            ports.add(extraServicePort);\n        }\n\n        return ports;\n    }\n\n    /**\n     * For StatefulSets, create an init container to parse ${HOSTNAME} to get the `instance.index` and write it to\n     * config/application.properties on a shared volume so the main container has it. Using the legacy annotation\n     * configuration since the current client version does not directly support InitContainers.\n     * <p>\n     * Since 1.8 the annotation method has been removed, and the initContainer API is supported since 1.6\n     *\n\t * @param podSpec the current pod spec the container is being added to\n     * @param imageName the image name to use in the init container\n     * @return a container definition with the  aforementioned configuration\n     */\n    private Container createStatefulSetInitContainer(PodSpec podSpec, String imageName) {\n        List<String> command = new LinkedList<>();\n\n        String commandString = String\n                .format(\"%s && %s\", setIndexProperty(\"INSTANCE_INDEX\"), setIndexProperty(\"spring.cloud.stream.instanceIndex\"));\n\n\t\tcommand.add(\"sh\");\n        command.add(\"-c\");\n        command.add(commandString);\n\n        ContainerBuilder containerBuilder = new ContainerBuilder().withName(\"index-provider\")\n                .withImage(imageName)\n                .withImagePullPolicy(\"IfNotPresent\")\n                .withCommand(command)\n                .withVolumeMounts(new VolumeMountBuilder().withName(\"config\").withMountPath(\"/config\").build());\n\n\t\tif (!CollectionUtils.isEmpty(podSpec.getContainers())) {\n\t\t\tSecurityContext securityContext = podSpec.getContainers().get(0).getSecurityContext();\n\t\t\tif (securityContext != null) {\n\t\t\t\tcontainerBuilder.withSecurityContext(securityContext);\n\t\t\t}\n\t\t}\n\n\t\treturn containerBuilder.build();\n    }\n\n    private String setIndexProperty(String name) {\n        return String\n                .format(\"echo %s=\\\"$(expr $HOSTNAME | grep -o \\\"[[:digit:]]*$\\\")\\\" >> \"+\n                        \"/config/application.properties\", name);\n    }\n\n    private void deleteAllObjects(String appIdToDelete) {\n        Map<String, String> labels = Collections.singletonMap(SPRING_APP_KEY, appIdToDelete);\n        if (logger.isDebugEnabled()) {\n            logger.debug(String.format(\"deleteAllObjects:%s:%s\", appIdToDelete, labels));\n        }\n        // waitForLoadBalancerReady(labels); // Not negative effect in not waiting for loadbalancer.\n        deleteService(labels);\n        deleteDeployment(labels);\n        deleteStatefulSet(labels);\n        deletePod(labels);\n        deletePvc(labels);\n    }\n\n    private void deleteService(Map<String, String> labels) {\n        FilterWatchListDeletable<Service, ServiceList> servicesToDelete =\n                client.services().withLabels(labels);\n\n        if (servicesToDelete != null && servicesToDelete.list().getItems() != null) {\n            boolean servicesDeleted = servicesToDelete.delete();\n            if (logger.isDebugEnabled()) {\n                logger.debug(String.format(\"Service deleted for: %s - %b\", labels, servicesDeleted));\n            }\n        }\n    }\n\n    private void deleteDeployment(Map<String, String> labels) {\n        FilterWatchListDeletable<Deployment, DeploymentList> deploymentsToDelete =\n                client.apps().deployments().withLabels(labels);\n\n        if (deploymentsToDelete != null && deploymentsToDelete.list().getItems() != null) {\n            boolean deploymentsDeleted = deploymentsToDelete.delete();\n            if (logger.isDebugEnabled()) {\n                logger.debug(String.format(\"Deployment deleted for: %s - %b\", labels, deploymentsDeleted));\n            }\n        }\n    }\n\n    private void deleteStatefulSet(Map<String, String> labels) {\n        FilterWatchListDeletable<StatefulSet, StatefulSetList> ssToDelete =\n                client.apps().statefulSets().withLabels(labels);\n\n        if (ssToDelete != null && ssToDelete.list().getItems() != null) {\n            boolean ssDeleted = ssToDelete.delete();\n            if (logger.isDebugEnabled()) {\n                logger.debug(String.format(\"StatefulSet deleted for: %s - %b\", labels, ssDeleted));\n            }\n        }\n    }\n\n    private void deletePod(Map<String, String> labels) {\n        FilterWatchListDeletable<Pod, PodList> podsToDelete = client.pods()\n                .withLabels(labels);\n\n        if (podsToDelete != null && podsToDelete.list().getItems() != null) {\n            boolean podsDeleted = podsToDelete.delete();\n            if (logger.isDebugEnabled()) {\n                logger.debug(String.format(\"Pod deleted for: %s - %b\", labels, podsDeleted));\n            }\n        }\n    }\n\n    private void deletePvc(Map<String, String> labels) {\n        FilterWatchListDeletable<PersistentVolumeClaim, PersistentVolumeClaimList> pvcsToDelete = client.persistentVolumeClaims()\n                .withLabels(labels);\n\n        if (pvcsToDelete != null && pvcsToDelete.list().getItems() != null) {\n            boolean pvcsDeleted = pvcsToDelete.delete();\n            if (logger.isDebugEnabled()) {\n                logger.debug(String.format(\"PVC deleted for: %s - %b\", labels, pvcsDeleted));\n            }\n        }\n    }\n\n    private void waitForLoadBalancerReady(Map<String, String> labels) {\n        List<Service> services = client.services().withLabels(labels).list().getItems();\n\n        if (!services.isEmpty()) {\n            Service svc = services.get(0);\n            if (svc != null && \"LoadBalancer\".equals(svc.getSpec().getType())) {\n                int tries = 0;\n                int maxWait = properties.getMinutesToWaitForLoadBalancer() * 6; // we check 6 times per minute\n                while (tries++ < maxWait) {\n                    if (svc.getStatus() != null && svc.getStatus().getLoadBalancer() != null\n                            && svc.getStatus().getLoadBalancer().getIngress() != null && svc.getStatus()\n                            .getLoadBalancer().getIngress().isEmpty()) {\n                        if (tries % 6 == 0) {\n                            logger.warn(\"Waiting for LoadBalancer to complete before deleting it ...\");\n                        }\n                        if (logger.isDebugEnabled()) {\n                            logger.debug(String.format(\"Waiting for LoadBalancer, try %d\", tries));\n                        }\n                        try {\n                            Thread.sleep(10000L);\n                        } catch (InterruptedException e) {\n                        }\n                        svc = client.services().withLabels(labels).list().getItems().get(0);\n                    } else {\n                        break;\n                    }\n                }\n                if (logger.isDebugEnabled()) {\n                    logger.debug(String.format(\"LoadBalancer Ingress: %s\",\n                            svc.getStatus().getLoadBalancer().getIngress().toString()));\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/springframework/cloud/deployer/spi/kubernetes/KubernetesAppInstanceStatus.java",
    "content": "/*\n * Copyright 2015-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.kubernetes;\n\nimport java.nio.file.Paths;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.concurrent.ConcurrentHashMap;\n\nimport io.fabric8.kubernetes.api.model.Container;\nimport io.fabric8.kubernetes.api.model.ContainerStatus;\nimport io.fabric8.kubernetes.api.model.IntOrString;\nimport io.fabric8.kubernetes.api.model.Pod;\nimport io.fabric8.kubernetes.api.model.Service;\nimport io.fabric8.kubernetes.api.model.ServicePort;\nimport org.apache.commons.logging.Log;\nimport org.apache.commons.logging.LogFactory;\n\nimport org.springframework.cloud.deployer.spi.app.AppInstanceStatus;\nimport org.springframework.cloud.deployer.spi.app.DeploymentState;\n\n/**\n * Represents the status of a module.\n *\n * @author Florian Rosenberg\n * @author Thomas Risberg\n * @author David Turanski\n * @author Chris Schaefer\n */\npublic class KubernetesAppInstanceStatus implements AppInstanceStatus {\n\n    private static Log logger = LogFactory.getLog(KubernetesAppInstanceStatus.class);\n\n    private final Pod pod;\n\n    private final Service service;\n\n    private final KubernetesDeployerProperties properties;\n\n    private ContainerStatus containerStatus;\n\n    private RunningPhaseDeploymentStateResolver runningPhaseDeploymentStateResolver;\n\n    @Deprecated\n    public KubernetesAppInstanceStatus(Pod pod, Service service, KubernetesDeployerProperties properties) {\n        this.pod = pod;\n        this.service = service;\n        this.properties = properties;\n        // we assume one container per pod\n        if (pod != null && pod.getStatus().getContainerStatuses().size() == 1) {\n            this.containerStatus = pod.getStatus().getContainerStatuses().get(0);\n        } else {\n            this.containerStatus = null;\n        }\n        this.runningPhaseDeploymentStateResolver = new DefaultRunningPhaseDeploymentStateResolver(properties);\n    }\n\n    public KubernetesAppInstanceStatus(Pod pod, Service service, KubernetesDeployerProperties properties,\n                                       ContainerStatus containerStatus) {\n        this.pod = pod;\n        this.service = service;\n        this.properties = properties;\n        this.containerStatus = containerStatus;\n        this.runningPhaseDeploymentStateResolver = new DefaultRunningPhaseDeploymentStateResolver(properties);\n    }\n\n    /**\n     * Override the default {@link RunningPhaseDeploymentStateResolver} implementation.\n     *\n     * @param runningPhaseDeploymentStateResolver the\n     *                                            {@link RunningPhaseDeploymentStateResolver} to use\n     */\n    public void setRunningPhaseDeploymentStateResolver(\n            RunningPhaseDeploymentStateResolver runningPhaseDeploymentStateResolver) {\n        this.runningPhaseDeploymentStateResolver = runningPhaseDeploymentStateResolver;\n    }\n\n    @Override\n    public String getId() {\n        return pod == null ? \"N/A\" : pod.getMetadata().getName();\n    }\n\n    @Override\n    public DeploymentState getState() {\n        return pod != null && containerStatus != null ? mapState() : DeploymentState.unknown;\n    }\n\n    /**\n     * Maps Kubernetes phases/states onto Spring Cloud Deployer states\n     */\n    private DeploymentState mapState() {\n        logger.debug(String.format(\"%s - Phase [ %s ]\", pod.getMetadata().getName(), pod.getStatus().getPhase()));\n        logger.debug(String.format(\"%s - ContainerStatus [ %s ]\", pod.getMetadata().getName(), containerStatus));\n        switch (pod.getStatus().getPhase()) {\n\n            case \"Pending\":\n                return DeploymentState.deploying;\n\n            // We only report a module as running if the container is also ready to service requests.\n            // We also implement the Readiness check as part of the container to ensure ready means\n            // that the module is up and running and not only that the JVM has been created and the\n            // Spring module is still starting up\n            case \"Running\":\n                // we assume we only have one container\n                return runningPhaseDeploymentStateResolver.resolve(containerStatus);\n            case \"Failed\":\n                return DeploymentState.failed;\n\n            case \"Unknown\":\n                return DeploymentState.unknown;\n\n            default:\n                return DeploymentState.unknown;\n        }\n    }\n\n    @Override\n    public Map<String, String> getAttributes() {\n\n        ConcurrentHashMap<String, String> result = new ConcurrentHashMap<>();\n\n        if (pod != null) {\n            result.put(\"pod.name\", pod.getMetadata().getName());\n            result.put(\"pod.startTime\", nullSafe(pod.getStatus().getStartTime()));\n            result.put(\"pod.ip\", nullSafe(pod.getStatus().getPodIP()));\n            result.put(\"actuator.path\", determineActuatorPathFromLivenessProbe(pod));\n            result.put(\"actuator.port\", determineActuatorPortFromLivenessProbe(pod, result.get(\"actuator.path\")));\n            result.put(\"host.ip\", nullSafe(pod.getStatus().getHostIP()));\n            result.put(\"phase\", nullSafe(pod.getStatus().getPhase()));\n            result.put(AbstractKubernetesDeployer.SPRING_APP_KEY.replace('-', '.'),\n                    pod.getMetadata().getLabels().get(AbstractKubernetesDeployer.SPRING_APP_KEY));\n            result.put(AbstractKubernetesDeployer.SPRING_DEPLOYMENT_KEY.replace('-', '.'),\n                    pod.getMetadata().getLabels().get(AbstractKubernetesDeployer.SPRING_DEPLOYMENT_KEY));\n            result.put(\"guid\", pod.getMetadata().getUid());\n        } else {\n            logger.debug(\"getAttributes:no pod\");\n        }\n        if (service != null) {\n            result.put(\"service.name\", service.getMetadata().getName());\n            if (\"LoadBalancer\".equals(service.getSpec().getType())) {\n                if (service.getStatus() != null && service.getStatus().getLoadBalancer() != null\n                        && service.getStatus().getLoadBalancer().getIngress() != null && !service.getStatus()\n                        .getLoadBalancer().getIngress().isEmpty()) {\n                    String externalIp = service.getStatus().getLoadBalancer().getIngress().get(0).getIp();\n                    if (externalIp == null) {\n                        externalIp = service.getStatus().getLoadBalancer().getIngress().get(0).getHostname();\n                    }\n                    result.put(\"service.external.ip\", externalIp);\n                    List<ServicePort> ports = service.getSpec().getPorts();\n                    int port = 0;\n                    if (ports != null && ports.size() > 0) {\n                        port = ports.get(0).getPort();\n                        result.put(\"service.external.port\", String.valueOf(port));\n                    }\n                    if (externalIp != null) {\n                        result.put(\"url\", \"http://\" + externalIp + (port > 0 && port != 80 ? \":\" + port : \"\"));\n                    }\n\n                }\n            }\n        } else {\n            logger.debug(\"getAttributes:no service\");\n        }\n        if (containerStatus != null) {\n            result.put(\"container.restartCount\", \"\" + containerStatus.getRestartCount());\n            if (containerStatus.getLastState() != null && containerStatus.getLastState().getTerminated() != null) {\n                result.put(\"container.lastState.terminated.exitCode\",\n                        \"\" + containerStatus.getLastState().getTerminated().getExitCode());\n                result.put(\"container.lastState.terminated.reason\",\n                        containerStatus.getLastState().getTerminated().getReason());\n            }\n            if (containerStatus.getState() != null && containerStatus.getState().getTerminated() != null) {\n                result.put(\"container.state.terminated.exitCode\",\n                        \"\" + containerStatus.getState().getTerminated().getExitCode());\n                result.put(\"container.state.terminated.reason\", containerStatus.getState().getTerminated().getReason());\n            }\n        } else {\n            logger.debug(\"getAttributes:no containerStatus\");\n        }\n        if (logger.isDebugEnabled()) {\n            logger.debug(\"getAttributes:\" + result);\n        }\n        return result;\n    }\n\n    private String nullSafe(String value) {\n        return value == null ? \"\" : value;\n    }\n\n    private String determineActuatorPathFromLivenessProbe(Pod pod) {\n        return pod.getSpec().getContainers().stream()\n                .filter((Container container) -> container.getLivenessProbe() != null &&\n                        container.getLivenessProbe().getHttpGet() != null)\n                .findFirst()\n                .map(container ->\n                        Paths.get(container.getLivenessProbe().getHttpGet().getPath()).getParent().toString())\n                .orElse(\"/actuator\");\n    }\n\n    private String determineActuatorPortFromLivenessProbe(Pod pod, String path) {\n        IntOrString intOrString = pod.getSpec().getContainers().stream()\n                .filter(container -> container.getLivenessProbe() != null &&\n                        container.getLivenessProbe().getHttpGet() != null &&\n                        container.getLivenessProbe().getHttpGet().getPath().equals(path))\n                .findFirst()\n                .map(container -> container.getLivenessProbe().getHttpGet().getPort())\n                .orElse(new IntOrString(8080));\n        return intOrString.getIntVal() != null ? String.valueOf(intOrString.getIntVal()) : intOrString.getStrVal();\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/springframework/cloud/deployer/spi/kubernetes/KubernetesDeployerProperties.java",
    "content": "/*\n * Copyright 2015-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.kubernetes;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport io.fabric8.kubernetes.api.model.NodeAffinity;\nimport io.fabric8.kubernetes.api.model.PodAffinity;\nimport io.fabric8.kubernetes.api.model.PodAntiAffinity;\nimport io.fabric8.kubernetes.api.model.Volume;\nimport io.fabric8.kubernetes.api.model.VolumeMount;\nimport io.fabric8.kubernetes.client.Config;\n\nimport org.springframework.boot.context.properties.ConfigurationProperties;\nimport org.springframework.boot.context.properties.NestedConfigurationProperty;\nimport org.springframework.cloud.deployer.spi.app.AppAdmin;\n\n/**\n * @author Florian Rosenberg\n * @author Thomas Risberg\n * @author Donovan Muller\n * @author Ilayaperumal Gopinathan\n * @author Leonardo Diniz\n * @author Chris Schaefer\n * @author David Turanski\n * @author Enrique Medina Montenegro\n * @author Chris Bono\n * @author Corneil du Plessis\n */\n@ConfigurationProperties(prefix = KubernetesDeployerProperties.KUBERNETES_DEPLOYER_PROPERTIES_PREFIX)\npublic class KubernetesDeployerProperties {\n\n    static final String KUBERNETES_DEPLOYER_PROPERTIES_PREFIX = \"spring.cloud.deployer.kubernetes\";\n\n    /**\n     * Constants for app deployment properties that don't have a deployer level default\n     * property.\n     */\n    static final String KUBERNETES_DEPLOYMENT_NODE_SELECTOR = \"spring.cloud.deployer.kubernetes.deployment.nodeSelector\";\n\n    /**\n     * The maximum concurrent tasks allowed for this platform instance.\n     */\n    private int maximumConcurrentTasks = 20;\n\n    @NestedConfigurationProperty\n    private Config fabric8 = Config.autoConfigure(null);\n\n\n    public Config getFabric8() {\n        return this.fabric8;\n    }\n\n    public void setFabric8(Config fabric8) {\n        this.fabric8 = fabric8;\n    }\n\n    /**\n     * Encapsulates resources for Kubernetes Container resource limits\n     */\n    public static class LimitsResources {\n\n        /**\n         * Container resource cpu limit.\n         */\n        private String cpu;\n\n        /**\n         * Container resource memory limit.\n         */\n        private String memory;\n        /**\n         * Container resource ephemeral storage size limit.\n         */\n        private String ephemeralStorage;\n\n        /**\n         * Container resource hugepages-2Mi limit.\n         */\n        private String hugepages2Mi;\n\n        /**\n         * Container resource hugepages-1Gi limit.\n         */\n        private String hugepages1Gi;\n\n        /**\n         * Container GPU vendor name for limit\n         * If gpuVendor=nvidia.com/gpu and gpuCount=2 then the following will be used\n         * {@code\n         * limits:\n         *   nvidia.com/gpu: 2\n         * }\n         */\n        private String gpuVendor;\n\n        /**\n         * Container GPU count for limit.\n         */\n        private String gpuCount;\n\n        public LimitsResources() {\n        }\n\n        /**\n         * 'All' args constructor\n         *\n         * @param cpu    Container resource cpu limit\n         * @param memory Container resource memory limit\n         * @deprecated This method should no longer be used to set all fields at construct time.\n         * <p>\n         * Use the default constructor and set() methods instead.\n         */\n        @Deprecated\n        public LimitsResources(String cpu, String memory) {\n            this.cpu = cpu;\n            this.memory = memory;\n        }\n\n        @Deprecated\n        public LimitsResources(String cpu, String memory, String ephemeralStorage, String hugepages2Mi, String hugepages1Gi, String gpuVendor, String gpuCount) {\n            this.cpu = cpu;\n            this.memory = memory;\n            this.ephemeralStorage = ephemeralStorage;\n            this.hugepages2Mi = hugepages2Mi;\n            this.hugepages1Gi = hugepages1Gi;\n            this.gpuVendor = gpuVendor;\n            this.gpuCount = gpuCount;\n        }\n\n        public String getCpu() {\n            return cpu;\n        }\n\n        public void setCpu(String cpu) {\n            this.cpu = cpu;\n        }\n\n        public String getMemory() {\n            return memory;\n        }\n\n        public void setMemory(String memory) {\n            this.memory = memory;\n        }\n\n        public String getEphemeralStorage() {\n            return ephemeralStorage;\n        }\n\n        public void setEphemeralStorage(String ephemeralStorage) {\n            this.ephemeralStorage = ephemeralStorage;\n        }\n\n        public String getHugepages2Mi() {\n            return hugepages2Mi;\n        }\n\n        public void setHugepages2Mi(String hugepages2Mi) {\n            this.hugepages2Mi = hugepages2Mi;\n        }\n\n        public String getHugepages1Gi() {\n            return hugepages1Gi;\n        }\n\n        public void setHugepages1Gi(String hugepages1Gi) {\n            this.hugepages1Gi = hugepages1Gi;\n        }\n\n        public String getGpuVendor() {\n            return gpuVendor;\n        }\n\n        public void setGpuVendor(String gpuVendor) {\n            this.gpuVendor = gpuVendor;\n        }\n\n        public String getGpuCount() {\n            return gpuCount;\n        }\n\n        public void setGpuCount(String gpuCount) {\n            this.gpuCount = gpuCount;\n        }\n    }\n\n    /**\n     * Encapsulates resources for Kubernetes Container resource requests\n     */\n    public static class RequestsResources {\n\n        /**\n         * Container request limit.\n         */\n        private String cpu;\n\n        /**\n         * Container memory limit.\n         */\n        private String memory;\n\n        /**\n         * Container resource ephemeral storage size request.\n         */\n        private String ephemeralStorage;\n\n        /**\n         * Container resource hugepages-2Mi request.\n         */\n        private String hugepages2Mi;\n\n        /**\n         * Container resource hugepages-1Gi request.\n         */\n        private String hugepages1Gi;\n\n        public RequestsResources() {\n        }\n\n        public RequestsResources(String cpu, String memory) {\n            this.cpu = cpu;\n            this.memory = memory;\n        }\n\n        public RequestsResources(String cpu, String memory, String ephemeralStorage, String hugepages2Mi, String hugepages1Gi) {\n            this.cpu = cpu;\n            this.memory = memory;\n            this.ephemeralStorage = ephemeralStorage;\n            this.hugepages2Mi = hugepages2Mi;\n            this.hugepages1Gi = hugepages1Gi;\n        }\n\n        public String getCpu() {\n            return cpu;\n        }\n\n        public void setCpu(String cpu) {\n            this.cpu = cpu;\n        }\n\n        public String getMemory() {\n            return memory;\n        }\n\n        public void setMemory(String memory) {\n            this.memory = memory;\n        }\n\n        public String getEphemeralStorage() {\n            return ephemeralStorage;\n        }\n\n        public void setEphemeralStorage(String ephemeralStorage) {\n            this.ephemeralStorage = ephemeralStorage;\n        }\n\n        public String getHugepages2Mi() {\n            return hugepages2Mi;\n        }\n\n        public void setHugepages2Mi(String hugepages2Mi) {\n            this.hugepages2Mi = hugepages2Mi;\n        }\n\n        public String getHugepages1Gi() {\n            return hugepages1Gi;\n        }\n\n        public void setHugepages1Gi(String hugepages1Gi) {\n            this.hugepages1Gi = hugepages1Gi;\n        }\n    }\n\n    public static class StatefulSet {\n\n        private VolumeClaimTemplate volumeClaimTemplate = new VolumeClaimTemplate();\n\n        public VolumeClaimTemplate getVolumeClaimTemplate() {\n            return volumeClaimTemplate;\n        }\n\n        public void setVolumeClaimTemplate(VolumeClaimTemplate volumeClaimTemplate) {\n            this.volumeClaimTemplate = volumeClaimTemplate;\n        }\n\n        public static class VolumeClaimTemplate {\n\n            /**\n             * VolumeClaimTemplate name\n             */\n            private String name;\n\n            /**\n             * VolumeClaimTemplate storage.\n             */\n            private String storage = \"10m\";\n\n            /**\n             * VolumeClaimTemplate storage class name.\n             */\n            private String storageClassName;\n\n            public String getName() {\n                return this.name;\n            }\n\n            public void setName(String name) {\n                this.name = name;\n            }\n\n            public String getStorage() {\n                return storage;\n            }\n\n            public void setStorage(String storage) {\n                this.storage = storage;\n            }\n\n            public String getStorageClassName() {\n                return storageClassName;\n            }\n\n            public void setStorageClassName(String storageClassName) {\n                this.storageClassName = storageClassName;\n            }\n        }\n    }\n\n    public static class Toleration {\n\n        private String effect;\n\n        private String key;\n\n        private String operator;\n\n        private Long tolerationSeconds;\n\n        private String value;\n\n        public String getEffect() {\n            return effect;\n        }\n\n        public void setEffect(String effect) {\n            this.effect = effect;\n        }\n\n        public String getKey() {\n            return key;\n        }\n\n        public void setKey(String key) {\n            this.key = key;\n        }\n\n        public String getOperator() {\n            return operator;\n        }\n\n        public void setOperator(String operator) {\n            this.operator = operator;\n        }\n\n        public Long getTolerationSeconds() {\n            return tolerationSeconds;\n        }\n\n        public void setTolerationSeconds(Long tolerationSeconds) {\n            this.tolerationSeconds = tolerationSeconds;\n        }\n\n        public String getValue() {\n            return value;\n        }\n\n        public void setValue(String value) {\n            this.value = value;\n        }\n    }\n\n    static class KeyRef {\n        private String envVarName;\n\n        private String dataKey;\n\n        public void setEnvVarName(String envVarName) {\n            this.envVarName = envVarName;\n        }\n\n        public String getEnvVarName() {\n            return envVarName;\n        }\n\n        public void setDataKey(String dataKey) {\n            this.dataKey = dataKey;\n        }\n\n        public String getDataKey() {\n            return dataKey;\n        }\n    }\n\n    public static class SecretKeyRef extends KeyRef {\n        private String secretName;\n\n        public void setSecretName(String secretName) {\n            this.secretName = secretName;\n        }\n\n        public String getSecretName() {\n            return secretName;\n        }\n    }\n\n    public static class ConfigMapKeyRef extends KeyRef {\n        private String configMapName;\n\n        public void setConfigMapName(String configMapName) {\n            this.configMapName = configMapName;\n        }\n\n        public String getConfigMapName() {\n            return configMapName;\n        }\n    }\n\n    public static class PodSecurityContext {\n\t\t/**\n\t\t * The numeric user ID to run pod container processes under\n\t\t */\n\t\tprivate Long runAsUser;\n\n\t\t/**\n\t\t * The numeric group id to run the entrypoint of the container process\n\t\t */\n\t\tprivate Long runAsGroup;\n\n\t\t/**\n\t\t * Indicates that the container must run as a non-root user\n\t\t */\n\t\tprivate Boolean runAsNonRoot;\n\n        /**\n         * The numeric group ID for the volumes of the pod\n         */\n        private Long fsGroup;\n\n\t\t/**\n\t\t * Defines behavior of changing ownership and permission of the volume before being\n\t\t * exposed inside pod (only applies to volume types which support fsGroup based\n\t\t * ownership and permissions) - possible values are \"OnRootMismatch\", \"Always\"\n\t\t */\n\t\tprivate String fsGroupChangePolicy;\n\n        /**\n         * The numeric group IDs applied to the pod container processes, in addition to the container's primary group ID\n         */\n        private Long[] supplementalGroups;\n\n\t\t/**\n\t\t * The seccomp options to use for the pod containers\n\t\t */\n\t\tprivate SeccompProfile seccompProfile;\n\n\t\t/**\n\t\t * The SELinux context to be applied to the pod containers\n\t\t */\n\t\tprivate SELinuxOptions seLinuxOptions;\n\n\t\t/**\n\t\t * List of namespaced sysctls used for the pod (not used when spec.os.name is windows).\n\t\t */\n\t\tprivate List<SysctlInfo> sysctls;\n\n\t\t/**\n\t\t * The Windows specific settings applied to all containers.\n\t\t */\n\t\tprivate WindowsSecurityContextOptions windowsOptions;\n\n        public void setRunAsUser(Long runAsUser) {\n            this.runAsUser = runAsUser;\n        }\n\n        public Long getRunAsUser() {\n            return this.runAsUser;\n        }\n\n\t\tpublic Long getRunAsGroup() {\n\t\t\treturn runAsGroup;\n\t\t}\n\n\t\tpublic void setRunAsGroup(Long runAsGroup) {\n\t\t\tthis.runAsGroup = runAsGroup;\n\t\t}\n\n\t\tpublic Boolean getRunAsNonRoot() {\n\t\t\treturn runAsNonRoot;\n\t\t}\n\n\t\tpublic void setRunAsNonRoot(Boolean runAsNonRoot) {\n\t\t\tthis.runAsNonRoot = runAsNonRoot;\n\t\t}\n\n\t\tpublic void setFsGroup(Long fsGroup) {\n            this.fsGroup = fsGroup;\n        }\n\n        public Long getFsGroup() {\n            return fsGroup;\n        }\n\n\t\tpublic String getFsGroupChangePolicy() {\n\t\t\treturn fsGroupChangePolicy;\n\t\t}\n\n\t\tpublic void setFsGroupChangePolicy(String fsGroupChangePolicy) {\n\t\t\tthis.fsGroupChangePolicy = fsGroupChangePolicy;\n\t\t}\n\n\t\tpublic void setSupplementalGroups(Long[] supplementalGroups) {\n            this.supplementalGroups = supplementalGroups;\n        }\n\n        public Long[] getSupplementalGroups() {\n            return supplementalGroups;\n        }\n\n        public SeccompProfile getSeccompProfile() {\n            return seccompProfile;\n        }\n\n        public void setSeccompProfile(SeccompProfile seccompProfile) {\n            this.seccompProfile = seccompProfile;\n        }\n\n\t\tpublic SELinuxOptions getSeLinuxOptions() {\n\t\t\treturn seLinuxOptions;\n\t\t}\n\n\t\tpublic void setSeLinuxOptions(SELinuxOptions seLinuxOptions) {\n\t\t\tthis.seLinuxOptions = seLinuxOptions;\n\t\t}\n\n\t\tpublic List<SysctlInfo> getSysctls() {\n\t\t\treturn sysctls;\n\t\t}\n\n\t\tpublic void setSysctls(List<SysctlInfo> sysctls) {\n\t\t\tthis.sysctls = sysctls;\n\t\t}\n\n\t\tpublic WindowsSecurityContextOptions getWindowsOptions() {\n\t\t\treturn windowsOptions;\n\t\t}\n\n\t\tpublic void setWindowsOptions(WindowsSecurityContextOptions windowsOptions) {\n\t\t\tthis.windowsOptions = windowsOptions;\n\t\t}\n\t}\n\n\tpublic static class ContainerSecurityContext {\n\n\t\t/**\n\t\t * Whether a process can gain more privileges than its parent process\n\t\t */\n\t\tprivate Boolean allowPrivilegeEscalation;\n\n\t\t/**\n\t\t * The capabilities to add/drop when running the container (cannot be set when spec.os.name is windows)\n\t\t */\n\t\tprivate Capabilities capabilities;\n\n\t\t/**\n\t\t * Run container in privileged mode.\n\t\t */\n\t\tprivate Boolean privileged;\n\n\t\t/**\n\t\t * The type of proc mount to use for the container (cannot be set when spec.os.name is windows)\n\t\t */\n\t\tprivate String procMount;\n\n\t\t/**\n\t\t * Mounts the container's root filesystem as read-only\n\t\t */\n\t\tprivate Boolean readOnlyRootFilesystem;\n\n\t\t/**\n\t\t * The numeric user ID to run pod container processes under\n\t\t */\n\t\tprivate Long runAsUser;\n\n\t\t/**\n\t\t * The numeric group id to run the entrypoint of the container process\n\t\t */\n\t\tprivate Long runAsGroup;\n\n\t\t/**\n\t\t * Indicates that the container must run as a non-root user\n\t\t */\n\t\tprivate Boolean runAsNonRoot;\n\n\t\t/**\n\t\t * The seccomp options to use for the container\n\t\t */\n\t\tprivate SeccompProfile seccompProfile;\n\n\t\t/**\n\t\t * The SELinux context to be applied to the container.\n\t\t */\n\t\tprivate SELinuxOptions seLinuxOptions;\n\n\t\t/**\n\t\t * The Windows specific settings applied to the container.\n\t\t */\n\t\tprivate WindowsSecurityContextOptions windowsOptions;\n\n\t\tpublic Boolean getAllowPrivilegeEscalation() {\n\t\t\treturn allowPrivilegeEscalation;\n\t\t}\n\n\t\tpublic void setAllowPrivilegeEscalation(Boolean allowPrivilegeEscalation) {\n\t\t\tthis.allowPrivilegeEscalation = allowPrivilegeEscalation;\n\t\t}\n\n\t\tpublic Capabilities getCapabilities() {\n\t\t\treturn capabilities;\n\t\t}\n\n\t\tpublic void setCapabilities(Capabilities capabilities) {\n\t\t\tthis.capabilities = capabilities;\n\t\t}\n\n\t\tpublic Boolean getPrivileged() {\n\t\t\treturn privileged;\n\t\t}\n\n\t\tpublic void setPrivileged(Boolean privileged) {\n\t\t\tthis.privileged = privileged;\n\t\t}\n\n\t\tpublic String getProcMount() {\n\t\t\treturn procMount;\n\t\t}\n\n\t\tpublic void setProcMount(String procMount) {\n\t\t\tthis.procMount = procMount;\n\t\t}\n\n\t\tpublic Boolean getReadOnlyRootFilesystem() {\n\t\t\treturn readOnlyRootFilesystem;\n\t\t}\n\n\t\tpublic void setReadOnlyRootFilesystem(Boolean readOnlyRootFilesystem) {\n\t\t\tthis.readOnlyRootFilesystem = readOnlyRootFilesystem;\n\t\t}\n\n\t\tpublic Long getRunAsUser() {\n\t\t\treturn runAsUser;\n\t\t}\n\n\t\tpublic void setRunAsUser(Long runAsUser) {\n\t\t\tthis.runAsUser = runAsUser;\n\t\t}\n\n\t\tpublic Long getRunAsGroup() {\n\t\t\treturn runAsGroup;\n\t\t}\n\n\t\tpublic void setRunAsGroup(Long runAsGroup) {\n\t\t\tthis.runAsGroup = runAsGroup;\n\t\t}\n\n\t\tpublic Boolean getRunAsNonRoot() {\n\t\t\treturn runAsNonRoot;\n\t\t}\n\n\t\tpublic void setRunAsNonRoot(Boolean runAsNonRoot) {\n\t\t\tthis.runAsNonRoot = runAsNonRoot;\n\t\t}\n\n\t\tpublic SeccompProfile getSeccompProfile() {\n\t\t\treturn seccompProfile;\n\t\t}\n\n\t\tpublic void setSeccompProfile(SeccompProfile seccompProfile) {\n\t\t\tthis.seccompProfile = seccompProfile;\n\t\t}\n\n\t\tpublic SELinuxOptions getSeLinuxOptions() {\n\t\t\treturn seLinuxOptions;\n\t\t}\n\n\t\tpublic void setSeLinuxOptions(SELinuxOptions seLinuxOptions) {\n\t\t\tthis.seLinuxOptions = seLinuxOptions;\n\t\t}\n\n\t\tpublic WindowsSecurityContextOptions getWindowsOptions() {\n\t\t\treturn windowsOptions;\n\t\t}\n\n\t\tpublic void setWindowsOptions(WindowsSecurityContextOptions windowsOptions) {\n\t\t\tthis.windowsOptions = windowsOptions;\n\t\t}\n\t}\n\n\t/**\n\t * Adds and removes POSIX capabilities from running containers.\n\t */\n\tpublic static class Capabilities {\n\t\t/**\n\t\t * Added capabilities.\n\t\t */\n\t\tprivate List<String> add;\n\n\t\t/**\n\t\t * Removed capabilities.\n\t\t */\n\t\tprivate List<String> drop;\n\n\t\tpublic List<String> getAdd() {\n\t\t\treturn add;\n\t\t}\n\n\t\tpublic void setAdd(List<String> add) {\n\t\t\tthis.add = add;\n\t\t}\n\n\t\tpublic List<String> getDrop() {\n\t\t\treturn drop;\n\t\t}\n\n\t\tpublic void setDrop(List<String> drop) {\n\t\t\tthis.drop = drop;\n\t\t}\n\t}\n\n\t/**\n     * Defines a pod seccomp profile settings.\n     */\n\tpublic static class SeccompProfile {\n\n        /**\n         * Type of seccomp profile.\n         */\n        private String type;\n\n        /**\n         * Path of the pre-configured profile on the node, relative to the kubelet's configured Seccomp profile location, only valid when type is \"Localhost\".\n         */\n        private String localhostProfile;\n\n        public String getType() {\n            return type;\n        }\n\n        public void setType(String type) {\n            this.type = type;\n        }\n\n        public String getLocalhostProfile() {\n            return localhostProfile;\n        }\n\n        public void setLocalhostProfile(String localhostProfile) {\n            this.localhostProfile = localhostProfile;\n        }\n    }\n\n\t/**\n\t * The labels to be applied to the container.\n\t */\n\tpublic static class SELinuxOptions {\n\t\t/**\n\t\t * Level label applied to the container\n\t\t */\n\t\tprivate String level;\n\n\t\t/**\n\t\t * Role Level label applied to the container\n\t\t */\n\t\tprivate String role;\n\n\t\t/**\n\t\t * Type label applied to the container\n\t\t */\n\t\tprivate String type;\n\n\t\t/**\n\t\t * User label applied to the container\n\t\t */\n\t\tprivate String user;\n\n\t\tpublic String getLevel() {\n\t\t\treturn level;\n\t\t}\n\n\t\tpublic void setLevel(String level) {\n\t\t\tthis.level = level;\n\t\t}\n\n\t\tpublic String getRole() {\n\t\t\treturn role;\n\t\t}\n\n\t\tpublic void setRole(String role) {\n\t\t\tthis.role = role;\n\t\t}\n\n\t\tpublic String getType() {\n\t\t\treturn type;\n\t\t}\n\n\t\tpublic void setType(String type) {\n\t\t\tthis.type = type;\n\t\t}\n\n\t\tpublic String getUser() {\n\t\t\treturn user;\n\t\t}\n\n\t\tpublic void setUser(String user) {\n\t\t\tthis.user = user;\n\t\t}\n\t}\n\n\t/**\n\t * Sysctl defines a kernel parameter to be set on the pod.\n\t */\n\tpublic static class SysctlInfo {\n\t\t/**\n\t\t * Name of the property\n\t\t */\n\t\tprivate String name;\n\n\t\t/**\n\t\t * Value of the property\n\t\t */\n\t\tprivate String value;\n\n\t\tpublic String getName() {\n\t\t\treturn name;\n\t\t}\n\n\t\tpublic void setName(String name) {\n\t\t\tthis.name = name;\n\t\t}\n\n\t\tpublic String getValue() {\n\t\t\treturn value;\n\t\t}\n\n\t\tpublic void setValue(String value) {\n\t\t\tthis.value = value;\n\t\t}\n\t}\n\n\t/**\n\t * Contains Windows-specific options and credentials.\n\t */\n\tpublic static class WindowsSecurityContextOptions {\n\n\t\t/**\n\t\t * Where the GMSA admission webhook inlines the contents of the GMSA credential spec\n\t\t * named by the GMSACredentialSpecName field.\n\t\t */\n\t\tprivate String gmsaCredentialSpec;\n\n\t\t/**\n\t\t * The name of the GMSA credential spec to use.\n\t\t */\n\t\tprivate String gmsaCredentialSpecName;\n\n\t\t/**\n\t\t * Whether a container should be run as a 'Host Process' container.\n\t\t */\n\t\tprivate Boolean hostProcess;\n\n\t\t/**\n\t\t * The username in Windows to run the entrypoint of the container process.\n\t\t */\n\t\tprivate String runAsUserName;\n\n\t\tpublic String getGmsaCredentialSpec() {\n\t\t\treturn gmsaCredentialSpec;\n\t\t}\n\n\t\tpublic void setGmsaCredentialSpec(String gmsaCredentialSpec) {\n\t\t\tthis.gmsaCredentialSpec = gmsaCredentialSpec;\n\t\t}\n\n\t\tpublic String getGmsaCredentialSpecName() {\n\t\t\treturn gmsaCredentialSpecName;\n\t\t}\n\n\t\tpublic void setGmsaCredentialSpecName(String gmsaCredentialSpecName) {\n\t\t\tthis.gmsaCredentialSpecName = gmsaCredentialSpecName;\n\t\t}\n\n\t\tpublic Boolean getHostProcess() {\n\t\t\treturn hostProcess;\n\t\t}\n\n\t\tpublic void setHostProcess(Boolean hostProcess) {\n\t\t\tthis.hostProcess = hostProcess;\n\t\t}\n\n\t\tpublic String getRunAsUserName() {\n\t\t\treturn runAsUserName;\n\t\t}\n\n\t\tpublic void setRunAsUserName(String runAsUserName) {\n\t\t\tthis.runAsUserName = runAsUserName;\n\t\t}\n\t}\n\n\tpublic static class Lifecycle {\n        private Hook postStart;\n        private Hook preStop;\n\n        Hook getPreStop() {\n            return preStop;\n        }\n\n        Hook getPostStart() {\n            return postStart;\n        }\n\n        void setPostStart(Hook postStart) {\n            this.postStart = postStart;\n        }\n\n        void setPreStop(Hook preStop) {\n            this.preStop = preStop;\n\n        }\n\n        public static class Hook {\n            private Exec exec;\n\n            Exec getExec() {\n                return exec;\n            }\n\n            void setExec(Exec exec) {\n                this.exec = exec;\n            }\n        }\n\n        public static class Exec {\n            private List<String> command;\n\n            List<String> getCommand() {\n                return command;\n            }\n\n            void setCommand(List<String> command) {\n                this.command = command;\n            }\n        }\n    }\n\n\tpublic static class InitContainer extends ContainerProperties {\n    }\n\n    static class Container extends io.fabric8.kubernetes.api.model.Container {\n    }\n\n    public static class ContainerProperties {\n        private String imageName;\n\n        private String containerName;\n\n        private List<String> commands;\n\n        private List<VolumeMount> volumeMounts;\n\n        /**\n         * Environment variables to set for any deployed init container.\n         */\n        private String[] environmentVariables = new String[]{};\n\n        public String getImageName() {\n            return imageName;\n        }\n\n        public void setImageName(String imageName) {\n            this.imageName = imageName;\n        }\n\n        public String getContainerName() {\n            return containerName;\n        }\n\n        public void setContainerName(String containerName) {\n            this.containerName = containerName;\n        }\n\n        public List<String> getCommands() {\n            return commands;\n        }\n\n        public void setCommands(List<String> commands) {\n            this.commands = commands;\n        }\n\n        public List<VolumeMount> getVolumeMounts() {\n            return volumeMounts;\n        }\n\n        public void setVolumeMounts(List<VolumeMount> volumeMounts) {\n            this.volumeMounts = volumeMounts;\n        }\n\n        public String[] getEnvironmentVariables() {\n            return environmentVariables;\n        }\n\n        public void setEnvironmentVariables(String[] environmentVariables) {\n            this.environmentVariables = environmentVariables;\n        }\n    }\n\n    /**\n     * The {@link RestartPolicy} to use. Defaults to {@link RestartPolicy#Always}.\n     */\n    private RestartPolicy restartPolicy = RestartPolicy.Always;\n\n    /**\n     * The default service account name to use for tasks.\n     */\n    protected static final String DEFAULT_TASK_SERVICE_ACCOUNT_NAME = \"default\";\n\n    /**\n     * Service account name to use for tasks, defaults to:\n     * {@link #DEFAULT_TASK_SERVICE_ACCOUNT_NAME}\n     */\n    private String taskServiceAccountName = DEFAULT_TASK_SERVICE_ACCOUNT_NAME;\n\n    /**\n     * Obtains the {@link RestartPolicy} to use. Defaults to\n     * {@link #restartPolicy}.\n     *\n     * @return the {@link RestartPolicy} to use\n     */\n    public RestartPolicy getRestartPolicy() {\n        return restartPolicy;\n    }\n\n    /**\n     * Sets the {@link RestartPolicy} to use.\n     *\n     * @param restartPolicy the {@link RestartPolicy} to use\n     */\n    public void setRestartPolicy(RestartPolicy restartPolicy) {\n        this.restartPolicy = restartPolicy;\n    }\n\n    /**\n     * Obtains the service account name to use for tasks.\n     *\n     * @return the service account name\n     */\n    public String getTaskServiceAccountName() {\n        return taskServiceAccountName;\n    }\n\n    /**\n     * Sets the service account name to use for tasks.\n     *\n     * @param taskServiceAccountName the service account name\n     */\n    public void setTaskServiceAccountName(String taskServiceAccountName) {\n        this.taskServiceAccountName = taskServiceAccountName;\n    }\n\n    /**\n     * Name of the environment variable that can define the Kubernetes namespace to use.\n     */\n    public static final String ENV_KEY_KUBERNETES_NAMESPACE = \"KUBERNETES_NAMESPACE\";\n\n    private static String KUBERNETES_NAMESPACE = System.getenv(\"KUBERNETES_NAMESPACE\");\n\n    /**\n     * Namespace to use.\n     */\n    private String namespace = KUBERNETES_NAMESPACE;\n\n    /**\n     * Secrets for a access a private registry to pull images.\n     */\n    private String imagePullSecret;\n\n    /**\n     * List of Secrets for a access a private registry to pull images.\n     */\n    private List<String> imagePullSecrets;\n\n    /**\n     * Delay in seconds when the Kubernetes liveness check of the app container should start\n     * checking its health status.\n     */\n    private int livenessHttpProbeDelay = 1;\n    /**\n     * When the probe fails more times than the failure value the pod is restarted.\n     */\n    private int livenessHttpProbeFailure = 3;\n    /**\n     * When the probe passes success times it is considered live.\n     */\n    private int livenessHttpProbeSuccess = 1;\n\n    /**\n     * Period in seconds for performing the Kubernetes liveness check of the app container.\n     */\n    private int livenessHttpProbePeriod = 60;\n\n    /**\n     * Timeout in seconds for the check to wait for a response after which it is considered a failed check.\n     */\n    private int livenessHttpProbeTimeout = 5;\n    /**\n     * Timeout in seconds for the liveness check to wait for a response before the check is considered a failure.\n     */\n    private int livenessTcpProbeTimeout = 5;\n\n    /**\n     * Path that app container has to respond to for liveness check.\n     */\n    private String livenessHttpProbePath;\n\n    /**\n     * Port that app container has to respond on for liveness check.\n     */\n    private Integer livenessHttpProbePort = null;\n\n    /**\n     * Schema that app container has to respond on for liveness check.\n     */\n    private String livenessHttpProbeScheme = \"HTTP\";\n\n    /**\n     * Schema that app container has to respond to for readiness check.\n     */\n    private String readinessHttpProbeScheme = \"HTTP\";\n\n    /**\n     * Schema that app container has to respond to for startup check.\n     */\n    private String startupProbeScheme = \"HTTP\";\n\n    /**\n     * If present will assign to spec.shareProcessNamespace of the Pod.\n     */\n    private Boolean shareProcessNamespace;\n    /**\n     * Delay in seconds when the readiness check of the app container should start checking if\n     * the module is fully up and running.\n     */\n    private int readinessHttpProbeDelay = 1;\n\n    private int readinessHttpProbeSuccess = 1;\n\n    private int readinessHttpProbeFailure = 3;\n\n    private int readinessTcpProbeTimeout = 3;\n\n    private int readinessTcpProbeFailure = 3;\n    private int readinessTcpProbeSuccess = 1;\n    /**\n     * Delay in seconds when the startup check of the app container should start checking if\n     * the module is fully up and running.\n     */\n    private int startupHttpProbeDelay = 30;\n    /**\n     * The number of time the http startup probe will be allowed to fail before restarting the pod.\n     */\n\n    private int startupHttpProbeFailure = 20;\n    /**\n     * The number of time the http startup probe will be required to pass before considering the application started.\n     */\n    private int startupHttpProbeSuccess = 1;\n    /**\n     * The number of time the tcp startup probe will be allowed to fail before restarting the pod.\n     */\n    private int startupTcpProbeFailure = 20;\n    /**\n     * The number of time the tcp startup probe will be required to pass before considering the application started.\n     */\n    private int startupTcpProbeSuccess = 1;\n    /**\n     * The number of time the command startup probe will be allowed to fail before restarting the pod.\n     */\n    private int startupCommandProbeFailure = 10;\n    /**\n     * The number of time the command startup probe will be required to pass before considering the application started.\n     */\n    private int startupCommandProbeSuccess = 1;\n    /**\n     * Period in seconds to perform the readiness check of the app container.\n     */\n    private int readinessHttpProbePeriod = 10;\n\n    /**\n     * Period in seconds to perform the startup check of the app container.\n     */\n    private int startupHttpProbePeriod = 3;\n\n    /**\n     * Timeout in seconds that the app container has to respond to its status during\n     * the startup check.\n     */\n    private int startupHttpProbeTimeout = 5;\n\n    /**\n     * Timeout in seconds that the app container has to respond to its health status during\n     * the readiness check.\n     */\n    private int readinessHttpProbeTimeout = 5;\n\n    private int readinessCommandProbeFailure = 3;\n    private int readinessCommandProbeSuccess = 1;\n\n    /**\n     * Path that app container has to respond to for readiness check.\n     */\n    private String readinessHttpProbePath;\n\n    /**\n     * Path that app container has to respond to for startup check.\n     */\n    private String startupHttpProbePath;\n    /**\n     * Port that app container has to respond on for readiness check.\n     */\n    private Integer readinessHttpProbePort = null;\n\n    /**\n     * Port that app container has to respond on for startup check.\n     */\n    private Integer startupHttpProbePort = null;\n    /**\n     * Delay in seconds when the liveness TCP check should start checking\n     */\n    private int livenessTcpProbeDelay = 10;\n\n    private int livenessTcpProbeSuccess = 1;\n\n    private int livenessTcpProbeFailure = 3;\n\n    /**\n     * Period in seconds to perform the liveness TCP check\n     */\n    private int livenessTcpProbePeriod = 60;\n\n    /**\n     * The TCP port the liveness probe should check\n     */\n    private Integer livenessTcpProbePort = null;\n\n    /**\n     * Delay in seconds when the readiness TCP check should start checking\n     */\n    private int readinessTcpProbeDelay = 1;\n\n    /**\n     * Period in seconds to perform the readiness TCP check\n     */\n    private int readinessTcpProbePeriod = 10;\n\n    /**\n     * The TCP port the readiness probe should check\n     */\n    private Integer readinessTcpProbePort = null;\n\n    /**\n     * Delay in seconds when the readiness command check should start checking\n     */\n    private int readinessCommandProbeDelay = 1;\n\n    /**\n     * Period in seconds to perform the readiness command check\n     */\n    private int readinessCommandProbePeriod = 10;\n\n    /**\n     * The command the readiness probe should use to check\n     */\n    private String readinessCommandProbeCommand = null;\n\n    /**\n     * Delay in seconds when the readiness TCP check should start checking\n     */\n    private int startupTcpProbeDelay = 30;\n\n    private int startupTcpProbeTimeout = 5;\n\n    /**\n     * Period in seconds to perform the readiness TCP check\n     */\n    private int startupTcpProbePeriod = 3;\n\n    /**\n     * The TCP port the readiness probe should check\n     */\n    private Integer startupTcpProbePort = null;\n\n    /**\n     * Delay in seconds when the readiness command check should start checking\n     */\n    private int startupCommandProbeDelay = 30;\n\n    /**\n     * Period in seconds to perform the readiness command check\n     */\n    private int startupCommandProbePeriod = 10;\n\n    /**\n     * The command the readiness probe should use to check\n     */\n    private String startupCommandProbeCommand = null;\n\n    /**\n     * Delay in seconds when the liveness command check should start checking\n     */\n    private int livenessCommandProbeDelay = 10;\n\n    private int livenessCommandProbeFailure = 3;\n    private int livenessCommandProbeSuccess = 1;\n\n    /**\n     * Period in seconds to perform the liveness command check\n     */\n    private int livenessCommandProbePeriod = 10;\n\n    /**\n     * The command the liveness probe should use to check\n     */\n    private String livenessCommandProbeCommand = null;\n\n    /**\n     * The secret name containing the credentials to use when accessing secured probe\n     * endpoints.\n     */\n    private String probeCredentialsSecret;\n\n    /**\n     * The probe type to use when doing health checks. Defaults to HTTP.\n     */\n    private ProbeType probeType = ProbeType.HTTP;\n\n    /**\n     * Memory and CPU limits (i.e. maximum needed values) to allocate for a Pod.\n     */\n    private LimitsResources limits = new LimitsResources();\n\n    /**\n     * Memory and CPU requests (i.e. guaranteed needed values) to allocate for a Pod.\n     */\n    private RequestsResources requests = new RequestsResources();\n\n    /**\n     * Tolerations to allocate for a Pod.\n     */\n    private List<Toleration> tolerations = new ArrayList<>();\n\n    /**\n     * Secret key references to be added to the Pod environment.\n     */\n    private List<SecretKeyRef> secretKeyRefs = new ArrayList<>();\n\n    /**\n     * ConfigMap key references to be added to the Pod environment.\n     */\n    private List<ConfigMapKeyRef> configMapKeyRefs = new ArrayList<>();\n\n    /**\n     * ConfigMap references to be added to the Pod environment.\n     */\n    private List<String> configMapRefs = new ArrayList<>();\n\n    /**\n     * Secret references to be added to the Pod environment.\n     */\n    private List<String> secretRefs = new ArrayList<>();\n\n    /**\n     * Resources to assign for VolumeClaimTemplates (identified by metadata name) inside\n     * StatefulSet.\n     */\n    private StatefulSet statefulSet = new StatefulSet();\n\n    /**\n     * Environment variables to set for any deployed app container. To be used for service\n     * binding.\n     */\n    private String[] environmentVariables = new String[]{};\n\n    /**\n     * Entry point style used for the Docker image. To be used to determine how to pass in\n     * properties.\n     */\n    private EntryPointStyle entryPointStyle = EntryPointStyle.exec;\n\n    /**\n     * Create a \"LoadBalancer\" for the service created for each app. This facilitates\n     * assignment of external IP to app.\n     */\n    private boolean createLoadBalancer = false;\n\n    /**\n     * Service annotations to set for the service created for each app.\n     */\n    private String serviceAnnotations = null;\n\n    /**\n     * Pod annotations to set for the pod created for each deployment.\n     */\n    private String podAnnotations;\n\n    /**\n     * Job annotations to set for the pod or job created for a job.\n     */\n    private String jobAnnotations;\n\n    /**\n     * Time to wait for load balancer to be available before attempting delete of service (in\n     * minutes).\n     */\n    private int minutesToWaitForLoadBalancer = 5;\n\n    /**\n     * Maximum allowed restarts for app that fails due to an error or excessive resource use.\n     */\n    private int maxTerminatedErrorRestarts = 2;\n\n    /**\n     * Maximum allowed restarts for app that is in a CrashLoopBackOff.\n     */\n    private int maxCrashLoopBackOffRestarts = 4;\n\n    /**\n     * The image pull policy to use for Pod deployments in Kubernetes.\n     */\n    private ImagePullPolicy imagePullPolicy = ImagePullPolicy.IfNotPresent;\n\n    /**\n     * Volume mounts that a container is requesting. This can be specified as a deployer\n     * property or as an app deployment property. Deployment properties will override deployer\n     * properties.\n     */\n    private List<VolumeMount> volumeMounts = new ArrayList<>();\n\n    /**\n     * The volumes that a Kubernetes instance supports. See\n     * https://kubernetes.io/docs/user-guide/volumes/#types-of-volumes This can be specified\n     * as a deployer property or as an app deployment property. Deployment properties will\n     * override deployer properties.\n     */\n    private List<Volume> volumes = new ArrayList<>();\n\n    /**\n     * The hostNetwork setting for the deployments. See\n     * https://kubernetes.io/docs/api-reference/v1/definitions/#_v1_podspec This can be\n     * specified as a deployer property or as an app deployment property. Deployment\n     * properties will override deployer properties.\n     */\n    private boolean hostNetwork = false;\n\n    /**\n     * Create a \"Job\" instead of just a \"Pod\" when launching tasks. See\n     * https://kubernetes.io/docs/concepts/workloads/controllers/jobs-run-to-completion/\n     */\n    private boolean createJob = false;\n\n    /**\n     * The node selector to use in key:value format, comma separated\n     */\n    private String nodeSelector;\n\n    /**\n     * Service account name to use for app deployments\n     */\n    private String deploymentServiceAccountName;\n\n    /**\n     * The security context to apply to created pod's.\n     */\n    private PodSecurityContext podSecurityContext;\n\n    /**\n     * The security context to apply to created pod's main container.\n     */\n    private ContainerSecurityContext containerSecurityContext;\n\n    /**\n     * The node affinity rules to apply.\n     */\n    private NodeAffinity nodeAffinity;\n\n    /**\n     * The pod affinity rules to apply\n     */\n    private PodAffinity podAffinity;\n\n    /**\n     * The pod anti-affinity rules to apply\n     */\n    private PodAntiAffinity podAntiAffinity;\n\n    /**\n     * A custom init container image name to use when creating a StatefulSet\n     */\n    private String statefulSetInitContainerImageName;\n\n    /**\n     * A custom init container to apply.\n     */\n    private InitContainer initContainer;\n\n    /**\n     * Lifecycle spec to apply.\n     */\n    private Lifecycle lifecycle = new Lifecycle();\n\n    /**\n     * The additional containers one can add to the main application container.\n     */\n    private List<Container> additionalContainers;\n\n    /**\n     * Deployment label to be applied to Deployment, StatefulSet, JobSpec etc.,\n     */\n    private String deploymentLabels;\n\n    /**\n     * Pod priorityClassName. The user should create a PriorityClass before setting this property.\n     */\n    private String priorityClassName;\n\n    private AppAdmin appAdmin = new AppAdmin();\n\n    public String getNamespace() {\n        return namespace;\n    }\n\n    public void setNamespace(String namespace) {\n        this.namespace = namespace;\n    }\n\n    public String getImagePullSecret() {\n        return imagePullSecret;\n    }\n\n    public void setImagePullSecret(String imagePullSecret) {\n        this.imagePullSecret = imagePullSecret;\n    }\n\n    public List<String> getImagePullSecrets() {\n        return imagePullSecrets;\n    }\n\n    public void setImagePullSecrets(List<String> imagePullSecrets) {\n        this.imagePullSecrets = imagePullSecrets;\n    }\n\n\tpublic static class CronConfig {\n\t\tprivate String concurrencyPolicy;\n\n\t\tprivate Integer ttlSecondsAfterFinished;\n\n\t\tpublic String getConcurrencyPolicy() {\n\t\t\treturn concurrencyPolicy;\n\t\t}\n\n\t\tpublic void setConcurrencyPolicy(String concurrencyPolicy) {\n\t\t\tthis.concurrencyPolicy = concurrencyPolicy;\n\t\t}\n\n\t\tpublic Integer getTtlSecondsAfterFinished() {\n\t\t\treturn ttlSecondsAfterFinished;\n\t\t}\n\n\t\tpublic void setTtlSecondsAfterFinished(Integer ttlSecondsAfterFinished) {\n\t\t\tthis.ttlSecondsAfterFinished = ttlSecondsAfterFinished;\n\t\t}\n\t}\n\n    public Boolean getShareProcessNamespace() {\n        return shareProcessNamespace;\n    }\n\n    public void setShareProcessNamespace(Boolean shareProcessNamespace) {\n        this.shareProcessNamespace = shareProcessNamespace;\n    }\n\n    public String getPriorityClassName() {\n        return priorityClassName;\n    }\n\n    public void setPriorityClassName(String priorityClassName) {\n        this.priorityClassName = priorityClassName;\n    }\n\n    /**\n     * @deprecated @{see {@link #getLivenessHttpProbeDelay()}}\n     */\n    @Deprecated\n    public int getLivenessProbeDelay() {\n        return livenessHttpProbeDelay;\n    }\n\n    /**\n     * @deprecated @{see {@link #setLivenessHttpProbeDelay(int)}}\n     */\n    @Deprecated\n    public void setLivenessProbeDelay(int livenessProbeDelay) {\n        this.livenessHttpProbeDelay = livenessProbeDelay;\n    }\n\n    /**\n     * @deprecated @{see {@link #getLivenessHttpProbePeriod()}}\n     */\n    @Deprecated\n    public int getLivenessProbePeriod() {\n        return livenessHttpProbePeriod;\n    }\n\n    /**\n     * @deprecated @{see {@link #setLivenessHttpProbePeriod(int)}}\n     */\n    @Deprecated\n    public void setLivenessProbePeriod(int livenessProbePeriod) {\n        this.livenessHttpProbePeriod = livenessProbePeriod;\n    }\n\n    /**\n     * @deprecated @{see {@link #getLivenessHttpProbeTimeout()}}\n     */\n    @Deprecated\n    public int getLivenessProbeTimeout() {\n        return livenessHttpProbeTimeout;\n    }\n\n    /**\n     * @deprecated @{see {@link #setLivenessHttpProbeTimeout(int)}}\n     */\n    @Deprecated\n    public void setLivenessProbeTimeout(int livenessProbeTimeout) {\n        this.livenessHttpProbeTimeout = livenessProbeTimeout;\n    }\n\n    /**\n     * @deprecated @{see {@link #getLivenessHttpProbePath()}}\n     */\n    @Deprecated\n    public String getLivenessProbePath() {\n        return livenessHttpProbePath;\n    }\n\n    /**\n     * @deprecated @{see {@link #setLivenessHttpProbePath(String)}}\n     */\n    @Deprecated\n    public void setLivenessProbePath(String livenessProbePath) {\n        this.livenessHttpProbePath = livenessProbePath;\n    }\n\n    /**\n     * @deprecated @{see {@link #getLivenessHttpProbePort()}}\n     */\n    @Deprecated\n    public Integer getLivenessProbePort() {\n        return livenessHttpProbePort;\n    }\n\n    /**\n     * @deprecated @{see {@link #setLivenessHttpProbePort(Integer)}}\n     */\n    @Deprecated\n    public void setLivenessProbePort(Integer livenessProbePort) {\n        this.livenessHttpProbePort = livenessProbePort;\n    }\n\n\n    public int getLivenessTcpProbeSuccess() {\n        return livenessTcpProbeSuccess;\n    }\n\n    public void setLivenessTcpProbeSuccess(int livenessTcpProbeSuccess) {\n        this.livenessTcpProbeSuccess = livenessTcpProbeSuccess;\n    }\n\n    public int getLivenessTcpProbeFailure() {\n        return livenessTcpProbeFailure;\n    }\n\n    public void setLivenessTcpProbeFailure(int livenessTcpProbeFailure) {\n        this.livenessTcpProbeFailure = livenessTcpProbeFailure;\n    }\n\n    public int getLivenessHttpProbeDelay() {\n        return livenessHttpProbeDelay;\n    }\n\n    public void setLivenessHttpProbeDelay(int livenessHttpProbeDelay) {\n        this.livenessHttpProbeDelay = livenessHttpProbeDelay;\n    }\n\n    public int getLivenessTcpProbeTimeout() {\n        return livenessTcpProbeTimeout;\n    }\n\n    public void setLivenessTcpProbeTimeout(int livenessTcpProbeTimeout) {\n        this.livenessTcpProbeTimeout = livenessTcpProbeTimeout;\n    }\n\n\n    public int getLivenessHttpProbePeriod() {\n        return livenessHttpProbePeriod;\n    }\n\n    public void setLivenessHttpProbePeriod(int livenessHttpProbePeriod) {\n        this.livenessHttpProbePeriod = livenessHttpProbePeriod;\n    }\n\n    public int getLivenessHttpProbeTimeout() {\n        return livenessHttpProbeTimeout;\n    }\n\n    public void setLivenessHttpProbeTimeout(int livenessHttpProbeTimeout) {\n        this.livenessHttpProbeTimeout = livenessHttpProbeTimeout;\n    }\n\n    public String getLivenessHttpProbePath() {\n        return livenessHttpProbePath;\n    }\n\n    public void setLivenessHttpProbePath(String livenessHttpProbePath) {\n        this.livenessHttpProbePath = livenessHttpProbePath;\n    }\n\n    public Integer getLivenessHttpProbePort() {\n        return livenessHttpProbePort;\n    }\n\n    public void setLivenessHttpProbePort(Integer livenessHttpProbePort) {\n        this.livenessHttpProbePort = livenessHttpProbePort;\n    }\n\n    public int getStartupHttpProbeTimeout() {\n        return startupHttpProbeTimeout;\n    }\n\n    public int getStartupHttpProbeFailure() {\n        return startupHttpProbeFailure;\n    }\n\n    public void setStartupHttpProbeFailure(int startupHttpProbeFailure) {\n        this.startupHttpProbeFailure = startupHttpProbeFailure;\n    }\n\n    public void setStartupProbeFailure(int startupHttpProbeFailure) {\n        this.startupHttpProbeFailure = startupHttpProbeFailure;\n    }\n\n    public int getLivenessHttpProbeFailure() {\n        return livenessHttpProbeFailure;\n    }\n\n    public void setLivenessHttpProbeFailure(int livenessHttpProbeFailure) {\n        this.livenessHttpProbeFailure = livenessHttpProbeFailure;\n    }\n\n    public void setLivenessProbeFailure(int livenessHttpProbeFailure) {\n        this.livenessHttpProbeFailure = livenessHttpProbeFailure;\n    }\n\n    public int getLivenessHttpProbeSuccess() {\n        return livenessHttpProbeSuccess;\n    }\n\n    public void setLivenessHttpProbeSuccess(int livenessHttpProbeSuccess) {\n        this.livenessHttpProbeSuccess = livenessHttpProbeSuccess;\n    }\n\n    public void setLivenessProbeSuccess(int livenessHttpProbeSuccess) {\n        this.livenessHttpProbeSuccess = livenessHttpProbeSuccess;\n    }\n\n    public int getStartupHttpProbeSuccess() {\n        return startupHttpProbeSuccess;\n    }\n\n    public void setStartupHttpProbeSuccess(int startupHttpProbeSuccess) {\n        this.startupHttpProbeSuccess = startupHttpProbeSuccess;\n    }\n\n    public void setStartupProbeSuccess(int startupHttpProbeSuccess) {\n        this.startupHttpProbeSuccess = startupHttpProbeSuccess;\n    }\n\n    public int getStartupTcpProbeFailure() {\n        return startupTcpProbeFailure;\n    }\n\n    public void setStartupTcpProbeFailure(int startupTcpProbeFailure) {\n        this.startupTcpProbeFailure = startupTcpProbeFailure;\n    }\n\n    public int getStartupTcpProbeSuccess() {\n        return startupTcpProbeSuccess;\n    }\n\n    public void setStartupTcpProbeSuccess(int startupTcpProbeSuccess) {\n        this.startupTcpProbeSuccess = startupTcpProbeSuccess;\n    }\n\n    public int getStartupCommandProbeFailure() {\n        return startupCommandProbeFailure;\n    }\n\n    public void setStartupCommandProbeFailure(int startupCommandProbeFailure) {\n        this.startupCommandProbeFailure = startupCommandProbeFailure;\n    }\n\n    public int getStartupCommandProbeSuccess() {\n        return startupCommandProbeSuccess;\n    }\n\n    public void setStartupCommandProbeSuccess(int startupCommandProbeSuccess) {\n        this.startupCommandProbeSuccess = startupCommandProbeSuccess;\n    }\n\n    public int getLivenessCommandProbeFailure() {\n        return livenessCommandProbeFailure;\n    }\n\n    public void setLivenessCommandProbeFailure(int livenessCommandProbeFailure) {\n        this.livenessCommandProbeFailure = livenessCommandProbeFailure;\n    }\n\n    public int getLivenessCommandProbeSuccess() {\n        return livenessCommandProbeSuccess;\n    }\n\n    public void setLivenessCommandProbeSuccess(int livenessCommandProbeSuccess) {\n        this.livenessCommandProbeSuccess = livenessCommandProbeSuccess;\n    }\n\n    public int getStartupHttpProbeDelay() {\n        return startupHttpProbeDelay;\n    }\n\n    public void setStartupHttpProbeTimeout(int startupHttpProbeTimeout) {\n        this.startupHttpProbeTimeout = startupHttpProbeTimeout;\n    }\n\n    public void setStartupProbeTimeout(int startupHttpProbeTimeout) {\n        this.startupHttpProbeTimeout = startupHttpProbeTimeout;\n    }\n\n    public void setStartupHttpProbeDelay(int startupHttpProbeDelay) {\n        this.startupHttpProbeDelay = startupHttpProbeDelay;\n    }\n\n    public void setStartupProbeDelay(int startupHttpProbeDelay) {\n        this.startupHttpProbeDelay = startupHttpProbeDelay;\n    }\n\n    public String getStartupHttpProbePath() {\n        return startupHttpProbePath;\n    }\n\n    public void setStartupHttpProbePath(String startupHttpProbePath) {\n        this.startupHttpProbePath = startupHttpProbePath;\n    }\n\n    public Integer getStartupHttpProbePort() {\n        return startupHttpProbePort;\n    }\n\n    public void setStartupHttpProbePort(Integer startupHttpProbePort) {\n        this.startupHttpProbePort = startupHttpProbePort;\n    }\n\n    public void setStartupProbePort(Integer startupHttpProbePort) {\n        this.startupHttpProbePort = startupHttpProbePort;\n    }\n\n    public String getStartupProbeScheme() {\n        return startupProbeScheme;\n    }\n\n    public void setStartupProbeScheme(String startupProbeScheme) {\n        this.startupProbeScheme = startupProbeScheme;\n    }\n\n    public int getStartupHttpProbePeriod() {\n        return startupHttpProbePeriod;\n    }\n\n    public void setStartupHttpProbePeriod(int startupHttpProbePeriod) {\n        this.startupHttpProbePeriod = startupHttpProbePeriod;\n    }\n\n    public int getStartupProbePeriod() {\n        return startupHttpProbePeriod;\n    }\n\n    public void setStartupProbePeriod(int startupHttpProbePeriod) {\n        this.startupHttpProbePeriod = startupHttpProbePeriod;\n    }\n\n    public int getStartupTcpProbeDelay() {\n        return startupTcpProbeDelay;\n    }\n\n    public void setStartupTcpProbeDelay(int startupTcpProbeDelay) {\n        this.startupTcpProbeDelay = startupTcpProbeDelay;\n    }\n\n    public int getStartupTcpProbeTimeout() {\n        return startupTcpProbeTimeout;\n    }\n\n    public void setStartupTcpProbeTimeout(int startupTcpProbeTimeout) {\n        this.startupTcpProbeTimeout = startupTcpProbeTimeout;\n    }\n\t\n\t/**\n\t * Cron configuration for job scheduling\n\t */\n\tprivate CronConfig cron = new CronConfig();\n\n    public int getStartupTcpProbePeriod() {\n        return startupTcpProbePeriod;\n    }\n\n    public void setStartupTcpProbePeriod(int startupTcpProbePeriod) {\n        this.startupTcpProbePeriod = startupTcpProbePeriod;\n    }\n\n    public Integer getStartupTcpProbePort() {\n        return startupTcpProbePort;\n    }\n\n    public void setStartupTcpProbePort(Integer startupTcpProbePort) {\n        this.startupTcpProbePort = startupTcpProbePort;\n    }\n\n    public int getStartupCommandProbeDelay() {\n        return startupCommandProbeDelay;\n    }\n\n    public void setStartupCommandProbeDelay(int startupCommandProbeDelay) {\n        this.startupCommandProbeDelay = startupCommandProbeDelay;\n    }\n\n    public int getStartupCommandProbePeriod() {\n        return startupCommandProbePeriod;\n    }\n\n    public void setStartupCommandProbePeriod(int startupCommandProbePeriod) {\n        this.startupCommandProbePeriod = startupCommandProbePeriod;\n    }\n\n    public String getStartupCommandProbeCommand() {\n        return startupCommandProbeCommand;\n    }\n\n    public void setStartupCommandProbeCommand(String startupCommandProbeCommand) {\n        this.startupCommandProbeCommand = startupCommandProbeCommand;\n    }\n\n    /**\n     * @deprecated @{see {@link #getReadinessHttpProbeDelay()}}\n     */\n    @Deprecated\n    public int getReadinessProbeDelay() {\n        return readinessHttpProbeDelay;\n    }\n\n    /**\n     * @deprecated @{see {@link #setReadinessHttpProbeDelay(int)}}\n     */\n    @Deprecated\n    public void setReadinessProbeDelay(int readinessProbeDelay) {\n        this.readinessHttpProbeDelay = readinessProbeDelay;\n    }\n\n    /**\n     * @deprecated @{see {@link #getReadinessHttpProbePeriod()}}\n     */\n    @Deprecated\n    public int getReadinessProbePeriod() {\n        return readinessHttpProbePeriod;\n    }\n\n    public int getReadinessTcpProbeFailure() {\n        return readinessTcpProbeFailure;\n    }\n\n    public void setReadinessTcpProbeFailure(int readinessTcpProbeFailure) {\n        this.readinessTcpProbeFailure = readinessTcpProbeFailure;\n    }\n\n    public int getReadinessTcpProbeSuccess() {\n        return readinessTcpProbeSuccess;\n    }\n\n    public void setReadinessTcpProbeSuccess(int readinessTcpProbeSuccess) {\n        this.readinessTcpProbeSuccess = readinessTcpProbeSuccess;\n    }\n\n    public int getReadinessHttpProbeSuccess() {\n        return readinessHttpProbeSuccess;\n    }\n\n    public void setReadinessHttpProbeSuccess(int readinessHttpProbeSuccess) {\n        this.readinessHttpProbeSuccess = readinessHttpProbeSuccess;\n    }\n\n    public int getReadinessHttpProbeFailure() {\n        return readinessHttpProbeFailure;\n    }\n\n    public void setReadinessHttpProbeFailure(int readinessHttpProbeFailure) {\n        this.readinessHttpProbeFailure = readinessHttpProbeFailure;\n    }\n\n    public void setReadinessProbeFailure(int readinessHttpProbeFailure) {\n        this.readinessHttpProbeFailure = readinessHttpProbeFailure;\n    }\n\n\n    public int getReadinessCommandProbeFailure() {\n        return readinessCommandProbeFailure;\n    }\n\n    public void setReadinessCommandProbeFailure(int readinessCommandProbeFailure) {\n        this.readinessCommandProbeFailure = readinessCommandProbeFailure;\n    }\n\n    public int getReadinessCommandProbeSuccess() {\n        return readinessCommandProbeSuccess;\n    }\n\n    public void setReadinessCommandProbeSuccess(int readinessCommandProbeSuccess) {\n        this.readinessCommandProbeSuccess = readinessCommandProbeSuccess;\n    }\n\n    /**\n     * @deprecated @{see {@link #setReadinessHttpProbePeriod(int)}}\n     */\n    @Deprecated\n    public void setReadinessProbePeriod(int readinessProbePeriod) {\n        this.readinessHttpProbePeriod = readinessProbePeriod;\n    }\n\n    /**\n     * @deprecated @{see {@link #getReadinessHttpProbeTimeout()}}\n     */\n    @Deprecated\n    public int getReadinessProbeTimeout() {\n        return readinessHttpProbeTimeout;\n    }\n\n    /**\n     * @deprecated @{see {@link #setReadinessHttpProbeTimeout(int)}}\n     */\n    @Deprecated\n    public void setReadinessProbeTimeout(int readinessProbeTimeout) {\n        this.readinessHttpProbeTimeout = readinessProbeTimeout;\n    }\n\n    /**\n     * @deprecated @{see {@link #getReadinessHttpProbePath()}}\n     */\n    @Deprecated\n    public String getReadinessProbePath() {\n        return readinessHttpProbePath;\n    }\n\n    /**\n     * @deprecated @{see {@link #setReadinessHttpProbePath(String)}}\n     */\n    @Deprecated\n    public void setReadinessProbePath(String readinessProbePath) {\n        this.readinessHttpProbePath = readinessProbePath;\n    }\n\n    /**\n     * @deprecated @{see {@link #getReadinessHttpProbePort()}}\n     */\n    @Deprecated\n    public Integer getReadinessProbePort() {\n        return readinessHttpProbePort;\n    }\n\n    /**\n     * @deprecated @{see {@link #setReadinessHttpProbePort(Integer)}}\n     */\n    @Deprecated\n    public void setReadinessProbePort(Integer readinessProbePort) {\n        this.readinessHttpProbePort = readinessProbePort;\n    }\n\n    public int getReadinessHttpProbeDelay() {\n        return readinessHttpProbeDelay;\n    }\n\n    public void setReadinessHttpProbeDelay(int readinessHttpProbeDelay) {\n        this.readinessHttpProbeDelay = readinessHttpProbeDelay;\n    }\n\n    public int getReadinessHttpProbePeriod() {\n        return readinessHttpProbePeriod;\n    }\n\n    public void setReadinessHttpProbePeriod(int readinessHttpProbePeriod) {\n        this.readinessHttpProbePeriod = readinessHttpProbePeriod;\n    }\n\n    public int getReadinessTcpProbeTimeout() {\n        return readinessTcpProbeTimeout;\n    }\n\n    public void setReadinessTcpProbeTimeout(int readinessTcpProbeTimeout) {\n        this.readinessTcpProbeTimeout = readinessTcpProbeTimeout;\n    }\n\n    public int getReadinessHttpProbeTimeout() {\n        return readinessHttpProbeTimeout;\n    }\n\n    public void setReadinessHttpProbeTimeout(int readinessHttpProbeTimeout) {\n        this.readinessHttpProbeTimeout = readinessHttpProbeTimeout;\n    }\n\n    public String getReadinessHttpProbePath() {\n        return readinessHttpProbePath;\n    }\n\n    public void setReadinessHttpProbePath(String readinessHttpProbePath) {\n        this.readinessHttpProbePath = readinessHttpProbePath;\n    }\n\n    public Integer getReadinessHttpProbePort() {\n        return readinessHttpProbePort;\n    }\n\n    public void setReadinessHttpProbePort(Integer readinessHttpProbePort) {\n        this.readinessHttpProbePort = readinessHttpProbePort;\n    }\n\n    public int getLivenessTcpProbeDelay() {\n        return livenessTcpProbeDelay;\n    }\n\n    public void setLivenessTcpProbeDelay(int livenessTcpProbeDelay) {\n        this.livenessTcpProbeDelay = livenessTcpProbeDelay;\n    }\n\n    public int getLivenessTcpProbePeriod() {\n        return livenessTcpProbePeriod;\n    }\n\n    public void setLivenessTcpProbePeriod(int livenessTcpProbePeriod) {\n        this.livenessTcpProbePeriod = livenessTcpProbePeriod;\n    }\n\n    public Integer getLivenessTcpProbePort() {\n        return livenessTcpProbePort;\n    }\n\n    public void setLivenessTcpProbePort(Integer livenessTcpProbePort) {\n        this.livenessTcpProbePort = livenessTcpProbePort;\n    }\n\n    public int getReadinessTcpProbeDelay() {\n        return readinessTcpProbeDelay;\n    }\n\n    public void setReadinessTcpProbeDelay(int readinessTcpProbeDelay) {\n        this.readinessTcpProbeDelay = readinessTcpProbeDelay;\n    }\n\n    public int getReadinessTcpProbePeriod() {\n        return readinessTcpProbePeriod;\n    }\n\n    public void setReadinessTcpProbePeriod(int readinessTcpProbePeriod) {\n        this.readinessTcpProbePeriod = readinessTcpProbePeriod;\n    }\n\n    public Integer getReadinessTcpProbePort() {\n        return readinessTcpProbePort;\n    }\n\n    public void setReadinessTcpProbePort(Integer readinessTcpProbePort) {\n        this.readinessTcpProbePort = readinessTcpProbePort;\n    }\n\n    public int getReadinessCommandProbeDelay() {\n        return readinessCommandProbeDelay;\n    }\n\n    public void setReadinessCommandProbeDelay(int readinessCommandProbeDelay) {\n        this.readinessCommandProbeDelay = readinessCommandProbeDelay;\n    }\n\n    public int getReadinessCommandProbePeriod() {\n        return readinessCommandProbePeriod;\n    }\n\n    public void setReadinessCommandProbePeriod(int readinessCommandProbePeriod) {\n        this.readinessCommandProbePeriod = readinessCommandProbePeriod;\n    }\n\n    public String getReadinessCommandProbeCommand() {\n        return readinessCommandProbeCommand;\n    }\n\n    public void setReadinessCommandProbeCommand(String readinessCommandProbeCommand) {\n        this.readinessCommandProbeCommand = readinessCommandProbeCommand;\n    }\n\n    public int getLivenessCommandProbeDelay() {\n        return livenessCommandProbeDelay;\n    }\n\n    public void setLivenessCommandProbeDelay(int livenessCommandProbeDelay) {\n        this.livenessCommandProbeDelay = livenessCommandProbeDelay;\n    }\n\n    public int getLivenessCommandProbePeriod() {\n        return livenessCommandProbePeriod;\n    }\n\n    public void setLivenessCommandProbePeriod(int livenessCommandProbePeriod) {\n        this.livenessCommandProbePeriod = livenessCommandProbePeriod;\n    }\n\n    public String getLivenessCommandProbeCommand() {\n        return livenessCommandProbeCommand;\n    }\n\n    public void setLivenessCommandProbeCommand(String livenessCommandProbeCommand) {\n        this.livenessCommandProbeCommand = livenessCommandProbeCommand;\n    }\n\n    public String getProbeCredentialsSecret() {\n        return probeCredentialsSecret;\n    }\n\n    public void setProbeCredentialsSecret(String probeCredentialsSecret) {\n        this.probeCredentialsSecret = probeCredentialsSecret;\n    }\n\n    public ProbeType getProbeType() {\n        return probeType;\n    }\n\n    public void setProbeType(ProbeType probeType) {\n        this.probeType = probeType;\n    }\n\n    public StatefulSet getStatefulSet() {\n        return statefulSet;\n    }\n\n    public void setStatefulSet(\n            StatefulSet statefulSet) {\n        this.statefulSet = statefulSet;\n    }\n\n    public List<Toleration> getTolerations() {\n        return tolerations;\n    }\n\n    public void setTolerations(List<Toleration> tolerations) {\n        this.tolerations = tolerations;\n    }\n\n    public List<SecretKeyRef> getSecretKeyRefs() {\n        return secretKeyRefs;\n    }\n\n    public void setSecretKeyRefs(List<SecretKeyRef> secretKeyRefs) {\n        this.secretKeyRefs = secretKeyRefs;\n    }\n\n    public List<ConfigMapKeyRef> getConfigMapKeyRefs() {\n        return configMapKeyRefs;\n    }\n\n    public void setConfigMapKeyRefs(List<ConfigMapKeyRef> configMapKeyRefs) {\n        this.configMapKeyRefs = configMapKeyRefs;\n    }\n\n    public List<String> getConfigMapRefs() {\n        return configMapRefs;\n    }\n\n    public void setConfigMapRefs(List<String> configMapRefs) {\n        this.configMapRefs = configMapRefs;\n    }\n\n    public List<String> getSecretRefs() {\n        return secretRefs;\n    }\n\n    public void setSecretRefs(List<String> secretRefs) {\n        this.secretRefs = secretRefs;\n    }\n\n    public String[] getEnvironmentVariables() {\n        return environmentVariables;\n    }\n\n    public void setEnvironmentVariables(String[] environmentVariables) {\n        this.environmentVariables = environmentVariables;\n    }\n\n    public EntryPointStyle getEntryPointStyle() {\n        return entryPointStyle;\n    }\n\n    public void setEntryPointStyle(EntryPointStyle entryPointStyle) {\n        this.entryPointStyle = entryPointStyle;\n    }\n\n    public boolean isCreateLoadBalancer() {\n        return createLoadBalancer;\n    }\n\n    public void setCreateLoadBalancer(boolean createLoadBalancer) {\n        this.createLoadBalancer = createLoadBalancer;\n    }\n\n    public String getServiceAnnotations() {\n        return serviceAnnotations;\n    }\n\n    public void setServiceAnnotations(String serviceAnnotations) {\n        this.serviceAnnotations = serviceAnnotations;\n    }\n\n    public String getPodAnnotations() {\n        return podAnnotations;\n    }\n\n    public void setPodAnnotations(String podAnnotations) {\n        this.podAnnotations = podAnnotations;\n    }\n\n    public String getJobAnnotations() {\n        return jobAnnotations;\n    }\n\n    public void setJobAnnotations(String jobAnnotations) {\n        this.jobAnnotations = jobAnnotations;\n    }\n\n    public int getMinutesToWaitForLoadBalancer() {\n        return minutesToWaitForLoadBalancer;\n    }\n\n    public void setMinutesToWaitForLoadBalancer(int minutesToWaitForLoadBalancer) {\n        this.minutesToWaitForLoadBalancer = minutesToWaitForLoadBalancer;\n    }\n\n    public int getMaxTerminatedErrorRestarts() {\n        return maxTerminatedErrorRestarts;\n    }\n\n    public void setMaxTerminatedErrorRestarts(int maxTerminatedErrorRestarts) {\n        this.maxTerminatedErrorRestarts = maxTerminatedErrorRestarts;\n    }\n\n    public int getMaxCrashLoopBackOffRestarts() {\n        return maxCrashLoopBackOffRestarts;\n    }\n\n    public void setMaxCrashLoopBackOffRestarts(int maxCrashLoopBackOffRestarts) {\n        this.maxCrashLoopBackOffRestarts = maxCrashLoopBackOffRestarts;\n    }\n\n    public ImagePullPolicy getImagePullPolicy() {\n        return imagePullPolicy;\n    }\n\n    public void setImagePullPolicy(ImagePullPolicy imagePullPolicy) {\n        this.imagePullPolicy = imagePullPolicy;\n    }\n\n    public LimitsResources getLimits() {\n        return limits;\n    }\n\n    public void setLimits(LimitsResources limits) {\n        this.limits = limits;\n    }\n\n    public RequestsResources getRequests() {\n        return requests;\n    }\n\n    public void setRequests(RequestsResources requests) {\n        this.requests = requests;\n    }\n\n    public List<VolumeMount> getVolumeMounts() {\n        return volumeMounts;\n    }\n\n    public void setVolumeMounts(List<VolumeMount> volumeMounts) {\n        this.volumeMounts = volumeMounts;\n    }\n\n    public List<Volume> getVolumes() {\n        return volumes;\n    }\n\n    public void setVolumes(List<Volume> volumes) {\n        this.volumes = volumes;\n    }\n\n    public boolean isHostNetwork() {\n        return hostNetwork;\n    }\n\n    public void setHostNetwork(boolean hostNetwork) {\n        this.hostNetwork = hostNetwork;\n    }\n\n    public boolean isCreateJob() {\n        return createJob;\n    }\n\n    public void setCreateJob(boolean createJob) {\n        this.createJob = createJob;\n    }\n\n    public String getDeploymentServiceAccountName() {\n        return deploymentServiceAccountName;\n    }\n\n    public void setDeploymentServiceAccountName(String deploymentServiceAccountName) {\n        this.deploymentServiceAccountName = deploymentServiceAccountName;\n    }\n\n    public int getMaximumConcurrentTasks() {\n        return maximumConcurrentTasks;\n    }\n\n    public void setMaximumConcurrentTasks(int maximumConcurrentTasks) {\n        this.maximumConcurrentTasks = maximumConcurrentTasks;\n    }\n\n    public void setNodeSelector(String nodeSelector) {\n        this.nodeSelector = nodeSelector;\n    }\n\n    public String getNodeSelector() {\n        return nodeSelector;\n    }\n\n    public void setPodSecurityContext(PodSecurityContext podSecurityContext) {\n        this.podSecurityContext = podSecurityContext;\n    }\n\n    public PodSecurityContext getPodSecurityContext() {\n        return podSecurityContext;\n    }\n\n    public void setContainerSecurityContext(ContainerSecurityContext containerSecurityContext) {\n        this.containerSecurityContext = containerSecurityContext;\n    }\n\n    public ContainerSecurityContext getContainerSecurityContext() {\n        return containerSecurityContext;\n    }\n\n    public NodeAffinity getNodeAffinity() {\n        return nodeAffinity;\n    }\n\n    public void setNodeAffinity(NodeAffinity nodeAffinity) {\n        this.nodeAffinity = nodeAffinity;\n    }\n\n    public PodAffinity getPodAffinity() {\n        return podAffinity;\n    }\n\n    public void setPodAffinity(PodAffinity podAffinity) {\n        this.podAffinity = podAffinity;\n    }\n\n    public PodAntiAffinity getPodAntiAffinity() {\n        return podAntiAffinity;\n    }\n\n    public void setPodAntiAffinity(PodAntiAffinity podAntiAffinity) {\n        this.podAntiAffinity = podAntiAffinity;\n    }\n\n    public String getStatefulSetInitContainerImageName() {\n        return statefulSetInitContainerImageName;\n    }\n\n    public void setStatefulSetInitContainerImageName(String statefulSetInitContainerImageName) {\n        this.statefulSetInitContainerImageName = statefulSetInitContainerImageName;\n    }\n\n    public InitContainer getInitContainer() {\n        return initContainer;\n    }\n\n    public void setInitContainer(InitContainer initContainer) {\n        this.initContainer = initContainer;\n    }\n\n    public List<Container> getAdditionalContainers() {\n        return this.additionalContainers;\n    }\n\n    public void setAdditionalContainers(List<Container> additionalContainers) {\n        this.additionalContainers = additionalContainers;\n    }\n\n    public String getLivenessHttpProbeScheme() {\n        return livenessHttpProbeScheme;\n    }\n\n    public void setLivenessHttpProbeScheme(String livenessHttpProbeScheme) {\n        this.livenessHttpProbeScheme = livenessHttpProbeScheme;\n    }\n\n    public String getReadinessHttpProbeScheme() {\n        return readinessHttpProbeScheme;\n    }\n\n    public void setReadinessHttpProbeScheme(String readinessHttpProbeScheme) {\n        this.readinessHttpProbeScheme = readinessHttpProbeScheme;\n    }\n\n    Lifecycle getLifecycle() {\n        return lifecycle;\n    }\n\n    void setLifecycle(Lifecycle lifecycle) {\n        this.lifecycle = lifecycle;\n    }\n\n    public String getDeploymentLabels() {\n        return deploymentLabels;\n    }\n\n    public void setDeploymentLabels(String deploymentLabels) {\n        this.deploymentLabels = deploymentLabels;\n    }\n\n    public AppAdmin getAppAdmin() {\n        return appAdmin;\n    }\n\n    public void setAppAdmin(AppAdmin appAdmin) {\n        this.appAdmin = appAdmin;\n    }\n\n\tpublic CronConfig getCron() {\n\t\treturn cron;\n\t}\n\n\tpublic void setCron(CronConfig cron) {\n\t\tthis.cron = cron;\n\t}\n}\n"
  },
  {
    "path": "src/main/java/org/springframework/cloud/deployer/spi/kubernetes/LivenessCommandProbeCreator.java",
    "content": "/*\n * Copyright 2020 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.kubernetes;\n\nimport org.springframework.cloud.deployer.spi.util.CommandLineTokenizer;\nimport org.springframework.util.StringUtils;\n\n/**\n * Creates a command based liveness probe\n *\n * @author Chris Schaefer\n * @author Corneil du Plessis\n * @since 2.5\n */\nclass LivenessCommandProbeCreator extends CommandProbeCreator {\n    LivenessCommandProbeCreator(KubernetesDeployerProperties kubernetesDeployerProperties, ContainerConfiguration containerConfiguration) {\n        super(kubernetesDeployerProperties, containerConfiguration);\n    }\n\n    @Override\n    int getInitialDelay() {\n        return getProbeIntProperty(LIVENESS_DEPLOYER_PROPERTY_PREFIX, \"Command\", \"ProbeDelay\",\n                getKubernetesDeployerProperties().getLivenessCommandProbeDelay());\n    }\n\n    @Override\n    int getPeriod() {\n        return getProbeIntProperty(LIVENESS_DEPLOYER_PROPERTY_PREFIX, \"Command\", \"ProbePeriod\",\n                getKubernetesDeployerProperties().getLivenessCommandProbePeriod());\n    }\n\n    @Override\n    int getFailure() {\n        return getProbeIntProperty(LIVENESS_DEPLOYER_PROPERTY_PREFIX, \"Command\", \"ProbeFailure\",\n                getKubernetesDeployerProperties().getLivenessCommandProbeFailure());\n    }\n\n    @Override\n    int getSuccess() {\n        return getProbeIntProperty(LIVENESS_DEPLOYER_PROPERTY_PREFIX, \"Command\", \"ProbeSuccess\",\n                getKubernetesDeployerProperties().getLivenessCommandProbeSuccess());\n    }\n\n    @Override\n    String[] getCommand() {\n        String probeCommandValue = getDeploymentPropertyValue(LIVENESS_DEPLOYER_PROPERTY_PREFIX + \"CommandProbeCommand\");\n\n        if (StringUtils.hasText(probeCommandValue)) {\n            return new CommandLineTokenizer(probeCommandValue).getArgs().toArray(new String[0]);\n        }\n\n        if (getKubernetesDeployerProperties().getLivenessCommandProbeCommand() != null) {\n            return new CommandLineTokenizer(getKubernetesDeployerProperties().getLivenessCommandProbeCommand())\n                    .getArgs().toArray(new String[0]);\n        }\n\n        throw new IllegalArgumentException(\"The livenessCommandProbeCommand property must be set.\");\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/springframework/cloud/deployer/spi/kubernetes/LivenessHttpProbeCreator.java",
    "content": "/*\n * Copyright 2018-2020 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.kubernetes;\n\nimport org.springframework.util.StringUtils;\n\n/**\n * Creates an HTTP Liveness probe\n *\n * @author Chris Schaefer\n * @author Ilayaperumal Gopinathan\n * @author Corneil du Plessis\n */\nclass LivenessHttpProbeCreator extends HttpProbeCreator {\n    LivenessHttpProbeCreator(KubernetesDeployerProperties kubernetesDeployerProperties,\n                             ContainerConfiguration containerConfiguration) {\n        super(kubernetesDeployerProperties, containerConfiguration);\n    }\n\n    @Override\n    public Integer getPort() {\n        String probePortValue = getProbeProperty(LIVENESS_DEPLOYER_PROPERTY_PREFIX, \"Http\", \"ProbePort\");\n\n        if (StringUtils.hasText(probePortValue)) {\n            return Integer.parseInt(probePortValue);\n        }\n\n        if (getKubernetesDeployerProperties().getLivenessHttpProbePort() != null) {\n            return getKubernetesDeployerProperties().getLivenessHttpProbePort();\n        }\n\n        if (getDefaultPort() != null) {\n            return getDefaultPort();\n        }\n\n        return null;\n    }\n\n    @Override\n    protected String getProbePath() {\n        String probePathValue = getProbeProperty(LIVENESS_DEPLOYER_PROPERTY_PREFIX, \"Http\", \"ProbePath\",\n                getKubernetesDeployerProperties().getLivenessHttpProbePath());\n\n        if (StringUtils.hasText(probePathValue)) {\n            return probePathValue;\n        }\n\n        if (useBoot1ProbePath()) {\n            return BOOT_1_LIVENESS_PROBE_PATH;\n        }\n\n        return BOOT_2_LIVENESS_PROBE_PATH;\n    }\n\n    @Override\n    protected String getScheme() {\n        String probeSchemeValue = getProbeProperty(LIVENESS_DEPLOYER_PROPERTY_PREFIX, \"Http\", \"ProbeScheme\",\n                getKubernetesDeployerProperties().getLivenessHttpProbeScheme());\n\n        if (StringUtils.hasText(probeSchemeValue)) {\n            return probeSchemeValue;\n        }\n\n        return DEFAULT_PROBE_SCHEME;\n    }\n\n    @Override\n    protected int getTimeout() {\n        return getProbeIntProperty(LIVENESS_DEPLOYER_PROPERTY_PREFIX, \"Http\", \"ProbeTimeout\",\n                getKubernetesDeployerProperties().getLivenessHttpProbeTimeout());\n    }\n\n    @Override\n    protected int getInitialDelay() {\n        return getProbeIntProperty(LIVENESS_DEPLOYER_PROPERTY_PREFIX, \"Http\", \"ProbeDelay\",\n                getKubernetesDeployerProperties().getLivenessHttpProbeDelay());\n    }\n\n    @Override\n    protected int getPeriod() {\n        return getProbeIntProperty(LIVENESS_DEPLOYER_PROPERTY_PREFIX, \"Http\", \"ProbePeriod\",\n                getKubernetesDeployerProperties().getLivenessHttpProbePeriod());\n    }\n\n    @Override\n    int getFailure() {\n        return getProbeIntProperty(LIVENESS_DEPLOYER_PROPERTY_PREFIX, \"Http\", \"ProbeFailure\",\n                getKubernetesDeployerProperties().getLivenessHttpProbeFailure());\n    }\n\n    @Override\n    int getSuccess() {\n        return getProbeIntProperty(LIVENESS_DEPLOYER_PROPERTY_PREFIX, \"Http\", \"ProbeSuccess\",\n                getKubernetesDeployerProperties().getLivenessHttpProbeSuccess());\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/springframework/cloud/deployer/spi/kubernetes/LivenessTcpProbeCreator.java",
    "content": "/*\n * Copyright 2020 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.kubernetes;\n\nimport org.springframework.util.StringUtils;\n\n/**\n * Creates a TCP liveness probe\n *\n * @author Chris Schaefer\n * @author Corneil du Plessis\n * @since 2.5\n */\nclass LivenessTcpProbeCreator extends TcpProbeCreator {\n    LivenessTcpProbeCreator(KubernetesDeployerProperties kubernetesDeployerProperties, ContainerConfiguration containerConfiguration) {\n        super(kubernetesDeployerProperties, containerConfiguration);\n    }\n\n    @Override\n    int getInitialDelay() {\n        return getProbeIntProperty(LIVENESS_DEPLOYER_PROPERTY_PREFIX, \"Tcp\", \"ProbeDelay\",\n                getKubernetesDeployerProperties().getLivenessTcpProbeDelay());\n    }\n\n    @Override\n    int getPeriod() {\n        return getProbeIntProperty(LIVENESS_DEPLOYER_PROPERTY_PREFIX, \"Tcp\", \"ProbePeriod\",\n                getKubernetesDeployerProperties().getLivenessTcpProbePeriod());\n    }\n\n    @Override\n    protected int getTimeout() {\n        return getProbeIntProperty(LIVENESS_DEPLOYER_PROPERTY_PREFIX, \"Tcp\", \"ProbeTimeout\",\n                getKubernetesDeployerProperties().getLivenessTcpProbeTimeout());\n    }\n\n    @Override\n    Integer getPort() {\n        String probePortValue = getProbeProperty(LIVENESS_DEPLOYER_PROPERTY_PREFIX, \"Tcp\", \"ProbePort\");\n\n        if (StringUtils.hasText(probePortValue)) {\n            if (!probePortValue.chars().allMatch(Character::isDigit)) {\n                throw new IllegalArgumentException(\"LivenessTcpProbePort must contain all digits\");\n            }\n\n            return Integer.parseInt(probePortValue);\n        }\n\n        if (getKubernetesDeployerProperties().getLivenessTcpProbePort() != null) {\n            return getKubernetesDeployerProperties().getLivenessTcpProbePort();\n        }\n\n        throw new IllegalArgumentException(\"The livenessTcpProbePort property must be set.\");\n    }\n\n    @Override\n    int getFailure() {\n        return getProbeIntProperty(LIVENESS_DEPLOYER_PROPERTY_PREFIX, \"Tcp\", \"ProbeFailure\",\n                getKubernetesDeployerProperties().getLivenessTcpProbeFailure());\n    }\n\n    @Override\n    int getSuccess() {\n        return getProbeIntProperty(LIVENESS_DEPLOYER_PROPERTY_PREFIX, \"Tcp\", \"ProbeSuccess\",\n                getKubernetesDeployerProperties().getLivenessTcpProbeSuccess());\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/springframework/cloud/deployer/spi/kubernetes/ProbeCreator.java",
    "content": "/*\n * Copyright 2018-2020 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.kubernetes;\n\nimport java.util.Map;\n\nimport io.fabric8.kubernetes.api.model.Probe;\n\nimport org.springframework.cloud.deployer.spi.kubernetes.support.PropertyParserUtils;\nimport org.springframework.util.StringUtils;\n\n/**\n * Base class for creating Probe's\n *\n * @author Chris Schaefer\n * @author Ilayaperumal Gopinathan\n */\nabstract class ProbeCreator {\n    static final String KUBERNETES_DEPLOYER_PREFIX = \"spring.cloud.deployer.kubernetes\";\n    static final String LIVENESS_DEPLOYER_PROPERTY_PREFIX = KUBERNETES_DEPLOYER_PREFIX + \".liveness\";\n    static final String READINESS_DEPLOYER_PROPERTY_PREFIX = KUBERNETES_DEPLOYER_PREFIX + \".readiness\";\n    static final String STARTUP_DEPLOYER_PROPERTY_PREFIX = KUBERNETES_DEPLOYER_PREFIX + \".startup\";\n\n    private ContainerConfiguration containerConfiguration;\n    private KubernetesDeployerProperties kubernetesDeployerProperties;\n\n    ProbeCreator(KubernetesDeployerProperties kubernetesDeployerProperties,\n                 ContainerConfiguration containerConfiguration) {\n        this.containerConfiguration = containerConfiguration;\n        this.kubernetesDeployerProperties = kubernetesDeployerProperties;\n    }\n\n    abstract Probe create();\n\n    abstract int getInitialDelay();\n\n    abstract int getPeriod();\n\n    abstract int getFailure();\n\n    abstract int getSuccess();\n\n    KubernetesDeployerProperties getKubernetesDeployerProperties() {\n        return kubernetesDeployerProperties;\n    }\n\n    private Map<String, String> getDeploymentProperties() {\n        return this.containerConfiguration.getAppDeploymentRequest().getDeploymentProperties();\n    }\n\n    protected String getDeploymentPropertyValue(String propertyName) {\n        return PropertyParserUtils.getDeploymentPropertyValue(getDeploymentProperties(), propertyName);\n    }\n    protected String getDeploymentPropertyValue(String propertyName, String defaultValue) {\n        return PropertyParserUtils.getDeploymentPropertyValue(getDeploymentProperties(), propertyName, defaultValue);\n    }\n\n    ContainerConfiguration getContainerConfiguration() {\n        return containerConfiguration;\n    }\n\n    // used to resolve deprecated HTTP probe property names that do not include \"Http\" in them\n    // can be removed when deprecated HTTP probes without \"Http\" in them get removed\n    String getProbeProperty(String propertyPrefix, String probeName, String propertySuffix) {\n        String defaultValue = getDeploymentPropertyValue(propertyPrefix + probeName + propertySuffix);\n        return StringUtils.hasText(defaultValue) ? defaultValue :\n                getDeploymentPropertyValue(propertyPrefix + propertySuffix);\n    }\n\n    String getProbeProperty(String propertyPrefix, String probeName, String propertySuffix, String defaultValue) {\n        return getDeploymentPropertyValue(propertyPrefix + probeName + propertySuffix,\n                getDeploymentPropertyValue(propertyPrefix + propertySuffix, defaultValue)\n        );\n    }\n    int getProbeIntProperty(String propertyPrefix, String probeName, String propertySuffix, int defaultValue) {\n        String propertyValue = getDeploymentPropertyValue(propertyPrefix + probeName + propertySuffix,\n                getDeploymentPropertyValue(propertyPrefix + propertySuffix)\n        );\n        return StringUtils.hasText(propertyValue) ? Integer.parseInt(propertyValue) : defaultValue;\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/springframework/cloud/deployer/spi/kubernetes/ProbeCreatorFactory.java",
    "content": "/*\n * Copyright 2020 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.kubernetes;\n\nimport io.fabric8.kubernetes.api.model.Probe;\n\n/**\n * Creates health check probes\n *\n * @author Chris Schaefer\n * @since 2.5\n */\nclass ProbeCreatorFactory {\n    static Probe createStartupProbe(ContainerConfiguration containerConfiguration,\n                                    KubernetesDeployerProperties kubernetesDeployerProperties, ProbeType probeType) {\n        switch (probeType) {\n            case HTTP:\n                return new StartupHttpProbeCreator(kubernetesDeployerProperties, containerConfiguration).create();\n            case TCP:\n                return new StartupTcpProbeCreator(kubernetesDeployerProperties, containerConfiguration).create();\n            case COMMAND:\n                return new StartupCommandProbeCreator(kubernetesDeployerProperties, containerConfiguration).create();\n            default:\n                throw new IllegalArgumentException(\"Unknown startup probe type: \" + probeType);\n        }\n    }\n\n    static Probe createReadinessProbe(ContainerConfiguration containerConfiguration,\n                                      KubernetesDeployerProperties kubernetesDeployerProperties, ProbeType probeType) {\n        switch (probeType) {\n            case HTTP:\n                return new ReadinessHttpProbeCreator(kubernetesDeployerProperties, containerConfiguration).create();\n            case TCP:\n                return new ReadinessTcpProbeCreator(kubernetesDeployerProperties, containerConfiguration).create();\n            case COMMAND:\n                return new ReadinessCommandProbeCreator(kubernetesDeployerProperties, containerConfiguration).create();\n            default:\n                throw new IllegalArgumentException(\"Unknown readiness probe type: \" + probeType);\n        }\n    }\n\n    static Probe createLivenessProbe(ContainerConfiguration containerConfiguration,\n                                     KubernetesDeployerProperties kubernetesDeployerProperties, ProbeType probeType) {\n        switch (probeType) {\n            case HTTP:\n                return new LivenessHttpProbeCreator(kubernetesDeployerProperties, containerConfiguration).create();\n            case TCP:\n                return new LivenessTcpProbeCreator(kubernetesDeployerProperties, containerConfiguration).create();\n            case COMMAND:\n                return new LivenessCommandProbeCreator(kubernetesDeployerProperties, containerConfiguration).create();\n            default:\n                throw new IllegalArgumentException(\"Unknown liveness probe type: \" + probeType);\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/springframework/cloud/deployer/spi/kubernetes/ReadinessCommandProbeCreator.java",
    "content": "/*\n * Copyright 2020 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.kubernetes;\n\nimport org.springframework.cloud.deployer.spi.util.CommandLineTokenizer;\nimport org.springframework.util.StringUtils;\n\n/**\n * Creates a command based readiness probe\n *\n * @author Chris Schaefer\n * @author Corneil du Plessis\n * @since 2.5\n */\nclass ReadinessCommandProbeCreator extends CommandProbeCreator {\n    ReadinessCommandProbeCreator(KubernetesDeployerProperties kubernetesDeployerProperties, ContainerConfiguration containerConfiguration) {\n        super(kubernetesDeployerProperties, containerConfiguration);\n    }\n\n    @Override\n    int getInitialDelay() {\n        return getProbeIntProperty(READINESS_DEPLOYER_PROPERTY_PREFIX, \"Command\", \"ProbeDelay\",\n                getKubernetesDeployerProperties().getReadinessCommandProbeDelay());\n    }\n\n    @Override\n    int getPeriod() {\n        return getProbeIntProperty(READINESS_DEPLOYER_PROPERTY_PREFIX, \"Command\", \"ProbePeriod\",\n                getKubernetesDeployerProperties().getReadinessCommandProbePeriod());\n    }\n\n    @Override\n    int getFailure() {\n        return getProbeIntProperty(READINESS_DEPLOYER_PROPERTY_PREFIX, \"Command\", \"ProbeFailure\",\n                getKubernetesDeployerProperties().getReadinessCommandProbeFailure());\n    }\n\n    @Override\n    int getSuccess() {\n        return getProbeIntProperty(READINESS_DEPLOYER_PROPERTY_PREFIX, \"Command\", \"ProbeSuccess\",\n                getKubernetesDeployerProperties().getReadinessCommandProbeSuccess());\n    }\n\n    @Override\n    String[] getCommand() {\n        String probeCommandValue = getDeploymentPropertyValue(READINESS_DEPLOYER_PROPERTY_PREFIX + \"CommandProbeCommand\");\n\n        if (StringUtils.hasText(probeCommandValue)) {\n            return new CommandLineTokenizer(probeCommandValue).getArgs().toArray(new String[0]);\n        }\n\n        if (getKubernetesDeployerProperties().getReadinessCommandProbeCommand() != null) {\n            return new CommandLineTokenizer(getKubernetesDeployerProperties().getReadinessCommandProbeCommand())\n                    .getArgs().toArray(new String[0]);\n        }\n\n        throw new IllegalArgumentException(\"The readinessCommandProbeCommand property must be set.\");\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/springframework/cloud/deployer/spi/kubernetes/ReadinessHttpProbeCreator.java",
    "content": "/*\n * Copyright 2018-2020 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.kubernetes;\n\nimport org.springframework.util.StringUtils;\n\n/**\n * Creates an HTTP Readiness Probe.\n *\n * @author Chris Schaefer\n * @author Ilayaperumal Gopinathan\n * @author Corneil du Plessis\n */\nclass ReadinessHttpProbeCreator extends HttpProbeCreator {\n    ReadinessHttpProbeCreator(KubernetesDeployerProperties kubernetesDeployerProperties,\n                              ContainerConfiguration containerConfiguration) {\n        super(kubernetesDeployerProperties, containerConfiguration);\n    }\n\n    @Override\n    public Integer getPort() {\n        String probePortValue = getProbeProperty(READINESS_DEPLOYER_PROPERTY_PREFIX, \"Http\", \"ProbePort\");\n\n        if (StringUtils.hasText(probePortValue)) {\n            if (!probePortValue.chars().allMatch(Character::isDigit)) {\n                throw new IllegalArgumentException(\"ReadinessHttpProbeCreator must contain all digits\");\n            }\n\n            return Integer.parseInt(probePortValue);\n        }\n\n        if (getKubernetesDeployerProperties().getReadinessHttpProbePort() != null) {\n            return getKubernetesDeployerProperties().getReadinessHttpProbePort();\n        }\n\n        if (getDefaultPort() != null) {\n            return getDefaultPort();\n        }\n\n        return null;\n    }\n\n    @Override\n    protected String getProbePath() {\n        String probePathValue = getProbeProperty(READINESS_DEPLOYER_PROPERTY_PREFIX, \"Http\", \"ProbePath\");\n\n        if (StringUtils.hasText(probePathValue)) {\n            return probePathValue;\n        }\n\n        if (getKubernetesDeployerProperties().getReadinessHttpProbePath() != null) {\n            return getKubernetesDeployerProperties().getReadinessHttpProbePath();\n        }\n\n        if (useBoot1ProbePath()) {\n            return BOOT_1_READINESS_PROBE_PATH;\n        }\n\n        return BOOT_2_READINESS_PROBE_PATH;\n    }\n\n    @Override\n    protected String getScheme() {\n        String probeSchemeValue = getProbeProperty(READINESS_DEPLOYER_PROPERTY_PREFIX, \"Http\", \"ProbeScheme\");\n\n        if (StringUtils.hasText(probeSchemeValue)) {\n            return probeSchemeValue;\n        }\n\n        if (getKubernetesDeployerProperties().getReadinessHttpProbeScheme() != null) {\n            return getKubernetesDeployerProperties().getReadinessHttpProbeScheme();\n        }\n\n        return DEFAULT_PROBE_SCHEME;\n    }\n\n    @Override\n    protected int getTimeout() {\n        return getProbeIntProperty(READINESS_DEPLOYER_PROPERTY_PREFIX, \"Http\", \"ProbeTimeout\",\n                getKubernetesDeployerProperties().getReadinessHttpProbeTimeout());\n    }\n\n    @Override\n    protected int getInitialDelay() {\n        return getProbeIntProperty(READINESS_DEPLOYER_PROPERTY_PREFIX, \"Http\", \"ProbeDelay\",\n                getKubernetesDeployerProperties().getReadinessHttpProbeDelay());\n    }\n\n    @Override\n    protected int getPeriod() {\n        return getProbeIntProperty(READINESS_DEPLOYER_PROPERTY_PREFIX, \"Http\", \"ProbePeriod\",\n                getKubernetesDeployerProperties().getReadinessHttpProbePeriod());\n    }\n\n    @Override\n    int getFailure() {\n        return getProbeIntProperty(READINESS_DEPLOYER_PROPERTY_PREFIX, \"Http\", \"ProbeFailure\",\n                getKubernetesDeployerProperties().getReadinessHttpProbeFailure());\n    }\n\n    @Override\n    int getSuccess() {\n        return getProbeIntProperty(READINESS_DEPLOYER_PROPERTY_PREFIX, \"Http\", \"ProbeSuccess\",\n                getKubernetesDeployerProperties().getReadinessHttpProbeSuccess());\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/springframework/cloud/deployer/spi/kubernetes/ReadinessTcpProbeCreator.java",
    "content": "/*\n * Copyright 2020 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.kubernetes;\n\nimport org.springframework.util.StringUtils;\n\n/**\n * Creates a TCP readiness probe\n *\n * @author Chris Schaefer\n * @author Corneil du Plessis\n * @since 2.5\n */\nclass ReadinessTcpProbeCreator extends TcpProbeCreator {\n    ReadinessTcpProbeCreator(KubernetesDeployerProperties kubernetesDeployerProperties, ContainerConfiguration containerConfiguration) {\n        super(kubernetesDeployerProperties, containerConfiguration);\n    }\n\n    @Override\n    int getInitialDelay() {\n        return getProbeIntProperty(READINESS_DEPLOYER_PROPERTY_PREFIX, \"Tcp\", \"ProbeDelay\",\n                getKubernetesDeployerProperties().getReadinessTcpProbeDelay());\n    }\n\n    @Override\n    int getPeriod() {\n        return getProbeIntProperty(READINESS_DEPLOYER_PROPERTY_PREFIX, \"Tcp\", \"ProbePeriod\",\n                getKubernetesDeployerProperties().getReadinessTcpProbePeriod());\n    }\n\n    @Override\n    protected int getTimeout() {\n        return getProbeIntProperty(READINESS_DEPLOYER_PROPERTY_PREFIX, \"Tcp\", \"ProbeTimeout\",\n                getKubernetesDeployerProperties().getReadinessTcpProbeTimeout());\n    }\n\n    @Override\n    Integer getPort() {\n        String probePortValue = getProbeProperty(READINESS_DEPLOYER_PROPERTY_PREFIX, \"Tcp\", \"ProbePort\");\n\n        if (StringUtils.hasText(probePortValue)) {\n            if (!probePortValue.chars().allMatch(Character::isDigit)) {\n                throw new IllegalArgumentException(\"ReadinessTcpProbePort must contain all digits\");\n            }\n\n            return Integer.parseInt(probePortValue);\n        }\n\n        if (getKubernetesDeployerProperties().getReadinessTcpProbePort() != null) {\n            return getKubernetesDeployerProperties().getReadinessTcpProbePort();\n        }\n\n        throw new IllegalArgumentException(\"A readinessTcpProbePort property must be set.\");\n    }\n\n    @Override\n    int getFailure() {\n        return getProbeIntProperty(READINESS_DEPLOYER_PROPERTY_PREFIX, \"Tcp\", \"ProbeFailure\",\n                getKubernetesDeployerProperties().getReadinessTcpProbeFailure());\n    }\n\n    @Override\n    int getSuccess() {\n        return getProbeIntProperty(READINESS_DEPLOYER_PROPERTY_PREFIX, \"Tcp\", \"ProbeSuccess\",\n                getKubernetesDeployerProperties().getReadinessTcpProbeSuccess());\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/springframework/cloud/deployer/spi/local/AbstractLocalDeployerSupport.java",
    "content": "/*\n * Copyright 2016-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.local;\n\nimport java.io.IOException;\nimport java.net.URL;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.LinkedHashMap;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.Set;\n\nimport com.fasterxml.jackson.core.JsonProcessingException;\nimport com.fasterxml.jackson.core.type.TypeReference;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport org.springframework.boot.context.properties.bind.Bindable;\nimport org.springframework.boot.context.properties.bind.Binder;\nimport org.springframework.boot.context.properties.source.MapConfigurationPropertySource;\nimport org.springframework.cloud.deployer.resource.docker.DockerResource;\nimport org.springframework.cloud.deployer.spi.core.AppDeploymentRequest;\nimport org.springframework.cloud.deployer.spi.core.RuntimeEnvironmentInfo;\nimport org.springframework.cloud.deployer.spi.util.RuntimeVersionUtils;\nimport org.springframework.http.ResponseEntity;\nimport org.springframework.http.client.SimpleClientHttpRequestFactory;\nimport org.springframework.util.Assert;\nimport org.springframework.web.client.RestTemplate;\n\n/**\n * Base class for local app deployer and task launcher providing support for common\n * functionality.\n *\n * @author Janne Valkealahti\n * @author Mark Fisher\n * @author Ilayaperumal Gopinathan\n * @author Thomas Risberg\n * @author Oleg Zhurakousky\n * @author Vinicius Carvalho\n * @author Michael Minella\n * @author David Turanski\n * @author Christian Tzolov\n */\npublic abstract class AbstractLocalDeployerSupport {\n\n\tprotected static Set<Integer> usedPorts = Collections.newSetFromMap(new LinkedHashMap<Integer, Boolean>() {\n\t\t@Override\n\t\tprotected boolean removeEldestEntry(Map.Entry<Integer, Boolean> eldest) {\n\t\t\treturn size() > 1000;\n\t\t}\n\t});\n\n\tprotected final Logger logger = LoggerFactory.getLogger(getClass());\n\n\tpublic static final String SPRING_APPLICATION_JSON = \"SPRING_APPLICATION_JSON\";\n\n\tpublic static final int DEFAULT_SERVER_PORT = 8080;\n\n\tprivate static final String USE_SPRING_APPLICATION_JSON_KEY = LocalDeployerProperties.PREFIX\n\t\t\t+ \".use-spring-application-json\";\n\n\tstatic final String SERVER_PORT_KEY = \"server.port\";\n\n\tstatic final String SERVER_PORT_KEY_COMMAND_LINE_ARG = \"--\" + SERVER_PORT_KEY + \"=\";\n\n\tprivate static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();\n\n\tprivate final LocalDeployerProperties localDeployerProperties;\n\n\tprivate final RestTemplate restTemplate;\n\n\tprivate final JavaCommandBuilder javaCommandBuilder;\n\n\tprivate final DockerCommandBuilder dockerCommandBuilder;\n\n\t/**\n\t * Instantiates a new abstract deployer support.\n\t *\n\t * @param localDeployerProperties the local deployer properties\n\t */\n\tpublic AbstractLocalDeployerSupport(LocalDeployerProperties localDeployerProperties) {\n\t\tAssert.notNull(localDeployerProperties, \"LocalDeployerProperties must not be null\");\n\t\tthis.localDeployerProperties = localDeployerProperties;\n\t\tthis.javaCommandBuilder = new JavaCommandBuilder(localDeployerProperties);\n\t\tthis.dockerCommandBuilder = new DockerCommandBuilder(localDeployerProperties.getDocker().getNetwork());\n\t\tthis.restTemplate = buildRestTemplate(localDeployerProperties);\n\t}\n\n\t/**\n\t * Builds a {@link RestTemplate} used for calling app's shutdown endpoint. If needed can\n\t * be overridden from an implementing class. This default implementation sets connection\n\t * and read timeouts for {@link SimpleClientHttpRequestFactory} and configures\n\t * {@link RestTemplate} to use that factory. If shutdown timeout in properties negative,\n\t * returns default {@link RestTemplate} which doesn't use timeouts.\n\t *\n\t * @param properties the local deployer properties\n\t * @return the rest template\n\t */\n\tprotected RestTemplate buildRestTemplate(LocalDeployerProperties properties) {\n\t\tif (properties != null && properties.getShutdownTimeout() > -1) {\n\t\t\tSimpleClientHttpRequestFactory clientHttpRequestFactory = new SimpleClientHttpRequestFactory();\n\t\t\tclientHttpRequestFactory.setConnectTimeout(properties.getShutdownTimeout() * 1000);\n\t\t\tclientHttpRequestFactory.setReadTimeout(properties.getShutdownTimeout() * 1000);\n\t\t\treturn new RestTemplate(clientHttpRequestFactory);\n\t\t}\n\t\t// fall back to plain default constructor\n\t\treturn new RestTemplate();\n\t}\n\n\t/**\n\t * Create the RuntimeEnvironmentInfo.\n\t *\n\t * @return the local runtime environment info\n\t */\n\tprotected RuntimeEnvironmentInfo createRuntimeEnvironmentInfo(Class<?> spiClass, Class<?> implementationClass) {\n\t\treturn new RuntimeEnvironmentInfo.Builder().spiClass(spiClass)\n\t\t\t\t.implementationName(implementationClass.getSimpleName())\n\t\t\t\t.implementationVersion(RuntimeVersionUtils.getVersion(implementationClass)).platformType(\"Local\")\n\t\t\t\t.platformApiVersion(System.getProperty(\"os.name\") + \" \" + System.getProperty(\"os.version\"))\n\t\t\t\t.platformClientVersion(System.getProperty(\"os.version\"))\n\t\t\t\t.platformHostVersion(System.getProperty(\"os.version\")).build();\n\t}\n\n\t/**\n\t * Gets the local deployer properties.\n\t *\n\t * @return the local deployer properties\n\t */\n\tfinal protected LocalDeployerProperties getLocalDeployerProperties() {\n\t\treturn localDeployerProperties;\n\t}\n\n\t/**\n\t * Builds the process builder. Application properties are expected to be calculated prior\n\t * to this method. No additional consolidation of application properties is done while\n\t * creating the {@code ProcessBuilder}.\n\t *\n\t * @param request the request\n\t * @param appInstanceEnv the instance environment variables\n\t * @return the process builder\n\t */\n\tprotected ProcessBuilder buildProcessBuilder(AppDeploymentRequest request, Map<String, String> appInstanceEnv,\n\t\t\tOptional<Integer> appInstanceNumber, String deploymentId) {\n\t\tAssert.notNull(request, \"AppDeploymentRequest must be set\");\n\n\t\tMap<String, String> appPropertiesToUse = formatApplicationProperties(request, appInstanceEnv);\n\t\tif (logger.isInfoEnabled()) {\n\t\t\tlogger.info(\"Preparing to run an application from {}. This may take some time if the artifact must be \" +\n\t\t\t\t\t\"downloaded from a remote host.\", request.getResource());\n\t\t}\n\n\t\tLocalDeployerProperties localDeployerProperties = bindDeploymentProperties(request.getDeploymentProperties());\n\n\t\tOptional<DebugAddress> debugAddressOption = DebugAddress.from(localDeployerProperties, appInstanceNumber.orElse(0));\n\n\t\tProcessBuilder builder = getCommandBuilder(request)\n\t\t\t\t.buildExecutionCommand(request, appPropertiesToUse, deploymentId, appInstanceNumber,\n\t\t\t\t\t\tlocalDeployerProperties, debugAddressOption);\n\n\t\tlogger.info(String.format(\"Command to be executed: %s\", String.join(\" \", builder.command())));\n\t\t//logger.debug(String.format(\"Environment Variables to be used : %s\", builder.environment().entrySet().stream()\n\t\t//\t\t.map(entry -> entry.getKey() + \" : \" + entry.getValue()).collect(Collectors.joining(\", \"))));\n\t\treturn builder;\n\t}\n\n\t/**\n\t * Detects the Command builder by the type of the resource in the requests.\n\t * @param request deployment request containing information about the resource to be deployed.\n\t * @return Returns a command builder compatible with the type of the resource set in the request.\n\t */\n\tprotected CommandBuilder getCommandBuilder(AppDeploymentRequest request) {\n\t\treturn (request.getResource() instanceof DockerResource) ? this.dockerCommandBuilder : this.javaCommandBuilder;\n\t}\n\n\t/**\n\t * tweak escaping double quotes needed for windows\n\t * @param commands\n\t * @return\n\t */\n\tpublic static String[] windowsSupport(String[] commands) {\n\t\tif (LocalDeployerUtils.isWindows()) {\n\t\t\tfor (int i = 0; i < commands.length; i++) {\n\t\t\t\tcommands[i] = commands[i].replace(\"\\\"\", \"\\\\\\\"\");\n\t\t\t}\n\t\t}\n\t\treturn commands;\n\t}\n\n\t/**\n\t * This will merge the deployment properties that were passed in at runtime with the\n\t * deployment properties of the Deployer instance.\n\t * @param runtimeDeploymentProperties deployment properties passed in at runtime\n\t * @return merged deployer properties\n\t */\n\tprotected LocalDeployerProperties bindDeploymentProperties(Map<String, String> runtimeDeploymentProperties) {\n\t\tLocalDeployerProperties copyOfDefaultProperties = new LocalDeployerProperties(this.localDeployerProperties);\n\t\treturn new Binder(new MapConfigurationPropertySource(runtimeDeploymentProperties))\n\t\t\t\t.bind(LocalDeployerProperties.PREFIX, Bindable.ofInstance(copyOfDefaultProperties))\n\t\t\t\t.orElse(copyOfDefaultProperties);\n\t}\n\n\tprotected Map<String, String> formatApplicationProperties(AppDeploymentRequest request,\n\t\t\tMap<String, String> appInstanceEnvToUse) {\n\t\tMap<String, String> applicationPropertiesToUse = new HashMap<>(appInstanceEnvToUse);\n\n\t\tif (useSpringApplicationJson(request)) {\n\t\t\ttry {\n\t\t\t\t// If SPRING_APPLICATION_JSON is found, explode it and merge back into appProperties\n\t\t\t\tif (applicationPropertiesToUse.containsKey(SPRING_APPLICATION_JSON)) {\n\t\t\t\t\tapplicationPropertiesToUse\n\t\t\t\t\t\t\t.putAll(OBJECT_MAPPER.readValue(applicationPropertiesToUse.get(SPRING_APPLICATION_JSON),\n\t\t\t\t\t\t\t\t\tnew TypeReference<HashMap<String, String>>() {\n\t\t\t\t\t\t\t\t\t}));\n\t\t\t\t\tapplicationPropertiesToUse.remove(SPRING_APPLICATION_JSON);\n\t\t\t\t}\n\t\t\t}\n\t\t\tcatch (IOException e) {\n\t\t\t\tthrow new IllegalArgumentException(\n\t\t\t\t\t\t\"Unable to read existing SPRING_APPLICATION_JSON to merge properties\", e);\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\tString saj = OBJECT_MAPPER.writeValueAsString(applicationPropertiesToUse);\n\n\t\t\t\tapplicationPropertiesToUse = new HashMap<>(1);\n\n\t\t\t\tapplicationPropertiesToUse.put(SPRING_APPLICATION_JSON, saj);\n\t\t\t}\n\t\t\tcatch (JsonProcessingException e) {\n\t\t\t\tthrow new IllegalArgumentException(\n\t\t\t\t\t\t\"Unable to create SPRING_APPLICATION_JSON from application properties\", e);\n\t\t\t}\n\t\t}\n\n\t\treturn applicationPropertiesToUse;\n\t}\n\n\t/**\n\t * Shut down the {@link Process} backing the application {@link Instance}. If the\n\t * application exposes a {@code /shutdown} endpoint, that will be invoked followed by a\n\t * wait that will not exceed the number of seconds indicated by\n\t * {@link LocalDeployerProperties#getShutdownTimeout()} . If the timeout period is exceeded (or\n\t * if the {@code /shutdown} endpoint is not exposed), the process will be shut down via\n\t * {@link Process#destroy()}.\n\t *\n\t * @param instance the application instance to shut down\n\t */\n\tprotected void shutdownAndWait(Instance instance) {\n\t\ttry {\n\t\t\tint timeout = getLocalDeployerProperties().getShutdownTimeout();\n\t\t\tif (timeout > 0) {\n\t\t\t\tlogger.debug(\"About to call shutdown endpoint for the instance {}\", instance);\n\t\t\t\tResponseEntity<String> response = restTemplate.postForEntity(\n\t\t\t\t\t\tinstance.getBaseUrl() + \"/shutdown\", null, String.class);\n\t\t\t\tlogger.debug(\"Response for shutdown endpoint completed for the instance {} with response {}\", instance,\n\t\t\t\t\t\tresponse);\n\t\t\t\tif (response.getStatusCode().is2xxSuccessful()) {\n\t\t\t\t\tlong timeoutTimestamp = System.currentTimeMillis() + (timeout * 1000);\n\t\t\t\t\twhile (isAlive(instance.getProcess()) && System.currentTimeMillis() < timeoutTimestamp) {\n\t\t\t\t\t\tThread.sleep(1000);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tcatch (InterruptedException e) {\n\t\t\tThread.currentThread().interrupt();\n\t\t}\n\t\tcatch (Exception e) {\n\t\t\t// ignore all other errors as we're going to\n\t\t\t// destroy process if it's alive\n\t\t}\n\t\tfinally {\n\t\t\tif (isAlive(instance.getProcess())) {\n\t\t\t\tlogger.debug(\"About to call destroy the process for the instance {}\", instance);\n\t\t\t\tinstance.getProcess().destroy();\n\t\t\t\tlogger.debug(\"Call completed to destroy the process for the instance {}\", instance);\n\t\t\t}\n\t\t}\n\t}\n\n\t// Copy-pasting of JDK8+ isAlive method to retain JDK7 compatibility\n\tprotected boolean isAlive(Process process) {\n\t\ttry {\n\t\t\tlogger.debug(\"About to call exitValue of the process {}\", process);\n\t\t\tprocess.exitValue();\n\t\t\tlogger.debug(\"Call to exitValue of the process {} complete, return false\", process);\n\t\t\treturn false;\n\t\t}\n\t\tcatch (IllegalThreadStateException e) {\n\t\t\tlogger.debug(\"Call to exitValue of the process {} threw exception, return true\", process);\n\t\t\treturn true;\n\t\t}\n\t}\n\n\tprotected boolean useSpringApplicationJson(AppDeploymentRequest request) {\n\t\treturn request.getDefinition().getProperties().containsKey(USE_SPRING_APPLICATION_JSON_KEY)\n\t\t\t\t|| this.localDeployerProperties.isUseSpringApplicationJson();\n\t}\n\n\t// TODO (tzolov): This method has a treacherous side affect! Apart from returning the computed Port it also modifies\n\t//  the appInstanceEnvVars map! Later is used for down stream app deployment configuration.\n\t//  As a consequence if you place the calcServerPort in wrong place (for example after the buildProcessBuilder(..)\n\t//  call then the Port configuration won't be known to the command builder).\n\t//  Proper solution is to (1) either make the method void and rename it to calcAndSetServerPort or (2) make the\n\t//  method return the mutated appInstanceEnvVars. Sync with the SCT team because the method is used by the\n\t//  LocalTaskLauncher (e.g. prod. grade)\n\tprotected int calcServerPort(AppDeploymentRequest request, boolean useDynamicPort,\n\t\t\tMap<String, String> appInstanceEnvVars) {\n\n\t\tint port = DEFAULT_SERVER_PORT;\n\t\tInteger commandLineArgPort = isServerPortKeyPresentOnArgs(request);\n\n\t\tif (useDynamicPort) {\n\t\t\tport = getRandomPort(request);\n\t\t\tappInstanceEnvVars.put(LocalAppDeployer.SERVER_PORT_KEY, String.valueOf(port));\n\t\t}\n\t\telse if (commandLineArgPort != null) {\n\t\t\tport = commandLineArgPort;\n\t\t}\n\t\telse if (request.getDefinition().getProperties().containsKey(LocalAppDeployer.SERVER_PORT_KEY)) {\n\t\t\tport = Integer.parseInt(request.getDefinition().getProperties().get(LocalAppDeployer.SERVER_PORT_KEY));\n\t\t}\n\n\t\treturn port;\n\t}\n\n\t/**\n\t * Will check if {@link LocalDeployerProperties#INHERIT_LOGGING} is set by checking\n\t * deployment properties.\n\t */\n\tprotected boolean shouldInheritLogging(AppDeploymentRequest request) {\n\t\tLocalDeployerProperties bindDeployerProperties = bindDeploymentProperties(request.getDeploymentProperties());\n\t\treturn bindDeployerProperties.isInheritLogging();\n\t}\n\n\tpublic synchronized int getRandomPort(AppDeploymentRequest request) {\n\t\tSet<Integer> availPorts = new HashSet<>();\n\t\t// SocketUtils.findAvailableTcpPorts retries 6 times, add additional retry on top.\n\t\tfor (int retryCount = 0; retryCount < 5; retryCount++) {\n\t\t\tint randomInt = getCommandBuilder(request).getPortSuggestion(localDeployerProperties);\n\t\t\ttry {\n\t\t\t\tavailPorts = DeployerSocketUtils.findAvailableTcpPorts(5, randomInt, randomInt + 5);\n\t\t\t\ttry {\n\t\t\t\t\t// Give some time for the system to release up ports that were scanned.\n\t\t\t\t\tThread.sleep(100);\n\t\t\t\t}\n\t\t\t\tcatch (InterruptedException e) {\n\t\t\t\t\tlogger.debug(e.getMessage() + \"Retrying to find available ports.\");\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcatch (IllegalStateException e) {\n\t\t\t\tlogger.debug(e.getMessage() + \"  Retrying to find available ports.\");\n\t\t\t}\n\t\t}\n\t\tif (availPorts.isEmpty()) {\n\t\t\tthrow new IllegalStateException(\n\t\t\t\t\t\"Could not find an available TCP port in the range\" + localDeployerProperties.getPortRange());\n\t\t}\n\n\t\tint finalPort = -1;\n\t\tlogger.debug(\"Available Ports: \" + availPorts);\n\t\tfor (Integer freePort : availPorts) {\n\t\t\tif (!usedPorts.contains(freePort)) {\n\t\t\t\tfinalPort = freePort;\n\t\t\t\tusedPorts.add(finalPort);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\tif (finalPort == -1) {\n\t\t\tthrow new IllegalStateException(\n\t\t\t\t\t\"Could not find a free random port range \" + localDeployerProperties.getPortRange());\n\t\t}\n\t\tlogger.debug(\"Using Port: \" + finalPort);\n\t\treturn finalPort;\n\t}\n\n\tprotected Integer isServerPortKeyPresentOnArgs(AppDeploymentRequest request) {\n\t\treturn request.getCommandlineArguments().stream()\n\t\t\t\t.filter(argument -> argument.startsWith(SERVER_PORT_KEY_COMMAND_LINE_ARG))\n\t\t\t\t.map(argument -> Integer.parseInt(argument.replace(SERVER_PORT_KEY_COMMAND_LINE_ARG, \"\").trim()))\n\t\t\t\t.findFirst()\n\t\t\t\t.orElse(null);\n\t}\n\n\tprotected interface Instance {\n\n\t\tURL getBaseUrl();\n\n\t\tProcess getProcess();\n\t}\n}\n"
  },
  {
    "path": "src/main/java/org/springframework/cloud/deployer/spi/local/CommandBuilder.java",
    "content": "/*\n * Copyright 2016-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.local;\n\nimport java.net.URL;\nimport java.util.Map;\nimport java.util.Optional;\n\nimport org.springframework.cloud.deployer.spi.core.AppDeploymentRequest;\n\n/**\n * Strategy interface for Execution Command builder.\n *\n * @author Ilayaperumal Gopinathan\n * @author Thomas Risberg\n * @author Michael Minella\n * @author Christian Tzolov\n */\npublic interface CommandBuilder {\n\n\t/**\n\t * Builds the execution command for an application.\n\t *\n\t * @param request the request for the application to execute.\n\t * @param appInstanceNumber application instance id.\n\t * @param appInstanceEnv the env vars tha might be needed when building the execution command.\n\t * @param debugAddress application remote debug address.\n\t * @return the build command as a string array.\n\t */\n\tProcessBuilder buildExecutionCommand(AppDeploymentRequest request,\n\t\t\tMap<String, String> appInstanceEnv, String deployerId,\n\t\t\tOptional<Integer> appInstanceNumber,\n\t\t\tLocalDeployerProperties localDeployerProperties,\n\t\t\tOptional<DebugAddress> debugAddress);\n\n\t/**\n\t * Compute an App unique URL over apps deployerId, instance index and computed port.\n\t * @param deploymentId App deployment id.\n\t * @param index App instance index.\n\t * @param port App port.\n\t * @return Returns app's URL.\n\t */\n\tURL getBaseUrl(String deploymentId, int index, int port);\n\n\t/**\n\t * Allow the concrete implementation to suggests the target application port.\n\t * @param localDeployerProperties\n\t * @return Returns a port suggestion.\n\t */\n\tint getPortSuggestion(LocalDeployerProperties localDeployerProperties);\n\n\t/**\n\t * Computes the JDWP options with the provided suspend and address arguments.\n\t * @param suspend suspend debug argument.\n\t * @param address debug address.\n\t * @return Returns the JDWP options with the provided suspend and address arguments.\n\t */\n\tdefault String getJdwpOptions(String suspend, String address) {\n\t\treturn String.format(\"-agentlib:jdwp=transport=dt_socket,server=y,suspend=%s,address=%s\", suspend, address);\n\t}\n}\n"
  },
  {
    "path": "src/main/java/org/springframework/cloud/deployer/spi/local/DebugAddress.java",
    "content": "/*\n * Copyright 2020-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.local;\n\nimport java.util.Optional;\nimport java.util.regex.Pattern;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport org.springframework.util.StringUtils;\n\n/**\n * Helper for parsing the Debugging address for both the legacy debug-port and the new debug-address properties.\n * The debug-port supports only Java 8 and is deprecated. The debug-address can be used for jdk 8 as well as\n * jdk 9 and newer.\n * When set the debug-address property has precedence over debug-port.\n *\n * @author Christian Tzolov\n */\npublic class DebugAddress {\n\tprivate static final Pattern HOSTNAME_PATTERN = Pattern.compile(\"^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\\\\-]*[a-zA-Z0-9])\\\\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\\\\-]*[A-Za-z0-9])$\");\n\tprivate static final Pattern IP_PATTERN = Pattern.compile(\"^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$\");\n\tprivate static final Pattern PORT_PATTERN = Pattern.compile(\"^([0-9]{1,4}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])$\");\n\n\tpublic final static Logger logger = LoggerFactory.getLogger(DebugAddress.class);\n\n\tprivate final String host;\n\tprivate final String port;\n\tprivate final String address;\n\tprivate final String suspend;\n\n\tprivate DebugAddress(String host, int port, String suspend) {\n\t\tthis.host = host;\n\t\tthis.port = \"\" + port;\n\t\tthis.suspend = (StringUtils.hasText(suspend)) ? suspend.trim() : \"y\";\n\t\tthis.address = (StringUtils.hasText(host)) ? String.format(\"%s:%s\", host, port) : this.port;\n\t}\n\n\tpublic String getHost() {\n\t\treturn host;\n\t}\n\n\tpublic String getPort() {\n\t\treturn port;\n\t}\n\n\tpublic String getSuspend() {\n\t\treturn suspend;\n\t}\n\n\tpublic String getAddress() {\n\t\treturn this.address;\n\t}\n\n\tpublic static Optional<DebugAddress> from(LocalDeployerProperties deployerProperties, int instanceNumber) {\n\n\t\tif (!StringUtils.hasText(deployerProperties.getDebugAddress()) && deployerProperties.getDebugPort() == null) {\n\t\t\treturn Optional.empty();\n\t\t}\n\n\t\tString debugHost = null;\n\t\tString debugPort = (\"\" + deployerProperties.getDebugPort()).trim();\n\n\t\tif (StringUtils.hasText(deployerProperties.getDebugAddress())) {\n\t\t\tString[] addressParts = deployerProperties.getDebugAddress().split(\":\");\n\n\t\t\tif (addressParts.length == 1) { // JDK 8 only\n\t\t\t\tdebugPort = addressParts[0].trim();\n\t\t\t}\n\t\t\telse if (addressParts.length == 2) { // JDK 9+\n\t\t\t\tdebugHost = addressParts[0].trim();\n\t\t\t\tdebugPort = addressParts[1].trim();\n\n\t\t\t\tif (!(\"*\".equals(debugHost)\n\t\t\t\t\t\t|| HOSTNAME_PATTERN.matcher(debugHost).matches()\n\t\t\t\t\t\t|| IP_PATTERN.matcher(debugHost).matches())) {\n\t\t\t\t\tlogger.warn(\"Invalid debug Host: {}\", deployerProperties.getDebugAddress());\n\t\t\t\t\treturn Optional.empty();\n\t\t\t\t}\n\t\t\t}\n\t\t\telse {\n\t\t\t\tlogger.warn(\"Invalid debug address: {}\", deployerProperties.getDebugAddress());\n\t\t\t\treturn Optional.empty();\n\t\t\t}\n\t\t}\n\n\t\tif (!PORT_PATTERN.matcher(debugPort).matches()) {\n\t\t\tlogger.warn(\"Invalid debug port: {}\", debugPort);\n\t\t\treturn Optional.empty();\n\t\t}\n\n\t\tint portToUse = Integer.parseInt(debugPort) + instanceNumber;\n\n\t\treturn Optional.of(new DebugAddress(debugHost, portToUse, deployerProperties.getDebugSuspend().toString()));\n\t}\n}\n"
  },
  {
    "path": "src/main/java/org/springframework/cloud/deployer/spi/local/DeployerSocketUtils.java",
    "content": "/*\n * Copyright 2016-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.local;\n\nimport java.net.InetAddress;\nimport java.net.ServerSocket;\nimport java.util.Random;\nimport java.util.SortedSet;\nimport java.util.TreeSet;\n\nimport javax.net.ServerSocketFactory;\n\nimport org.springframework.util.Assert;\n\n/**\n * Simple utility methods for working with network sockets &mdash; for example, for\n * finding available ports on {@code localhost}.\n *\n * This is a replacement for SocketUtils.   SocketUtils was introduced in Spring Framework 4.0,\n * primarily to assist in writing integration tests which start an external server on an available random port.\n * However, these utilities make no guarantee about the subsequent availability\n * of a given port and are therefore unreliable. Instead of using SocketUtils to\n * find an available local port for a server, it is recommended that you rely on a\n * server's ability to start on a random port that it selects or is assigned by the operating system.\n * To interact with that server, you should query the server for the port it is currently using.\n *\n * @author Sam Brannen\n * @author Ben Hale\n * @author Arjen Poutsma\n * @author Gunnar Hillert\n * @author Gary Russell\n * @author Glenn Renfro\n * @deprecated to be replaced with a more robust mechanism in https://github.com/spring-cloud/spring-cloud-deployer-local/issues/215\n */\n@Deprecated\npublic class DeployerSocketUtils {\n\n\t/**\n\t * The default maximum value for port ranges used when finding an available socket\n\t * port.\n\t */\n\tstatic final int PORT_RANGE_MAX = 65535;\n\n\tprivate static final Random random = new Random(System.nanoTime());\n\n\t/**\n\t * Find an available TCP port randomly selected from the range [{@code minPort},\n\t * {@value #PORT_RANGE_MAX}].\n\t * @param minPort the minimum port number\n\t * @return an available TCP port number\n\t * @throws IllegalStateException if no available port could be found\n\t */\n\tpublic static int findAvailableTcpPort(int minPort) {\n\t\treturn findAvailableTcpPort(minPort, PORT_RANGE_MAX);\n\t}\n\n\t/**\n\t * Find an available TCP port randomly selected from the range [{@code minPort},\n\t * {@code maxPort}].\n\t * @param minPort the minimum port number\n\t * @param maxPort the maximum port number\n\t * @return an available TCP port number\n\t * @throws IllegalStateException if no available port could be found\n\t */\n\tpublic static int findAvailableTcpPort(int minPort, int maxPort) {\n\t\treturn SocketType.TCP.findAvailablePort(minPort, maxPort);\n\t}\n\n\t/**\n\t * Find the requested number of available TCP ports, each randomly selected from the\n\t * range [{@code minPort}, {@code maxPort}].\n\t * @param numRequested the number of available ports to find\n\t * @param minPort the minimum port number\n\t * @param maxPort the maximum port number\n\t * @return a sorted set of available TCP port numbers\n\t * @throws IllegalStateException if the requested number of available ports could not\n\t * be found\n\t */\n\tpublic static SortedSet<Integer> findAvailableTcpPorts(int numRequested, int minPort, int maxPort) {\n\t\treturn SocketType.TCP.findAvailablePorts(numRequested, minPort, maxPort);\n\t}\n\n\tprivate enum SocketType {\n\t\tTCP {\n\t\t\t@Override\n\t\t\tprotected boolean isPortAvailable(int port) {\n\t\t\t\ttry {\n\t\t\t\t\tServerSocket serverSocket = ServerSocketFactory.getDefault().createServerSocket(port, 1,\n\t\t\t\t\t\t\tInetAddress.getByName(\"localhost\"));\n\t\t\t\t\tserverSocket.close();\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t\tcatch (Exception ex) {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\n\t\t/**\n\t\t * Determine if the specified port for this {@code SocketType} is currently\n\t\t * available on {@code localhost}.\n\t\t */\n\t\tprotected abstract boolean isPortAvailable(int port);\n\n\t\t/**\n\t\t * Find a pseudo-random port number within the range [{@code minPort},\n\t\t * {@code maxPort}].\n\t\t * @param minPort the minimum port number\n\t\t * @param maxPort the maximum port number\n\t\t * @return a random port number within the specified range\n\t\t */\n\t\tprivate int findRandomPort(int minPort, int maxPort) {\n\t\t\tint portRange = maxPort - minPort;\n\t\t\treturn minPort + random.nextInt(portRange + 1);\n\t\t}\n\n\t\t/**\n\t\t * Find an available port for this {@code SocketType}, randomly selected from the\n\t\t * range [{@code minPort}, {@code maxPort}].\n\t\t * @param minPort the minimum port number\n\t\t * @param maxPort the maximum port number\n\t\t * @return an available port number for this socket type\n\t\t * @throws IllegalStateException if no available port could be found\n\t\t */\n\t\tint findAvailablePort(int minPort, int maxPort) {\n\t\t\tAssert.isTrue(minPort > 0, \"'minPort' must be greater than 0\");\n\t\t\tAssert.isTrue(maxPort >= minPort, \"'maxPort' must be greater than or equal to 'minPort'\");\n\t\t\tAssert.isTrue(maxPort <= PORT_RANGE_MAX, \"'maxPort' must be less than or equal to \" + PORT_RANGE_MAX);\n\n\t\t\tint portRange = maxPort - minPort;\n\t\t\tint candidatePort;\n\t\t\tint searchCounter = 0;\n\t\t\tdo {\n\t\t\t\tif (searchCounter > portRange) {\n\t\t\t\t\tthrow new IllegalStateException(\n\t\t\t\t\t\t\tString.format(\"Could not find an available %s port in the range [%d, %d] after %d attempts\",\n\t\t\t\t\t\t\t\t\tname(), minPort, maxPort, searchCounter));\n\t\t\t\t}\n\t\t\t\tcandidatePort = findRandomPort(minPort, maxPort);\n\t\t\t\tsearchCounter++;\n\t\t\t}\n\t\t\twhile (!isPortAvailable(candidatePort));\n\n\t\t\treturn candidatePort;\n\t\t}\n\n\t\t/**\n\t\t * Find the requested number of available ports for this {@code SocketType}, each\n\t\t * randomly selected from the range [{@code minPort}, {@code maxPort}].\n\t\t * @param numRequested the number of available ports to find\n\t\t * @param minPort the minimum port number\n\t\t * @param maxPort the maximum port number\n\t\t * @return a sorted set of available port numbers for this socket type\n\t\t * @throws IllegalStateException if the requested number of available ports could\n\t\t * not be found\n\t\t */\n\t\tSortedSet<Integer> findAvailablePorts(int numRequested, int minPort, int maxPort) {\n\t\t\tAssert.isTrue(minPort > 0, \"'minPort' must be greater than 0\");\n\t\t\tAssert.isTrue(maxPort > minPort, \"'maxPort' must be greater than 'minPort'\");\n\t\t\tAssert.isTrue(maxPort <= PORT_RANGE_MAX, \"'maxPort' must be less than or equal to \" + PORT_RANGE_MAX);\n\t\t\tAssert.isTrue(numRequested > 0, \"'numRequested' must be greater than 0\");\n\t\t\tAssert.isTrue((maxPort - minPort) >= numRequested,\n\t\t\t\t\t\"'numRequested' must not be greater than 'maxPort' - 'minPort'\");\n\n\t\t\tSortedSet<Integer> availablePorts = new TreeSet<>();\n\t\t\tint attemptCount = 0;\n\t\t\twhile ((++attemptCount <= numRequested + 100) && availablePorts.size() < numRequested) {\n\t\t\t\tavailablePorts.add(findAvailablePort(minPort, maxPort));\n\t\t\t}\n\n\t\t\tif (availablePorts.size() != numRequested) {\n\t\t\t\tthrow new IllegalStateException(\n\t\t\t\t\t\tString.format(\"Could not find %d available %s ports in the range [%d, %d]\", numRequested,\n\t\t\t\t\t\t\t\tname(), minPort, maxPort));\n\t\t\t}\n\n\t\t\treturn availablePorts;\n\t\t}\n\t}\n}\n\n"
  },
  {
    "path": "src/main/java/org/springframework/cloud/deployer/spi/local/DockerCommandBuilder.java",
    "content": "/*\n * Copyright 2016-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.local;\n\nimport java.io.IOException;\nimport java.net.URL;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.concurrent.ThreadLocalRandom;\nimport java.util.function.Supplier;\nimport java.util.stream.Collectors;\nimport java.util.stream.Stream;\n\nimport com.fasterxml.jackson.core.type.TypeReference;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport org.springframework.cloud.deployer.resource.docker.DockerResource;\nimport org.springframework.cloud.deployer.spi.app.AppDeployer;\nimport org.springframework.cloud.deployer.spi.core.AppDeploymentRequest;\nimport org.springframework.util.StringUtils;\n\n/**\n * Command builder used to craft the command used when running apps inside docker containers.\n *\n * @author Ilayaperumal Gopinathan\n * @author Eric Bottard\n * @author Henryk Konsek\n * @author Thomas Risberg\n * @author Michael Minella\n * @author Christian Tzolov\n */\npublic class DockerCommandBuilder implements CommandBuilder {\n\n\tprivate static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();\n\n\t/**\n\t * Name of the deployment property used to specify the container name pattern to use.\n\t */\n\tpublic static final String DOCKER_CONTAINER_NAME_KEY = AppDeployer.PREFIX + \"docker.container.name\";\n\n\tprivate final Logger logger = LoggerFactory.getLogger(getClass());\n\tprivate final String dockerNetwork;\n\n\tpublic DockerCommandBuilder(String dockerNetwork) {\n\t\tthis.dockerNetwork = dockerNetwork;\n\t}\n\n\t@Override\n\tpublic int getPortSuggestion(LocalDeployerProperties localDeployerProperties) {\n\t\treturn ThreadLocalRandom.current().nextInt(localDeployerProperties.getDocker().getPortRange().getLow(),\n\t\t\t\tlocalDeployerProperties.getDocker().getPortRange().getHigh());\n\t}\n\n\t@Override\n\tpublic URL getBaseUrl(String deploymentId, int index, int port) {\n\t\ttry {\n\t\t\treturn new URL(\"http\", String.format(\"%s-%d\", deploymentId, index), port, \"\");\n\t\t}\n\t\tcatch (Exception e) {\n\t\t\tthrow new IllegalArgumentException(e);\n\t\t}\n\t}\n\n\t@Override\n\tpublic ProcessBuilder buildExecutionCommand(AppDeploymentRequest request, Map<String, String> appInstanceEnv, String deployerId,\n\t\t\tOptional<Integer> appInstanceNumber, LocalDeployerProperties localDeployerProperties,\n\t\t\tOptional<DebugAddress> debugAddressOption) {\n\n\t\tappInstanceEnv.put(\"deployerId\", deployerId);\n\t\tList<String> commands = addDockerOptions(request, appInstanceEnv, appInstanceNumber, localDeployerProperties, debugAddressOption);\n\t\tcommands.addAll(request.getCommandlineArguments());\n\t\tlogger.debug(\"Docker Command = \" + commands);\n\t\treturn new ProcessBuilder(Arrays.asList(AbstractLocalDeployerSupport.windowsSupport(commands.toArray(new String[0]))));\n\t}\n\n\tprivate List<String> addDockerOptions(AppDeploymentRequest request, Map<String, String> appInstanceEnv,\n\t\t\tOptional<Integer> appInstanceNumber, LocalDeployerProperties localDeployerProperties,\n\t\t\tOptional<DebugAddress> debugAddressOption) {\n\n\t\tList<String> commands = new ArrayList<>();\n\t\tcommands.add(\"docker\");\n\t\tcommands.add(\"run\");\n\n\t\tif (StringUtils.hasText(this.dockerNetwork)) {\n\t\t\tcommands.add(\"--network\");\n\t\t\tcommands.add(this.dockerNetwork);\n\t\t}\n\n\t\tif (localDeployerProperties.getDocker().isDeleteContainerOnExit()) {\n\t\t\tcommands.add(\"--rm\");\n\t\t}\n\n\t\t// Add env vars\n\t\tfor (String env : appInstanceEnv.keySet()) {\n\t\t\tcommands.add(\"-e\");\n\t\t\tcommands.add(String.format(\"%s=%s\", env, appInstanceEnv.get(env)));\n\t\t}\n\n\t\tdebugAddressOption.ifPresent(debugAddress -> {\n\t\t\tString debugCommand = getJdwpOptions(debugAddress.getSuspend(), debugAddress.getAddress());\n\t\t\tlogger.debug(\"Deploying app with Debug Command = [{}]\", debugCommand);\n\n\t\t\tcommands.add(\"-e\");\n\t\t\tcommands.add(\"JAVA_TOOL_OPTIONS=\" + debugCommand);\n\t\t\tcommands.add(\"-p\");\n\t\t\tcommands.add(String.format(\"%s:%s\", debugAddress.getPort(), debugAddress.getPort()));\n\t\t});\n\n\t\tString port = getPort(appInstanceEnv);\n\n\t\tif (StringUtils.hasText(port)) {\n\t\t\tcommands.add(\"-p\");\n\t\t\tcommands.add(String.format(\"%s:%s\", port, port));\n\t\t}\n\n\t\tapplyPortMappings(commands,localDeployerProperties);\n\t\tapplyVolumeMountings(commands,localDeployerProperties);\n\n\n\t\tif (request.getDeploymentProperties().containsKey(DOCKER_CONTAINER_NAME_KEY)) {\n\t\t\tif (appInstanceNumber.isPresent()) {\n\t\t\t\tcommands.add(String.format(\"--name=%s-%d\", request.getDeploymentProperties().get(DOCKER_CONTAINER_NAME_KEY), appInstanceNumber.get()));\n\t\t\t}\n\t\t\telse {\n\t\t\t\tcommands.add(String.format(\"--name=%s\", request.getDeploymentProperties().get(DOCKER_CONTAINER_NAME_KEY)));\n\t\t\t}\n\t\t}\n\t\telse {\n\t\t\tString group = request.getDeploymentProperties().get(AppDeployer.GROUP_PROPERTY_KEY);\n\t\t\tif (StringUtils.hasText(group)) {\n\t\t\t\tString deploymentId = String.format(\"%s.%s\", group, request.getDefinition().getName());\n\t\t\t\tint index = appInstanceNumber.orElse(0);\n\t\t\t\tcommands.add(String.format(\"--name=%s-%d\", deploymentId, index));\n\t\t\t}\n\t\t}\n\n\t\tDockerResource dockerResource = (DockerResource) request.getResource();\n\n\t\ttry {\n\t\t\tString dockerImageURI = dockerResource.getURI().toString();\n\t\t\tcommands.add(dockerImageURI.substring(\"docker:\".length()));\n\t\t}\n\t\tcatch (IOException e) {\n\t\t\tthrow new IllegalStateException(e);\n\t\t}\n\n\t\treturn commands;\n\t}\n\n\tprivate void applyVolumeMountings(List<String> commands, LocalDeployerProperties localDeployerProperties) {\n\t\tString volumeMounts = localDeployerProperties.getDocker().getVolumeMounts();\n\t\tif (StringUtils.hasText(volumeMounts)) {\n\t\t\tfor (String v : parseMapping(volumeMounts)) {\n\t\t\t\tcommands.add(\"-v\");\n\t\t\t\tcommands.add(v);\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate void applyPortMappings(List<String> commands, LocalDeployerProperties properties) {\n\t\tString portMappings = properties.getDocker().getPortMappings();\n\t\tif (StringUtils.hasText(portMappings)) {\n\t\t\tfor (String p : parseMapping(portMappings)) {\n\t\t\t\tcommands.add(\"-p\");\n\t\t\t\tcommands.add(p);\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate String getPort(Map<String, String> appInstanceEnv) {\n\t\tif (appInstanceEnv.containsKey(AbstractLocalDeployerSupport.SPRING_APPLICATION_JSON)) {\n\t\t\ttry {\n\t\t\t\tHashMap<String, String> flatProperties = new HashMap<>((OBJECT_MAPPER.readValue(\n\t\t\t\t\t\tappInstanceEnv.get(AbstractLocalDeployerSupport.SPRING_APPLICATION_JSON),\n\t\t\t\t\t\tnew TypeReference<HashMap<String, String>>() {})));\n\n\t\t\t\tif (flatProperties.containsKey(LocalAppDeployer.SERVER_PORT_KEY)) {\n\t\t\t\t\treturn flatProperties.get(LocalAppDeployer.SERVER_PORT_KEY);\n\t\t\t\t}\n\t\t\t\t// fall back to appInstanceEnv.\n\t\t\t}\n\t\t\tcatch (IOException e) {\n\t\t\t\tthrow new IllegalArgumentException(\"Unable to determine server port from SPRING_APPLICATION_JSON\");\n\t\t\t}\n\t\t}\n\t\treturn appInstanceEnv.get(LocalAppDeployer.SERVER_PORT_KEY);\n\t}\n\n\tprivate List<String> parseMapping(String map) {\n\t\tSupplier<Stream<String>> stream = () -> Arrays.stream(map.split(\",\"));\n\t\tstream.get().filter(s -> !s.contains(\":\")).forEach(s -> logger.warn(\"incomplete mapping {} will be ignored\", s));\n\t\treturn stream.get().filter(s -> s.contains(\":\")).collect(Collectors.toList());\n\t}\n}\n"
  },
  {
    "path": "src/main/java/org/springframework/cloud/deployer/spi/local/HttpProbeExecutor.java",
    "content": "/*\n * Copyright 2020 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage org.springframework.cloud.deployer.spi.local;\n\nimport java.net.URI;\nimport java.net.URL;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport org.springframework.cloud.deployer.spi.local.LocalDeployerProperties.HttpProbe;\nimport org.springframework.http.HttpStatus;\nimport org.springframework.http.ResponseEntity;\nimport org.springframework.web.client.RestTemplate;\nimport org.springframework.web.util.DefaultUriBuilderFactory;\n\n/**\n * Simple probe executor using rest endpoints.\n *\n * @author Janne Valkealahti\n *\n */\npublic class HttpProbeExecutor {\n\n\tprivate static final Logger logger = LoggerFactory.getLogger(HttpProbeExecutor.class);\n    private final RestTemplate restTemplate;\n    private final URI uri;\n\n    public HttpProbeExecutor(RestTemplate restTemplate, URI uri) {\n        this.restTemplate = restTemplate;\n        this.uri = uri;\n    }\n\n    public static HttpProbeExecutor from(URL baseUrl, HttpProbe httpProbe) {\n        URI base = null;\n        try {\n            base = baseUrl.toURI();\n        } catch (Exception e) {\n        }\n        if (httpProbe == null || httpProbe.getPath() == null || base == null) {\n            return null;\n        }\n        DefaultUriBuilderFactory uriBuilderFactory = new DefaultUriBuilderFactory(base.toString());\n        uriBuilderFactory.setEncodingMode(DefaultUriBuilderFactory.EncodingMode.NONE);\n        URI uri = uriBuilderFactory.builder().path(\"{path}\").build(httpProbe.getPath());\n        return new HttpProbeExecutor(new RestTemplate(), uri);\n    }\n\n    public boolean probe() {\n        try {\n            logger.info(\"Probing for {}\", this.uri);\n            ResponseEntity<Void> response = restTemplate.getForEntity(uri, Void.class);\n            HttpStatus statusCode = response.getStatusCode();\n            boolean ok = statusCode.is2xxSuccessful();\n            if (!ok) {\n                logger.info(\"Probe for {} returned {}\", this.uri, statusCode);\n            }\n            return ok;\n        } catch (Exception e) {\n            logger.trace(\"Probe error for {}\", this.uri, e);\n        }\n        return false;\n    }\n}\n"
  },
  {
    "path": "src/main/java/org/springframework/cloud/deployer/spi/local/JavaCommandBuilder.java",
    "content": "/*\n * Copyright 2016-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.local;\n\nimport java.io.IOException;\nimport java.net.Inet4Address;\nimport java.net.URL;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Iterator;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Map.Entry;\nimport java.util.Optional;\nimport java.util.concurrent.ThreadLocalRandom;\nimport java.util.regex.Pattern;\nimport java.util.stream.Stream;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport org.springframework.boot.context.properties.bind.Bindable;\nimport org.springframework.boot.context.properties.bind.Binder;\nimport org.springframework.boot.context.properties.source.MapConfigurationPropertySource;\nimport org.springframework.cloud.deployer.spi.app.AppDeployer;\nimport org.springframework.cloud.deployer.spi.core.AppDeploymentRequest;\nimport org.springframework.cloud.deployer.spi.util.ByteSizeUtils;\nimport org.springframework.core.io.Resource;\nimport org.springframework.util.StringUtils;\n\n/**\n * @author Mark Pollack\n * @author Ilayaperumal Gopinathan\n * @author Thomas Risberg\n * @author Michael Minella\n */\npublic class JavaCommandBuilder implements CommandBuilder {\n\n\tprivate final Logger logger = LoggerFactory.getLogger(getClass());\n\n\tprivate final LocalDeployerProperties properties;\n\n\tpublic JavaCommandBuilder(LocalDeployerProperties properties) {\n\t\tthis.properties = properties;\n\t}\n\n\t@Override\n\tpublic int getPortSuggestion(LocalDeployerProperties localDeployerProperties) {\n\t\treturn ThreadLocalRandom.current().nextInt(localDeployerProperties.getPortRange().getLow(),\n\t\t\t\tlocalDeployerProperties.getPortRange().getHigh());\n\t}\n\n\t@Override\n\tpublic URL getBaseUrl(String deploymentId, int index, int port) {\n\t\ttry {\n\t\t\treturn new URL(\"http\", Inet4Address.getLocalHost().getHostAddress(), port, \"\");\n\t\t}\n\t\tcatch (Exception e) {\n\t\t\tthrow new IllegalArgumentException(e);\n\t\t}\n\t}\n\n\t@Override\n\tpublic ProcessBuilder buildExecutionCommand(AppDeploymentRequest request, Map<String, String> appInstanceEnv,\n\t\t\tString deployerId, Optional<Integer> appInstanceNumber, LocalDeployerProperties localDeployerProperties,\n\t\t\tOptional<DebugAddress> debugAddressOption) {\n\t\tArrayList<String> commands = new ArrayList<>();\n\t\tMap<String, String> deploymentProperties = request.getDeploymentProperties();\n\t\tcommands.add(bindDeploymentProperties(deploymentProperties).getJavaCmd());\n\n\t\tdebugAddressOption.ifPresent(debugAddress -> {\n\t\t\tcommands.add(getJdwpOptions(debugAddress.getSuspend(), debugAddress.getAddress()));\n\t\t});\n\n\t\t// Add Java System Properties (ie -Dmy.prop=val) before main class or -jar\n\t\taddJavaOptions(commands, deploymentProperties, properties);\n\t\taddJavaExecutionOptions(commands, request);\n\t\tcommands.addAll(request.getCommandlineArguments());\n\t\tlogger.debug(\"Java Command = \" + commands);\n\n\t\tProcessBuilder builder = new ProcessBuilder(AbstractLocalDeployerSupport.windowsSupport(commands.toArray(new String[0])));\n\n\t\t// retain before we put in app related variables.\n\t\tretainEnvVars(builder.environment(), localDeployerProperties);\n\t\tbuilder.environment().putAll(appInstanceEnv);\n\n\t\treturn builder;\n\t}\n\n\t/**\n\t * Retain the environment variable strings in the provided set indicated by\n\t * {@link LocalDeployerProperties#getEnvVarsToInherit}.\n\t * This assumes that the provided set can be modified.\n\t *\n\t * @param vars set of environment variable strings\n\t * @param localDeployerProperties local deployer properties\n\t */\n\tprotected void retainEnvVars(Map<String, String> vars, LocalDeployerProperties localDeployerProperties) {\n\t\tList<String> patterns = new ArrayList<>(Arrays.asList(localDeployerProperties.getEnvVarsToInherit()));\n\t\tfor (Iterator<Entry<String, String>> iterator = vars.entrySet().iterator(); iterator.hasNext();) {\n\t\t\tEntry<String, String> entry = iterator.next();\n\t\t\tString var = entry.getKey();\n\t\t\tboolean retain = false;\n\t\t\tfor (String pattern : patterns) {\n\t\t\t\tif (Pattern.matches(pattern, var)) {\n\t\t\t\t\tretain = true;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (!retain) {\n\t\t\t\titerator.remove();\n\t\t\t}\n\t\t}\n\t}\n\n\tprotected void addJavaOptions(List<String> commands, Map<String, String> deploymentProperties,\n\t\t\tLocalDeployerProperties localDeployerProperties) {\n\t\tString memory = null;\n\t\tif (deploymentProperties.containsKey(AppDeployer.MEMORY_PROPERTY_KEY)) {\n\t\t\tmemory = \"-Xmx\" +\n\t\t\t\t\tByteSizeUtils.parseToMebibytes(deploymentProperties.get(AppDeployer.MEMORY_PROPERTY_KEY)) + \"m\";\n\t\t}\n\n\t\tString javaOptsString = bindDeploymentProperties(deploymentProperties).getJavaOpts();\n\t\tif (javaOptsString == null && memory != null) {\n\t\t\tcommands.add(memory);\n\t\t}\n\n\t\tif (javaOptsString != null) {\n\t\t\tString[] javaOpts = StringUtils.tokenizeToStringArray(javaOptsString, \" \");\n\t\t\tboolean noJavaMemoryOption = Stream.of(javaOpts).noneMatch(s -> s.startsWith(\"-Xmx\"));\n\t\t\tif (noJavaMemoryOption && memory != null) {\n\t\t\t\tcommands.add(memory);\n\t\t\t}\n\t\t\tcommands.addAll(Arrays.asList(javaOpts));\n\t\t}\n\t\telse {\n\t\t\tif (localDeployerProperties.getJavaOpts() != null) {\n\t\t\t\tString[] javaOpts = StringUtils.tokenizeToStringArray(localDeployerProperties.getJavaOpts(), \" \");\n\t\t\t\tcommands.addAll(Arrays.asList(javaOpts));\n\t\t\t}\n\t\t}\n\t}\n\n\tprotected void addJavaExecutionOptions(List<String> commands, AppDeploymentRequest request) {\n\t\tcommands.add(\"-jar\");\n\t\tResource resource = request.getResource();\n\t\ttry {\n\t\t\tcommands.add(resource.getFile().getAbsolutePath());\n\t\t}\n\t\tcatch (IOException e) {\n\t\t\tthrow new IllegalStateException(e);\n\t\t}\n\t}\n\n\t/**\n\t * This will merge the deployment properties that were passed in at runtime with the deployment properties\n\t * of the Deployer instance.\n\t * @param runtimeDeploymentProperties deployment properties passed in at runtime\n\t * @return merged deployer properties\n\t */\n\tprotected LocalDeployerProperties bindDeploymentProperties(Map<String, String> runtimeDeploymentProperties) {\n\t\tLocalDeployerProperties copyOfDefaultProperties = new LocalDeployerProperties(this.properties);\n\t\treturn new Binder(new MapConfigurationPropertySource(runtimeDeploymentProperties))\n\t\t\t\t.bind(LocalDeployerProperties.PREFIX, Bindable.ofInstance(copyOfDefaultProperties))\n\t\t\t\t.orElse(copyOfDefaultProperties);\n\t}\n\n}\n"
  },
  {
    "path": "src/main/java/org/springframework/cloud/deployer/spi/local/LocalActuatorTemplate.java",
    "content": "/*\n * Copyright 2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.local;\n\nimport org.springframework.cloud.deployer.spi.app.AbstractActuatorTemplate;\nimport org.springframework.cloud.deployer.spi.app.AppAdmin;\nimport org.springframework.cloud.deployer.spi.app.AppDeployer;\nimport org.springframework.cloud.deployer.spi.app.AppInstanceStatus;\nimport org.springframework.web.client.RestTemplate;\nimport org.springframework.web.util.UriComponentsBuilder;\n\n/**\n * @author David Turanski\n */\npublic class LocalActuatorTemplate extends AbstractActuatorTemplate {\n\n\tpublic LocalActuatorTemplate(RestTemplate restTemplate, AppDeployer appDeployer,\n\t\t\tAppAdmin appAdmin) {\n\t\tsuper(restTemplate, appDeployer, appAdmin);\n\t}\n\n\t@Override\n\tprotected String actuatorUrlForInstance(AppInstanceStatus appInstanceStatus) {\n\t\treturn UriComponentsBuilder.fromHttpUrl(appInstanceStatus.getAttributes().get(\"url\"))\n\t\t\t\t.path(\"/actuator\").toUriString();\n\t}\n}\n"
  },
  {
    "path": "src/main/java/org/springframework/cloud/deployer/spi/local/LocalAppDeployer.java",
    "content": "/*\n * Copyright 2016-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.local;\n\nimport java.io.File;\nimport java.io.FileInputStream;\nimport java.io.IOException;\nimport java.io.InputStreamReader;\nimport java.lang.ProcessBuilder.Redirect;\nimport java.lang.reflect.Field;\nimport java.net.HttpURLConnection;\nimport java.net.URL;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.TreeMap;\nimport java.util.concurrent.ConcurrentHashMap;\n\nimport jakarta.annotation.PreDestroy;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport org.springframework.cloud.deployer.spi.app.AppDeployer;\nimport org.springframework.cloud.deployer.spi.app.AppInstanceStatus;\nimport org.springframework.cloud.deployer.spi.app.AppScaleRequest;\nimport org.springframework.cloud.deployer.spi.app.AppStatus;\nimport org.springframework.cloud.deployer.spi.app.DeploymentState;\nimport org.springframework.cloud.deployer.spi.core.AppDeploymentRequest;\nimport org.springframework.cloud.deployer.spi.core.RuntimeEnvironmentInfo;\nimport org.springframework.cloud.deployer.spi.local.LocalDeployerProperties.HttpProbe;\nimport org.springframework.util.Assert;\nimport org.springframework.util.FileCopyUtils;\nimport org.springframework.util.StringUtils;\n\n/**\n * An {@link AppDeployer} implementation that spins off a new JVM process per app\n * instance.\n *\n * @author Eric Bottard\n * @author Marius Bogoevici\n * @author Mark Fisher\n * @author Ilayaperumal Gopinathan\n * @author Janne Valkealahti\n * @author Patrick Peralta\n * @author Thomas Risberg\n * @author Oleg Zhurakousky\n * @author Michael Minella\n * @author Glenn Renfro\n * @author Christian Tzolov\n * @author David Turanski\n */\npublic class LocalAppDeployer extends AbstractLocalDeployerSupport implements AppDeployer {\n\n\tprivate static final Logger logger = LoggerFactory.getLogger(LocalAppDeployer.class);\n\tprivate static final String JMX_DEFAULT_DOMAIN_KEY = \"spring.jmx.default-domain\";\n\tprivate static final String ENDPOINTS_SHUTDOWN_ENABLED_KEY = \"endpoints.shutdown.enabled\";\n\tprivate final Map<String, AppInstancesHolder> running = new ConcurrentHashMap<>();\n\n\t/**\n\t * Instantiates a new local app deployer.\n\t *\n\t * @param properties the properties\n\t */\n\tpublic LocalAppDeployer(LocalDeployerProperties properties) {\n\t\tsuper(properties);\n\t}\n\n\t/**\n\t * Returns the process exit value. We explicitly use Integer instead of int to indicate\n\t * that if {@code NULL} is returned, the process is still running.\n\t * @param process the process\n\t * @return the process exit value or {@code NULL} if process is still alive\n\t */\n\tprivate static Integer getProcessExitValue(Process process) {\n\t\ttry {\n\t\t\treturn process.exitValue();\n\t\t}\n\t\tcatch (IllegalThreadStateException e) {\n\t\t\t// process is still alive\n\t\t\treturn null;\n\t\t}\n\t}\n\n\t/**\n\t * Gets the local process pid if available. This should be a safe workaround for unix\n\t * systems where reflection can be used to get pid. More reliable way should land with\n\t * jdk9.\n\t *\n\t * @param p the process\n\t * @return the local process pid\n\t */\n\tprivate static synchronized int getLocalProcessPid(Process p) {\n\t\tint pid = 0;\n\t\ttry {\n\t\t\tif (p.getClass().getName().equals(\"java.lang.UNIXProcess\")) {\n\t\t\t\tField f = p.getClass().getDeclaredField(\"pid\");\n\t\t\t\tf.setAccessible(true);\n\t\t\t\tpid = f.getInt(p);\n\t\t\t\tf.setAccessible(false);\n\t\t\t}\n\t\t}\n\t\tcatch (Exception e) {\n\t\t\tpid = 0;\n\t\t}\n\t\treturn pid;\n\t}\n\n\t@Override\n\tpublic String deploy(AppDeploymentRequest request) {\n\t\tString group = request.getDeploymentProperties().get(GROUP_PROPERTY_KEY);\n\t\tString deploymentId = String.format(\"%s.%s\", group, request.getDefinition().getName());\n\t\tvalidateStatus(deploymentId, DeploymentState.unknown);\n\t\tList<AppInstance> processes = new ArrayList<>();\n\t\trunning.put(deploymentId, new AppInstancesHolder(processes, request));\n\n\t\ttry {\n\t\t\tPath workDir = createWorkingDir(request.getDeploymentProperties(), deploymentId);\n\n\t\t\tString countProperty = request.getDeploymentProperties().get(COUNT_PROPERTY_KEY);\n\t\t\tint count = (StringUtils.hasText(countProperty)) ? Integer.parseInt(countProperty) : 1;\n\n\t\t\tfor (int index = 0; index < count; index++) {\n\t\t\t\tprocesses.add(deployApp(request, workDir, group, deploymentId, index, request.getDeploymentProperties()));\n\t\t\t}\n\t\t}\n\t\tcatch (IOException e) {\n\t\t\tthrow new RuntimeException(\"Exception trying to deploy \" + request, e);\n\t\t}\n\t\treturn deploymentId;\n\t}\n\n\t@Override\n\tpublic void scale(AppScaleRequest appScaleRequest) {\n\t\tvalidateStatus(appScaleRequest.getDeploymentId(), DeploymentState.deployed);\n\t\tAppInstancesHolder holder = running.get(appScaleRequest.getDeploymentId());\n\t\tList<AppInstance> instances = holder != null ? holder.instances : null;\n\t\tif (instances == null) {\n\t\t\tthrow new IllegalStateException(\n\t\t\t\t\t\"Can't find existing instances for deploymentId \" + appScaleRequest.getDeploymentId());\n\t\t}\n\t\tAppDeploymentRequest request = holder.request;\n\n\t\tString group = request.getDeploymentProperties().get(GROUP_PROPERTY_KEY);\n\t\tString deploymentId = String.format(\"%s.%s\", group, request.getDefinition().getName());\n\n\t\ttry {\n\t\t\tPath workDir = createWorkingDir(request.getDeploymentProperties(), deploymentId);\n\t\t\tint deltaCount = appScaleRequest.getCount() - instances.size();\n\t\t\tint targetCount = instances.size() + deltaCount;\n\n\t\t\tif (deltaCount > 0) {\n\t\t\t\tfor (int index = instances.size(); index < targetCount; index++) {\n\t\t\t\t\tinstances.add(deployApp(request, workDir, group, deploymentId, index, request.getDeploymentProperties()));\n\t\t\t\t}\n\t\t\t}\n\t\t\telse if (deltaCount < 0) {\n\t\t\t\tList<AppInstance> processes = new ArrayList<>();\n\t\t\t\tfor (int index = instances.size() - 1; index >= targetCount; index--) {\n\t\t\t\t\tprocesses.add(instances.remove(index));\n\t\t\t\t}\n\t\t\t\tfor (AppInstance instance : processes) {\n\t\t\t\t\tif (isAlive(instance.getProcess())) {\n\t\t\t\t\t\tlogger.info(\"Un-deploying app with deploymentId {} instance {}.\", deploymentId, instance.getInstanceNumber());\n\t\t\t\t\t\tshutdownAndWait(instance);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t}\n\t\tcatch (IOException e) {\n\t\t\tthrow new RuntimeException(\"Exception trying to deploy \" + request, e);\n\t\t}\n\t}\n\n\t@Override\n\tpublic void undeploy(String id) {\n\t\tAppInstancesHolder holder = running.get(id);\n\t\tList<AppInstance> processes = holder != null ? holder.instances : null;\n\t\tif (processes != null) {\n\t\t\tfor (AppInstance instance : processes) {\n\t\t\t\tif (isAlive(instance.getProcess())) {\n\t\t\t\t\tlogger.info(\"Un-deploying app with deploymentId {} instance {}.\", id, instance.getInstanceNumber());\n\t\t\t\t\tshutdownAndWait(instance);\n\t\t\t\t}\n\t\t\t}\n\t\t\trunning.remove(id);\n\t\t}\n\t\telse {\n\t\t\tthrow new IllegalStateException(String.format(\"App with deploymentId %s is not in a deployed state.\", id));\n\t\t}\n\t}\n\n\t@Override\n\tpublic AppStatus status(String id) {\n\t\tAppInstancesHolder holder = running.get(id);\n\t\tList<AppInstance> instances = holder != null ? holder.instances : null;\n\t\tAppStatus.Builder builder = AppStatus.of(id);\n\n\t\tif (instances != null) {\n\t\t\tfor (AppInstance instance : instances) {\n\t\t\t\tbuilder.with(instance);\n\t\t\t}\n\t\t}\n\n\t\treturn builder.build();\n\t}\n\n\t@Override\n\tpublic String getLog(String id) {\n\t\tAppInstancesHolder holder = running.get(id);\n\t\tList<AppInstance> instances = holder != null ? holder.instances : null;\n\t\tStringBuilder stringBuilder = new StringBuilder();\n\t\tif (instances != null) {\n\t\t\tfor (AppInstance instance : instances) {\n\t\t\t\tString stderr = instance.getStdErr();\n\t\t\t\tif (StringUtils.hasText(stderr)) {\n\t\t\t\t\tstringBuilder.append(\"stderr:\\n\");\n\t\t\t\t\tstringBuilder.append(stderr);\n\t\t\t\t}\n\t\t\t\tString stdout = instance.getStdOut();\n\t\t\t\tif (StringUtils.hasText(stdout)) {\n\t\t\t\t\tstringBuilder.append(\"stdout:\\n\");\n\t\t\t\t\tstringBuilder.append(stdout);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn stringBuilder.toString();\n\t}\n\n\t@Override\n\tpublic RuntimeEnvironmentInfo environmentInfo() {\n\t\treturn super.createRuntimeEnvironmentInfo(AppDeployer.class, this.getClass());\n\t}\n\n\t@PreDestroy\n\tpublic void shutdown() {\n\t\tfor (String deploymentId : running.keySet()) {\n\t\t\tundeploy(deploymentId);\n\t\t}\n\t}\n\n\tprivate AppInstance deployApp(AppDeploymentRequest request, Path workDir, String group, String deploymentId,\n\t\t\tint index, Map<String, String> deploymentProperties) throws IOException {\n\n\t\tLocalDeployerProperties localDeployerPropertiesToUse = bindDeploymentProperties(deploymentProperties);\n\n\t\t// consolidatedAppProperties is a Map of all application properties to be used by\n\t\t// the app being launched. These values should end up as environment variables\n\t\t// either explicitly or as a SPRING_APPLICATION_JSON value.\n\t\tHashMap<String, String> consolidatedAppProperties = new HashMap<>(request.getDefinition().getProperties());\n\n\t\tconsolidatedAppProperties.put(JMX_DEFAULT_DOMAIN_KEY, deploymentId);\n\n\t\tif (!request.getDefinition().getProperties().containsKey(ENDPOINTS_SHUTDOWN_ENABLED_KEY)) {\n\t\t\tconsolidatedAppProperties.put(ENDPOINTS_SHUTDOWN_ENABLED_KEY, \"true\");\n\t\t}\n\n\t\tconsolidatedAppProperties.put(\"endpoints.jmx.unique-names\", \"true\");\n\n\t\tif (group != null) {\n\t\t\tconsolidatedAppProperties.put(\"spring.cloud.application.group\", group);\n\t\t}\n\n\t\t// This Map is the consolidated application properties *for the instance*\n\t\t// to be deployed in this iteration\n\t\tMap<String, String> appInstanceEnv = new HashMap<>(consolidatedAppProperties);\n\n\t\t// we only set 'normal' style props reflecting what we set for env format\n\t\t// for cross reference to work inside SAJ.\n\t\t// looks like for now we can't remove these env style formats as i.e.\n\t\t// DeployerIntegrationTestProperties in tests really assume 'INSTANCE_INDEX' and\n\t\t// this might be indication that we can't yet fully remove those.\n\t\tString guid = toGuid(deploymentId, index);\n\t\tif (useSpringApplicationJson(request)) {\n\t\t\tappInstanceEnv.put(\"instance.index\", Integer.toString(index));\n\t\t\tappInstanceEnv.put(\"spring.cloud.stream.instanceIndex\", Integer.toString(index));\n\t\t\tappInstanceEnv.put(\"spring.application.index\", Integer.toString(index));\n\t\t\tappInstanceEnv.put(\"spring.cloud.application.guid\", guid);\n\t\t}\n\t\telse {\n\t\t\tappInstanceEnv.put(\"INSTANCE_INDEX\", Integer.toString(index));\n\t\t\tappInstanceEnv.put(\"SPRING_APPLICATION_INDEX\", Integer.toString(index));\n\t\t\tappInstanceEnv.put(\"SPRING_CLOUD_APPLICATION_GUID\", guid);\n\t\t}\n\n\t\tthis.getLocalDeployerProperties().getAppAdmin().addCredentialsToAppEnvironmentAsProperties(appInstanceEnv);\n\n\t\tboolean useDynamicPort = !request.getDefinition().getProperties().containsKey(SERVER_PORT_KEY);\n\t\t// WATCH OUT: The calcServerPort sets the computed port in the appInstanceEnv#SERVER_PORT_KEY.\n\t\t//  Later is implicitly passed to and used inside the command builder. Therefore the calcServerPort() method\n\t\t//  must always be called before the buildProcessBuilder(..)!\n\t\tint port = calcServerPort(request, useDynamicPort, appInstanceEnv);\n\n\t\tProcessBuilder builder = buildProcessBuilder(request, appInstanceEnv, Optional.of(index), deploymentId)\n\t\t\t\t.inheritIO();\n\t\tbuilder.directory(workDir.toFile());\n\n\t\tURL baseUrl = (StringUtils.hasText(localDeployerPropertiesToUse.getHostname())) ?\n\t\t\t\tnew URL(\"http\", localDeployerPropertiesToUse.getHostname(), port, \"\")\n\t\t\t\t: getCommandBuilder(request).getBaseUrl(deploymentId, index, port);\n\n\t\tAppInstance instance = new AppInstance(deploymentId, index, port, baseUrl,\n\t\t\t\tlocalDeployerPropertiesToUse.getStartupProbe(), localDeployerPropertiesToUse.getHealthProbe());\n\t\tif (this.shouldInheritLogging(request)) {\n\t\t\tinstance.start(builder, workDir);\n\t\t\tlogger.info(\"Deploying app with deploymentId {} instance {}.\\n   Logs will be inherited.\",\n\t\t\t\t\tdeploymentId, index);\n\t\t}\n\t\telse {\n\t\t\tinstance.start(builder, workDir, getLocalDeployerProperties().isDeleteFilesOnExit());\n\t\t\tlogger.info(\"Deploying app with deploymentId {} instance {}.\\n   Logs will be in {}\", deploymentId,\n\t\t\t\t\tindex, workDir);\n\t\t}\n\t\treturn instance;\n\t}\n\n\tprivate Path createWorkingDir(Map<String, String> deploymentProperties, String deploymentId) throws IOException {\n\t\tLocalDeployerProperties localDeployerPropertiesToUse = bindDeploymentProperties(deploymentProperties);\n\n\t\tPath workingDirectoryRoot = Files.createDirectories(localDeployerPropertiesToUse.getWorkingDirectoriesRoot());\n\t\tPath workDir = Files.createDirectories(workingDirectoryRoot.resolve(Long.toString(System.currentTimeMillis())).resolve(deploymentId));\n\n\t\tif (getLocalDeployerProperties().isDeleteFilesOnExit()) {\n\t\t\tworkDir.toFile().deleteOnExit();\n\t\t}\n\t\treturn workDir;\n\t}\n\n\tprivate void validateStatus(String deploymentId, DeploymentState expectedState) {\n\t\tDeploymentState state = status(deploymentId).getState();\n\t\tAssert.state(state == expectedState,\n\t\t\t\tString.format(\"App with deploymentId [%s] with state [%s] doesn't match expected state [%s]\",\n\t\t\t\t\t\tdeploymentId, state, expectedState));\n\t}\n\n\tprivate static String toGuid(String deploymentId, int appIndex) {\n\t\treturn String.format(\"%s-%s\", deploymentId, appIndex);\n\t}\n\n\tprivate static class AppInstance implements Instance, AppInstanceStatus {\n\n\t\tprivate final String deploymentId;\n\n\t\tprivate final int instanceNumber;\n\n\t\tprivate final URL baseUrl;\n\t\tprivate final Map<String, String> attributes = new TreeMap<>();\n\t\tprivate int pid;\n\t\tprivate Process process;\n\t\tprivate File workFile;\n\t\tprivate File stdout;\n\t\tprivate File stderr;\n\t\tprivate int port;\n\t\tprivate HttpProbeExecutor startupProbeExecutor;\n\t\tprivate HttpProbeExecutor healthProbeExecutor;\n\t\tprivate boolean startupProbeOk;\n\n\t\tprivate AppInstance(String deploymentId, int instanceNumber, int port, URL baseUrl, HttpProbe startupProbe,\n\t\t\t\tHttpProbe healthProbe) {\n\t\t\tthis.deploymentId = deploymentId;\n\t\t\tthis.instanceNumber = instanceNumber;\n\t\t\tthis.port = port;\n\t\t\tthis.baseUrl = baseUrl;\n\t\t\tthis.attributes.put(\"port\", Integer.toString(port));\n\t\t\tthis.attributes.put(\"guid\", toGuid(deploymentId, instanceNumber));\n\t\t\tthis.attributes.put(\"url\", baseUrl.toString());\n\t\t\tthis.startupProbeExecutor = HttpProbeExecutor.from(baseUrl, startupProbe);\n\t\t\tthis.healthProbeExecutor = HttpProbeExecutor.from(baseUrl, healthProbe);\n\t\t}\n\n\t\t@Override\n\t\tpublic String getId() {\n\t\t\treturn deploymentId + \"-\" + instanceNumber;\n\t\t}\n\n\t\t@Override\n\t\tpublic URL getBaseUrl() {\n\t\t\treturn this.baseUrl;\n\t\t}\n\n\t\t@Override\n\t\tpublic Process getProcess() {\n\t\t\treturn this.process;\n\t\t}\n\n\t\t@Override\n\t\tpublic String toString() {\n\t\t\treturn String.format(\"%s [%s]\", getId(), getState());\n\t\t}\n\n\t\t@Override\n\t\tpublic DeploymentState getState() {\n\t\t\tInteger exit = getProcessExitValue(process);\n\t\t\t// TODO: consider using exit code mapper concept from batch\n\t\t\tif (exit != null) {\n\t\t\t\treturn DeploymentState.failed;\n\t\t\t}\n\t\t\tif (port < 1) {\n\t\t\t\t// Support case where user passed in zero or negative port indicating fully random\n\t\t\t\t// chosen by OS. In this case we simply return deployed if process is up.\n\t\t\t\t// Also we can't even try http check as we would not know port to connect to.\n\t\t\t\treturn DeploymentState.deployed;\n\t\t\t}\n\t\t\t// do startup probe first and only until we're deployed\n\t\t\tif (startupProbeExecutor != null && !startupProbeOk) {\n\t\t\t\tboolean ok = startupProbeExecutor.probe();\n\t\t\t\tif (ok) {\n\t\t\t\t\tstartupProbeOk = true;\n\t\t\t\t\treturn DeploymentState.deployed;\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\treturn DeploymentState.deploying;\n\t\t\t\t}\n\t\t\t}\n\t\t\t// now deployed, checking health probe\n\t\t\tif (healthProbeExecutor != null) {\n\t\t\t\treturn healthProbeExecutor.probe() ? DeploymentState.deployed : DeploymentState.failed;\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tHttpURLConnection urlConnection = (HttpURLConnection) baseUrl.openConnection();\n\t\t\t\turlConnection.setConnectTimeout(100);\n\t\t\t\turlConnection.connect();\n\t\t\t\turlConnection.disconnect();\n\t\t\t\treturn DeploymentState.deployed;\n\t\t\t}\n\t\t\tcatch (IOException e) {\n\t\t\t\treturn DeploymentState.deploying;\n\t\t\t}\n\t\t}\n\n\t\tpublic String getStdOut() {\n\t\t\ttry {\n\t\t\t\treturn FileCopyUtils.copyToString(new InputStreamReader(new FileInputStream(this.stdout)));\n\t\t\t}\n\t\t\tcatch (IOException e) {\n\t\t\t\treturn \"Log retrieval returned \" + e.getMessage();\n\t\t\t}\n\t\t}\n\n\t\tpublic String getStdErr() {\n\t\t\ttry {\n\t\t\t\treturn FileCopyUtils.copyToString(new InputStreamReader(new FileInputStream(this.stderr)));\n\t\t\t}\n\t\t\tcatch (IOException e) {\n\t\t\t\treturn \"Log retrieval returned \" + e.getMessage();\n\t\t\t}\n\t\t}\n\n\t\tpublic int getInstanceNumber() {\n\t\t\treturn instanceNumber;\n\t\t}\n\n\t\t@Override\n\t\tpublic Map<String, String> getAttributes() {\n\t\t\treturn this.attributes;\n\t\t}\n\n\t\t/**\n\t\t * Will start the process while redirecting 'out' and 'err' streams to the 'out' and 'err'\n\t\t * streams of this process.\n\t\t */\n\t\tprivate void start(ProcessBuilder builder, Path workDir) throws IOException {\n\t\t\tif (logger.isDebugEnabled()) {\n\t\t\t\tlogger.debug(\"Local App Deployer Commands: \" + String.join(\",\", builder.command())\n\t\t\t\t\t\t+ \", Environment: \" + builder.environment());\n\t\t\t}\n\t\t\tthis.workFile = workDir.toFile();\n\t\t\tthis.attributes.put(\"working.dir\", this.workFile.getAbsolutePath());\n\t\t\tthis.process = builder.start();\n\t\t\tthis.pid = getLocalProcessPid(this.process);\n\t\t\tif (pid > 0) {\n\t\t\t\t// add pid if we got it\n\t\t\t\tattributes.put(\"pid\", Integer.toString(pid));\n\t\t\t}\n\t\t}\n\n\t\tprivate void start(ProcessBuilder builder, Path workDir, boolean deleteOnExit) throws IOException {\n\t\t\tString workDirPath = workDir.toFile().getAbsolutePath();\n\n\t\t\tthis.stdout = Files.createFile(Paths.get(workDirPath, \"stdout_\" + instanceNumber + \".log\")).toFile();\n\t\t\tthis.attributes.put(\"stdout\", stdout.getAbsolutePath());\n\n\t\t\tthis.stderr = Files.createFile(Paths.get(workDirPath, \"stderr_\" + instanceNumber + \".log\")).toFile();\n\t\t\tthis.attributes.put(\"stderr\", stderr.getAbsolutePath());\n\n\t\t\tif (deleteOnExit) {\n\t\t\t\tthis.stdout.deleteOnExit();\n\t\t\t\tthis.stderr.deleteOnExit();\n\t\t\t}\n\t\t\tbuilder.redirectOutput(Redirect.to(this.stdout));\n\t\t\tbuilder.redirectError(Redirect.to(this.stderr));\n\n\t\t\tthis.start(builder, workDir);\n\t\t}\n\t}\n\n\tprivate static class AppInstancesHolder {\n\t\tfinal List<AppInstance> instances;\n\t\tfinal AppDeploymentRequest request;\n\n\t\tpublic AppInstancesHolder(List<AppInstance> instances, AppDeploymentRequest request) {\n\t\t\tthis.instances = instances;\n\t\t\tthis.request = request;\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "src/main/java/org/springframework/cloud/deployer/spi/local/LocalDeployerAutoConfiguration.java",
    "content": "/*\n * Copyright 2016-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.local;\n\nimport org.springframework.boot.autoconfigure.AutoConfigureOrder;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;\nimport org.springframework.boot.context.properties.EnableConfigurationProperties;\nimport org.springframework.cloud.deployer.spi.app.ActuatorOperations;\nimport org.springframework.cloud.deployer.spi.app.AppDeployer;\nimport org.springframework.cloud.deployer.spi.task.TaskLauncher;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.core.Ordered;\nimport org.springframework.web.client.RestTemplate;\n\n/**\n * Creates a {@link LocalAppDeployer} and {@link LocalTaskLauncher}\n *\n * @author Mark Fisher\n * @author David Turanski\n */\n@Configuration\n@EnableConfigurationProperties(LocalDeployerProperties.class)\n@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)\npublic class LocalDeployerAutoConfiguration {\n\n\t@Bean\n\t@ConditionalOnMissingBean(AppDeployer.class)\n\tpublic AppDeployer appDeployer(LocalDeployerProperties properties) {\n\t\treturn new LocalAppDeployer(properties);\n\t}\n\n\t@Bean\n\t@ConditionalOnMissingBean(TaskLauncher.class)\n\tpublic TaskLauncher taskLauncher(LocalDeployerProperties properties) {\n\t\treturn new LocalTaskLauncher(properties);\n\t}\n\n\t@Bean\n\t@ConditionalOnMissingBean\n\tRestTemplate actuatorRestTemplate() {\n\t\treturn new RestTemplate();\n\t}\n\n\t@Bean\n\t@ConditionalOnMissingBean(ActuatorOperations.class)\n\tActuatorOperations actuatorOperations(RestTemplate actuatorRestTemplate, AppDeployer appDeployer,\n\t\t\tLocalDeployerProperties properties) {\n\t\treturn new LocalActuatorTemplate(actuatorRestTemplate, appDeployer, properties.getAppAdmin());\n\t}\n}\n"
  },
  {
    "path": "src/main/java/org/springframework/cloud/deployer/spi/local/LocalDeployerProperties.java",
    "content": "/*\n * Copyright 2015-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.local;\n\nimport java.io.File;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.Arrays;\n\nimport javax.validation.constraints.Min;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport org.springframework.boot.context.properties.ConfigurationProperties;\nimport org.springframework.cloud.deployer.spi.app.AppAdmin;\nimport org.springframework.core.style.ToStringCreator;\nimport org.springframework.util.Assert;\nimport org.springframework.validation.annotation.Validated;\n\n/**\n * Configuration properties for the local deployer.\n *\n * @author Eric Bottard\n * @author Mark Fisher\n * @author Ilayaperumal Gopinathan\n * @author Oleg Zhurakousky\n * @author Vinicius Carvalho\n * @author David Turanski\n * @author Christian Tzolov\n */\n@Validated\n@ConfigurationProperties(prefix = LocalDeployerProperties.PREFIX)\npublic class LocalDeployerProperties {\n\n\t/**\n\t * Top level prefix for local deployer configuration properties.\n\t */\n\tpublic static final String PREFIX = \"spring.cloud.deployer.local\";\n\n\t/**\n\t * Deployer property allowing logging to be redirected to the output stream of\n\t * the process that triggered child process. Could be set per the entire\n\t * deployment (<em>i.e.</em> {@literal deployer.*.local.inheritLogging=true}) or\n\t * per individual application (<em>i.e.</em>\n\t * {@literal deployer.<app-name>.local.inheritLogging=true}).\n\t */\n\tpublic static final String INHERIT_LOGGING = PREFIX + \".inherit-logging\";\n\n\t/**\n\t * Remote debugging property allowing one to specify port for the remote debug\n\t * session. Must be set per individual application (<em>i.e.</em>\n\t * {@literal deployer.<app-name>.local.debugPort=9999}).\n\t * @deprecated This is only JDK 8 compatible. Use the {@link #DEBUG_ADDRESS} instead for supporting all JDKs.\n\t */\n\tpublic static final String DEBUG_PORT = PREFIX + \".debug-port\";\n\n\t/**\n\t * Remote debugging property allowing one to specify the address for the remote debug\n\t * session. On Java versions 1.8 or older use the <em>port</em> format. On Java versions 1.9 or greater use the\n\t * <em>host:port</em> format. The host could default to <em>*</em>. May be set for individual applications (<em>i.e.</em>\n\t * {@literal deployer.<app-name>.local.debugAddress=*:9999}).\n\t */\n\tpublic static final String DEBUG_ADDRESS = PREFIX + \".debug-address\";\n\n\t/**\n\t * Remote debugging property allowing one to specify if the startup of the\n\t * application should be suspended until remote debug session is established.\n\t * Values must be either 'y' or 'n'. Must be set per individual application\n\t * (<em>i.e.</em> {@literal deployer.<app-name>.local.debugSuspend=y}).\n\t */\n\tpublic static final String DEBUG_SUSPEND = PREFIX + \".debug-suspend\";\n\n\tprivate static final Logger logger = LoggerFactory.getLogger(LocalDeployerProperties.class);\n\n\tprivate static final String JAVA_COMMAND = LocalDeployerUtils.isWindows() ? \"java.exe\" : \"java\";\n\n\t// looks like some windows systems uses 'Path' but process builder give it as 'PATH'\n\tprivate static final String[] ENV_VARS_TO_INHERIT_DEFAULTS_WIN = { \"TMP\", \"TEMP\", \"PATH\", \"Path\",\n\t\t\tAbstractLocalDeployerSupport.SPRING_APPLICATION_JSON };\n\n\tprivate static final String[] ENV_VARS_TO_INHERIT_DEFAULTS_OTHER = { \"TMP\", \"LANG\", \"LANGUAGE\", \"LC_.*\", \"PATH\",\n\t\t\tAbstractLocalDeployerSupport.SPRING_APPLICATION_JSON };\n\n\t/**\n\t * Directory in which all created processes will run and create log files.\n\t */\n\tprivate Path workingDirectoriesRoot = new File(System.getProperty(\"java.io.tmpdir\")).toPath();\n\n\t/**\n\t * Whether to delete created files and directories on JVM exit.\n\t */\n\tprivate boolean deleteFilesOnExit = true;\n\n\t/**\n\t * Array of regular expression patterns for environment variables that should be\n\t * passed to launched applications.\n\t */\n\tprivate String[] envVarsToInherit = LocalDeployerUtils.isWindows() ? ENV_VARS_TO_INHERIT_DEFAULTS_WIN\n\t\t\t: ENV_VARS_TO_INHERIT_DEFAULTS_OTHER;\n\n\t/**\n\t * The command to run java.\n\t */\n\tprivate String javaCmd = deduceJavaCommand();\n\n\t/**\n\t * Maximum number of seconds to wait for application shutdown. via the\n\t * {@code /shutdown} endpoint. A timeout value of 0 specifies an infinite\n\t * timeout. Default is 30 seconds.\n\t */\n\t@Min(-1)\n\tprivate int shutdownTimeout = 30;\n\n\t/**\n\t * The Java Options to pass to the JVM, e.g -Dtest=foo\n\t */\n\tprivate String javaOpts;\n\n\t/**\n\t * Flag to indicate whether application properties are passed as command line\n\t * args or in a SPRING_APPLICATION_JSON environment variable. Default value is\n\t * {@code true}.\n\t */\n\tprivate boolean useSpringApplicationJson = true;\n\n\tprivate final PortRange portRange = new PortRange();\n\n\n\t/**\n\t * The maximum concurrent tasks allowed for this platform instance.\n\t */\n\t@Min(1)\n\tprivate int maximumConcurrentTasks = 20;\n\n\t/**\n\t * Set remote debugging port for JDK 8 runtimes.\n\t * @deprecated Use the {@link #debugAddress} instead!\n\t */\n\tprivate Integer debugPort;\n\n\t/**\n\t * Debugging address for the remote clients to attache to. Addresses have the format \"<name>:<port>\" where <name>\n\t * is the host name and <port> is the socket port number at which it attaches or listens.\n\t * For JDK 8 or earlier, the address consists of the port number alone (the host name is implicit to localhost).\n\t * Example addresses for JDK version 9 or higher: <code>*:20075, 192.168.178.10:20075</code>.\n\t * Example addresses for JDK version 8 or earlier: <code>20075</code>.\n\t */\n\tprivate String debugAddress;\n\n\tpublic enum DebugSuspendType {y, n};\n\t/**\n\t * Suspend defines whether the JVM should suspend and wait for a debugger to attach or not\n\t */\n\tprivate DebugSuspendType debugSuspend = DebugSuspendType.y;\n\n\tprivate boolean inheritLogging;\n\n\tprivate final Docker docker = new Docker();\n\n\t/**\n\t * (optional) hostname to use when computing the URL of the deployed application.\n\t * By default the {@link CommandBuilder} implementations decide how to build the hostname.\n\t */\n\tprivate String hostname;\n\n\tprivate AppAdmin appAdmin = new AppAdmin();\n\n\tpublic LocalDeployerProperties() {\n\t}\n\n\tpublic LocalDeployerProperties(LocalDeployerProperties from) {\n\t\tthis.debugPort = from.getDebugPort();\n\t\tthis.debugAddress = from.getDebugAddress();\n\t\tthis.debugSuspend = from.getDebugSuspend();\n\t\tthis.deleteFilesOnExit = from.isDeleteFilesOnExit();\n\t\tthis.docker.network = from.getDocker().getNetwork();\n\t\tthis.docker.deleteContainerOnExit = from.getDocker().isDeleteContainerOnExit();\n\t\tthis.docker.portRange = from.getDocker().getPortRange();\n\t\tthis.envVarsToInherit = new String[from.getEnvVarsToInherit().length];\n\t\tSystem.arraycopy(from.getEnvVarsToInherit(), 0, this.envVarsToInherit, 0, from.getEnvVarsToInherit().length);\n\t\tthis.inheritLogging = from.isInheritLogging();\n\t\tthis.javaCmd = from.getJavaCmd();\n\t\tthis.javaOpts = from.getJavaOpts();\n\t\tthis.maximumConcurrentTasks = from.getMaximumConcurrentTasks();\n\t\tthis.portRange.high = from.getPortRange().getHigh();\n\t\tthis.portRange.low = from.getPortRange().getLow();\n\t\tthis.shutdownTimeout = from.getShutdownTimeout();\n\t\tthis.useSpringApplicationJson = from.isUseSpringApplicationJson();\n\t\tthis.workingDirectoriesRoot = Paths.get(from.getWorkingDirectoriesRoot().toUri());\n\t\tthis.hostname =from.getHostname();\n\t\tthis.appAdmin = from.appAdmin;\n\t}\n\n\tpublic static class PortRange {\n\n\t\t/**\n\t\t * Lower bound for computing applications's random port.\n\t\t */\n\t\tprivate int low = 20000;\n\n\t\t/**\n\t\t * Upper bound for computing applications's random port.\n\t\t */\n\t\tprivate int high = 61000;\n\n\t\tpublic int getLow() {\n\t\t\treturn low;\n\t\t}\n\n\t\tpublic void setLow(int low) {\n\t\t\tthis.low = low;\n\t\t}\n\n\t\tpublic int getHigh() {\n\t\t\treturn high;\n\t\t}\n\n\t\tpublic void setHigh(int high) {\n\t\t\tthis.high = high;\n\t\t}\n\n\t\t@Override\n\t\tpublic String toString() {\n\t\t\treturn \"{ low=\" + low + \", high=\" + high + '}';\n\t\t}\n\n\t\t@Override\n\t\tpublic int hashCode() {\n\t\t\tfinal int prime = 31;\n\t\t\tint result = 1;\n\t\t\tresult = prime * result + high;\n\t\t\tresult = prime * result + low;\n\t\t\treturn result;\n\t\t}\n\n\t\t@Override\n\t\tpublic boolean equals(Object obj) {\n\t\t\tif (this == obj) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t\tif (obj == null) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tif (getClass() != obj.getClass()) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tPortRange other = (PortRange) obj;\n\t\t\tif (high != other.high) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tif (low != other.low) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\treturn true;\n\t\t}\n\t}\n\n\tpublic static class Docker {\n\t\t/**\n\t\t * Container network\n\t\t */\n\t\tprivate String network = \"bridge\";\n\n\t\t/**\n\t\t * Whether to delete the container on container exit.\n\t\t */\n\t\tprivate boolean deleteContainerOnExit = true;\n\n\t\t/**\n\t\t *  Allow the Docker command builder use its own port range.\n\t\t */\n\t\tprivate PortRange portRange = new PortRange();\n\n\t\t/**\n\t\t * Set port mappings for container\n\t\t */\n\t\tprivate String portMappings;\n\n\t\t/**\n\t\t * Set volume mappings\n\t\t */\n\t\tprivate String volumeMounts;\n\n\t\tpublic PortRange getPortRange() {\n\t\t\treturn portRange;\n\t\t}\n\n\t\tpublic String getNetwork() {\n\t\t\treturn network;\n\t\t}\n\n\t\tpublic void setNetwork(String network) {\n\t\t\tthis.network = network;\n\t\t}\n\n\t\tpublic boolean isDeleteContainerOnExit() {\n\t\t\treturn deleteContainerOnExit;\n\t\t}\n\n\t\tpublic void setDeleteContainerOnExit(boolean deleteContainerOnExit) {\n\t\t\tthis.deleteContainerOnExit = deleteContainerOnExit;\n\t\t}\n\n\t\tpublic String getPortMappings() {\n\t\t\treturn portMappings;\n\t\t}\n\n\t\tpublic void setPortMappings(String portMappings) {\n\t\t\tthis.portMappings = portMappings;\n\t\t}\n\n\t\tpublic String getVolumeMounts() {\n\t\t\treturn volumeMounts;\n\t\t}\n\n\t\tpublic void setVolumeMounts(String volumeMounts) {\n\t\t\tthis.volumeMounts = volumeMounts;\n\t\t}\n\n\t\t@Override\n\t\tpublic int hashCode() {\n\t\t\tfinal int prime = 31;\n\t\t\tint result = 1;\n\t\t\tresult = prime * result + ((network == null) ? 0 : network.hashCode());\n\t\t\treturn result;\n\t\t}\n\n\t\t@Override\n\t\tpublic boolean equals(Object obj) {\n\t\t\tif (this == obj) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t\tif (obj == null) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tif (getClass() != obj.getClass()) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tDocker other = (Docker) obj;\n\t\t\tif (network == null) {\n\t\t\t\treturn other.network == null;\n\t\t\t}\n\t\t\telse return network.equals(other.network);\n\t\t}\n\t}\n\n\tpublic Docker getDocker() {\n\t\treturn docker;\n\t}\n\n\tpublic String getHostname() {\n\t\treturn hostname;\n\t}\n\n\tpublic void setHostname(String hostname) {\n\t\tthis.hostname = hostname;\n\t}\n\n\tpublic Integer getDebugPort() {\n\t\treturn debugPort;\n\t}\n\n\tpublic DebugSuspendType getDebugSuspend() {\n\t\treturn debugSuspend;\n\t}\n\n\tpublic void setDebugSuspend(DebugSuspendType debugSuspend) {\n\t\tthis.debugSuspend = debugSuspend;\n\t}\n\n\tpublic void setDebugPort(Integer debugPort) {\n\t\tlogger.warn(\"The debugPort is deprecated! It supports only pre Java 9 environments. \" +\n\t\t\t\t\"Please use the debugAddress property instead!\");\n\t\tthis.debugPort = debugPort;\n\t}\n\n\tpublic String getDebugAddress() {\n\t\treturn debugAddress;\n\t}\n\n\tpublic void setDebugAddress(String debugAddress) {\n\t\tthis.debugAddress = debugAddress;\n\t}\n\n\tpublic boolean isInheritLogging() {\n\t\treturn inheritLogging;\n\t}\n\n\tpublic void setInheritLogging(boolean inheritLogging) {\n\t\tthis.inheritLogging = inheritLogging;\n\t}\n\n\tpublic String getJavaCmd() {\n\t\treturn javaCmd;\n\t}\n\n\tpublic void setJavaCmd(String javaCmd) {\n\t\tthis.javaCmd = javaCmd;\n\t}\n\n\tpublic Path getWorkingDirectoriesRoot() {\n\t\treturn workingDirectoriesRoot;\n\t}\n\n\tpublic void setWorkingDirectoriesRoot(String workingDirectoriesRoot) {\n\t\tthis.workingDirectoriesRoot = Paths.get(workingDirectoriesRoot);\n\t}\n\n\tpublic void setWorkingDirectoriesRoot(Path workingDirectoriesRoot) {\n\t\tthis.workingDirectoriesRoot = workingDirectoriesRoot;\n\t}\n\n\tpublic boolean isDeleteFilesOnExit() {\n\t\treturn deleteFilesOnExit;\n\t}\n\n\tpublic void setDeleteFilesOnExit(boolean deleteFilesOnExit) {\n\t\tthis.deleteFilesOnExit = deleteFilesOnExit;\n\t}\n\n\tpublic String[] getEnvVarsToInherit() {\n\t\treturn envVarsToInherit;\n\t}\n\n\tpublic void setEnvVarsToInherit(String[] envVarsToInherit) {\n\t\tthis.envVarsToInherit = envVarsToInherit;\n\t}\n\n\tpublic int getShutdownTimeout() {\n\t\treturn shutdownTimeout;\n\t}\n\n\tpublic LocalDeployerProperties setShutdownTimeout(int shutdownTimeout) {\n\t\tthis.shutdownTimeout = shutdownTimeout;\n\t\treturn this;\n\t}\n\n\tpublic String getJavaOpts() {\n\t\treturn javaOpts;\n\t}\n\n\tpublic void setJavaOpts(String javaOpts) {\n\t\tthis.javaOpts = javaOpts;\n\t}\n\n\tpublic boolean isUseSpringApplicationJson() {\n\t\treturn useSpringApplicationJson;\n\t}\n\n\tpublic void setUseSpringApplicationJson(boolean useSpringApplicationJson) {\n\t\tthis.useSpringApplicationJson = useSpringApplicationJson;\n\t}\n\n\tpublic PortRange getPortRange() {\n\t\treturn portRange;\n\t}\n\n\tpublic int getMaximumConcurrentTasks() {\n\t\treturn maximumConcurrentTasks;\n\t}\n\n\tpublic void setMaximumConcurrentTasks(int maximumConcurrentTasks) {\n\t\tthis.maximumConcurrentTasks = maximumConcurrentTasks;\n\t}\n\n\tprivate HttpProbe startupProbe = new HttpProbe();\n\tprivate HttpProbe healthProbe = new HttpProbe();\n\n\tpublic HttpProbe getStartupProbe() {\n\t\treturn startupProbe;\n\t}\n\n\tpublic void setStartupProbe(HttpProbe startupProbe) {\n\t\tthis.startupProbe = startupProbe;\n\t}\n\n\tpublic HttpProbe getHealthProbe() {\n\t\treturn healthProbe;\n\t}\n\n\tpublic void setHealthProbe(HttpProbe healthProbe) {\n\t\tthis.healthProbe = healthProbe;\n\t}\n\n\tpublic AppAdmin getAppAdmin() {\n\t\treturn appAdmin;\n\t}\n\n\tpublic void setAppAdmin(AppAdmin appAdmin) {\n\t\tthis.appAdmin = appAdmin;\n\t}\n\n\tpublic static class HttpProbe {\n\n\t\t/** Path to check as a probe */\n\t\tprivate String path;\n\n\t\tpublic String getPath() {\n\t\t\treturn path;\n\t\t}\n\n\t\tpublic void setPath(String path) {\n\t\t\tthis.path = path;\n\t\t}\n\t}\n\n\tprivate String deduceJavaCommand() {\n\t\tString javaExecutablePath = JAVA_COMMAND;\n\t\tString javaHome = System.getProperty(\"java.home\");\n\t\tif (javaHome != null) {\n\t\t\tFile javaExecutable = new File(javaHome, \"bin\" + File.separator + javaExecutablePath);\n\t\t\tAssert.isTrue(javaExecutable.canExecute(),\n\t\t\t\t\t\"Java executable'\" + javaExecutable + \"'discovered via 'java.home' system property '\" + javaHome\n\t\t\t\t\t\t\t+ \"' is not executable or does not exist.\");\n\t\t\tjavaExecutablePath = javaExecutable.getAbsolutePath();\n\t\t}\n\t\telse {\n\t\t\tlogger.warn(\"System property 'java.home' is not set. Defaulting to the java executable path as \"\n\t\t\t\t\t+ JAVA_COMMAND + \" assuming it's in PATH.\");\n\t\t}\n\n\t\treturn javaExecutablePath;\n\t}\n\n\t@Override\n\tpublic String toString() {\n\t\treturn new ToStringCreator(this).append(\"workingDirectoriesRoot\", this.workingDirectoriesRoot)\n\t\t\t\t.append(\"javaOpts\", this.javaOpts).append(\"envVarsToInherit\", this.envVarsToInherit).toString();\n\t}\n\n\t@Override\n\tpublic int hashCode() {\n\t\tfinal int prime = 31;\n\t\tint result = 1;\n\t\tresult = prime * result + ((debugPort == null) ? 0 : debugPort.hashCode());\n\t\tresult = prime * result + ((debugSuspend == null) ? 0 : debugSuspend.hashCode());\n\t\tresult = prime * result + (deleteFilesOnExit ? 1231 : 1237);\n\t\tresult = prime * result + ((docker == null) ? 0 : docker.hashCode());\n\t\tresult = prime * result + Arrays.hashCode(envVarsToInherit);\n\t\tresult = prime * result + (inheritLogging ? 1231 : 1237);\n\t\tresult = prime * result + ((javaCmd == null) ? 0 : javaCmd.hashCode());\n\t\tresult = prime * result + ((javaOpts == null) ? 0 : javaOpts.hashCode());\n\t\tresult = prime * result + maximumConcurrentTasks;\n\t\tresult = prime * result + ((portRange == null) ? 0 : portRange.hashCode());\n\t\tresult = prime * result + shutdownTimeout;\n\t\tresult = prime * result + (useSpringApplicationJson ? 1231 : 1237);\n\t\tresult = prime * result + ((workingDirectoriesRoot == null) ? 0 : workingDirectoriesRoot.hashCode());\n\t\treturn result;\n\t}\n\n\t@Override\n\tpublic boolean equals(Object obj) {\n\t\tif (this == obj) {\n\t\t\treturn true;\n\t\t}\n\t\tif (obj == null) {\n\t\t\treturn false;\n\t\t}\n\t\tif (getClass() != obj.getClass()) {\n\t\t\treturn false;\n\t\t}\n\t\tLocalDeployerProperties other = (LocalDeployerProperties) obj;\n\t\tif (debugPort == null) {\n\t\t\tif (other.debugPort != null) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\t\telse if (!debugPort.equals(other.debugPort)) {\n\t\t\treturn false;\n\t\t}\n\t\tif (debugAddress == null) {\n\t\t\tif (other.debugAddress != null) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\t\telse if (!debugAddress.equals(other.debugAddress)) {\n\t\t\treturn false;\n\t\t}\n\t\tif (debugSuspend == null) {\n\t\t\tif (other.debugSuspend != null) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\t\telse if (!debugSuspend.equals(other.debugSuspend)) {\n\t\t\treturn false;\n\t\t}\n\t\tif (deleteFilesOnExit != other.deleteFilesOnExit) {\n\t\t\treturn false;\n\t\t}\n\t\tif (docker == null) {\n\t\t\tif (other.docker != null) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\t\telse if (!docker.equals(other.docker)) {\n\t\t\treturn false;\n\t\t}\n\t\tif (!Arrays.equals(envVarsToInherit, other.envVarsToInherit)) {\n\t\t\treturn false;\n\t\t}\n\t\tif (inheritLogging != other.inheritLogging) {\n\t\t\treturn false;\n\t\t}\n\t\tif (javaCmd == null) {\n\t\t\tif (other.javaCmd != null) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\t\telse if (!javaCmd.equals(other.javaCmd)) {\n\t\t\treturn false;\n\t\t}\n\t\tif (javaOpts == null) {\n\t\t\tif (other.javaOpts != null) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\t\telse if (!javaOpts.equals(other.javaOpts)) {\n\t\t\treturn false;\n\t\t}\n\t\tif (maximumConcurrentTasks != other.maximumConcurrentTasks) {\n\t\t\treturn false;\n\t\t}\n\t\tif (portRange == null) {\n\t\t\tif (other.portRange != null) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\t\telse if (!portRange.equals(other.portRange)) {\n\t\t\treturn false;\n\t\t}\n\t\tif (shutdownTimeout != other.shutdownTimeout) {\n\t\t\treturn false;\n\t\t}\n\t\tif (useSpringApplicationJson != other.useSpringApplicationJson) {\n\t\t\treturn false;\n\t\t}\n\t\tif (workingDirectoriesRoot == null) {\n\t\t\tif (other.workingDirectoriesRoot != null) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\t\telse if (!workingDirectoriesRoot.equals(other.workingDirectoriesRoot)) {\n\t\t\treturn false;\n\t\t}\n\t\treturn true;\n\t}\n}\n"
  },
  {
    "path": "src/main/java/org/springframework/cloud/deployer/spi/local/LocalDeployerUtils.java",
    "content": "/*\n * Copyright 2017-2018 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.local;\n\n/**\n * Deployer utility functions.\n *\n * @author Janne Valkealahti\n * @author Michael Minella\n *\n */\npublic class LocalDeployerUtils {\n\n\t/**\n\t * Checks if jvm is running on windows.\n\t *\n\t * @return true if windows detected\n\t */\n\tprotected static boolean isWindows() {\n\t\tString osName = System.getProperty(\"os.name\");\n\n\t\treturn osName != null && osName.toLowerCase(Locale.ROOT).startsWith(\"windows\");\n\t}\n}\n"
  },
  {
    "path": "src/main/java/org/springframework/cloud/deployer/spi/local/LocalTaskLauncher.java",
    "content": "/*\n * Copyright 2016-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.local;\n\nimport java.io.File;\nimport java.io.FileInputStream;\nimport java.io.IOException;\nimport java.io.InputStreamReader;\nimport java.net.HttpURLConnection;\nimport java.net.Inet4Address;\nimport java.net.URL;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.UUID;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.concurrent.CopyOnWriteArrayList;\n\nimport jakarta.annotation.PreDestroy;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport org.springframework.cloud.deployer.spi.core.AppDeploymentRequest;\nimport org.springframework.cloud.deployer.spi.core.RuntimeEnvironmentInfo;\nimport org.springframework.cloud.deployer.spi.task.LaunchState;\nimport org.springframework.cloud.deployer.spi.task.TaskLauncher;\nimport org.springframework.cloud.deployer.spi.task.TaskStatus;\nimport org.springframework.util.FileCopyUtils;\nimport org.springframework.util.StringUtils;\n\n/**\n * A {@link TaskLauncher} implementation that spins off a new JVM process per task launch.\n *\n * @author Eric Bottard\n * @author Marius Bogoevici\n * @author Mark Fisher\n * @author Janne Valkealahti\n * @author Thomas Risberg\n * @author Oleg Zhurakousky\n * @author Michael Minella\n * @author Christian Tzolov\n * @author David Turanski\n * @author Glenn Renfro\n * @author Ben Blinebury\n */\npublic class LocalTaskLauncher extends AbstractLocalDeployerSupport implements TaskLauncher {\n\n\tprivate static final Logger logger = LoggerFactory.getLogger(LocalTaskLauncher.class);\n\n\tprivate static final String JMX_DEFAULT_DOMAIN_KEY = \"spring.jmx.default-domain\";\n\n\tprivate final Map<String, TaskInstance> running = new ConcurrentHashMap<>();\n\n\tprivate final Map<String, CopyOnWriteArrayList<String>> taskInstanceHistory = new ConcurrentHashMap<>();\n\n\t/**\n\t * Instantiates a new local task launcher.\n\t *\n\t * @param properties the properties\n\t */\n\tpublic LocalTaskLauncher(LocalDeployerProperties properties) {\n\t\tsuper(properties);\n\t}\n\n\t@Override\n\tpublic String launch(AppDeploymentRequest request) {\n\n\t\tif (this.maxConcurrentExecutionsReached()) {\n\t\t\tthrow new IllegalStateException(\n\t\t\t\tString.format(\"Cannot launch task %s. The maximum concurrent task executions is at its limit [%d].\",\n\t\t\t\t\trequest.getDefinition().getName(), this.getMaximumConcurrentTasks())\n\t\t\t);\n\t\t}\n\n\t\tString taskLaunchId = request.getDefinition().getName() + \"-\" + UUID.randomUUID().toString();\n\n\t\tpruneTaskInstanceHistory(request.getDefinition().getName(), taskLaunchId);\n\n\t\tHashMap<String, String> args = new HashMap<>();\n\t\targs.putAll(request.getDefinition().getProperties());\n\t\targs.put(JMX_DEFAULT_DOMAIN_KEY, taskLaunchId);\n\t\targs.put(\"endpoints.shutdown.enabled\", \"true\");\n\t\targs.put(\"endpoints.jmx.unique-names\", \"true\");\n\n\t\ttry {\n\t\t\tPath workingDirectory = createWorkingDirectory(request.getDeploymentProperties(), taskLaunchId);\n\n\t\t\tboolean useDynamicPort = isDynamicPort(request);\n\n\t\t\tint port = calcServerPort(request, useDynamicPort, args);\n\n\t\t\tProcessBuilder builder = buildProcessBuilder(request, args, Optional.empty(), taskLaunchId).inheritIO();\n\n\t\t\tTaskInstance instance = new TaskInstance(builder, workingDirectory, port);\n\t\t\tif (this.shouldInheritLogging(request)) {\n\t\t\t\tinstance.start(builder);\n\t\t\t\tlogger.info(\"launching task {}\\n    Logs will be inherited.\", taskLaunchId);\n\n\t\t\t}\n\t\t\telse {\n\t\t\t\tinstance.start(builder, getLocalDeployerProperties().isDeleteFilesOnExit());\n\t\t\t\tlogger.info(\"launching task {}\\n   Logs will be in {}\", taskLaunchId, workingDirectory);\n\t\t\t}\n\t\t\trunning.put(taskLaunchId, instance);\n\t\t}\n\t\tcatch (IOException e) {\n\t\t\tthrow new RuntimeException(\"Exception trying to launch \" + request, e);\n\t\t}\n\n\t\treturn taskLaunchId;\n\t}\n\n\tprivate void pruneTaskInstanceHistory(String taskDefinitionName, String taskLaunchId) {\n\t\tCopyOnWriteArrayList<String> oldTaskInstanceIds = taskInstanceHistory.get(taskDefinitionName);\n\t\tif (oldTaskInstanceIds == null) {\n\t\t\toldTaskInstanceIds = new CopyOnWriteArrayList<>();\n\t\t\ttaskInstanceHistory.put(taskDefinitionName, oldTaskInstanceIds);\n\t\t}\n\n\t\tfor (String oldTaskInstanceId : oldTaskInstanceIds) {\n\t\t\tTaskInstance oldTaskInstance = running.get(oldTaskInstanceId);\n\t\t\tif (oldTaskInstance != null && oldTaskInstance.getState() != LaunchState.running\n\t\t\t\t\t&& oldTaskInstance.getState() != LaunchState.launching) {\n\t\t\t\trunning.remove(oldTaskInstanceId);\n\t\t\t\toldTaskInstanceIds.remove(oldTaskInstanceId);\n\t\t\t} else {\n\t\t\t\toldTaskInstanceIds.remove(oldTaskInstanceId);\n\t\t\t}\n\t\t}\n\t\toldTaskInstanceIds.add(taskLaunchId);\n\t}\n\n\tprivate boolean isDynamicPort(AppDeploymentRequest request) {\n\t\tboolean isServerPortKeyonArgs = isServerPortKeyPresentOnArgs(request) != null;\n\t\treturn !request.getDefinition().getProperties().containsKey(SERVER_PORT_KEY)\n\t\t\t\t&& !isServerPortKeyonArgs;\n\t}\n\n\t@Override\n\tpublic void cancel(String id) {\n\t\tTaskInstance instance = running.get(id);\n\t\tif (instance != null) {\n\t\t\tinstance.cancelled = true;\n\t\t\tif (isAlive(instance.getProcess())) {\n\t\t\t\tshutdownAndWait(instance);\n\t\t\t}\n\t\t}\n\t}\n\n\t@Override\n\tpublic TaskStatus status(String id) {\n\t\tTaskInstance instance = running.get(id);\n\t\tif (instance != null) {\n\t\t\treturn new TaskStatus(id, instance.getState(), instance.getAttributes());\n\t\t}\n\t\treturn new TaskStatus(id, LaunchState.unknown, null);\n\t}\n\n\t@Override\n\tpublic String getLog(String id) {\n\t\tTaskInstance instance = running.get(id);\n\t\tif (instance != null) {\n\t\t\tStringBuilder stringBuilder = new StringBuilder();\n\t\t\tString stderr = instance.getStdErr();\n\t\t\tif (StringUtils.hasText(stderr)) {\n\t\t\t\tstringBuilder.append(\"stderr:\\n\");\n\t\t\t\tstringBuilder.append(stderr);\n\t\t\t}\n\t\t\tString stdout = instance.getStdOut();\n\t\t\tif (StringUtils.hasText(stdout)) {\n\t\t\t\tstringBuilder.append(\"stdout:\\n\");\n\t\t\t\tstringBuilder.append(stdout);\n\t\t\t}\n\t\t\treturn stringBuilder.toString();\n\t\t}\n\t\telse {\n\t\t\treturn \"Log could not be retrieved as the task instance is not running.\";\n\t\t}\n\t}\n\n\t@Override\n\tpublic void cleanup(String id) {\n\t}\n\n\t@Override\n\tpublic void destroy(String appName) {\n\t}\n\n\t@Override\n\tpublic RuntimeEnvironmentInfo environmentInfo() {\n\t\treturn super.createRuntimeEnvironmentInfo(TaskLauncher.class, this.getClass());\n\t}\n\n\t@Override\n\tpublic int getMaximumConcurrentTasks() {\n\t\treturn getLocalDeployerProperties().getMaximumConcurrentTasks();\n\t}\n\n\n\t@Override\n\tpublic int getRunningTaskExecutionCount() {\n\t\tint runningExecutionCount = 0;\n\n\t\tfor (TaskInstance taskInstance: running.values()) {\n\t\t\tif (taskInstance.getProcess().isAlive()) {\n\t\t\t\trunningExecutionCount++;\n\t\t\t}\n\t\t}\n\t\treturn runningExecutionCount;\n\t}\n\n\tprivate synchronized boolean maxConcurrentExecutionsReached() {\n\t\treturn getRunningTaskExecutionCount() >= getMaximumConcurrentTasks();\n\t}\n\n\t@PreDestroy\n\tpublic void shutdown() throws Exception {\n\t\tfor (String taskLaunchId : running.keySet()) {\n\t\t\tcancel(taskLaunchId);\n\t\t}\n\t\ttaskInstanceHistory.clear();\n\t}\n\n\tprivate Path createWorkingDirectory(Map<String, String> deploymentProperties, String taskLaunchId) throws IOException {\n\t\tLocalDeployerProperties localDeployerPropertiesToUse = bindDeploymentProperties(deploymentProperties);\n\n\t\tPath workingDirectoryRoot = Files.isSymbolicLink(localDeployerPropertiesToUse.getWorkingDirectoriesRoot())\n\t\t\t\t? Files.readSymbolicLink(localDeployerPropertiesToUse.getWorkingDirectoriesRoot())\n\t\t\t\t: Files.createDirectories(localDeployerPropertiesToUse.getWorkingDirectoriesRoot());\n\n\t\tPath workingDirectory = Files.createDirectories(workingDirectoryRoot.resolve(Long.toString(System.nanoTime())).resolve(taskLaunchId));\n\n\t\tif (localDeployerPropertiesToUse.isDeleteFilesOnExit()) {\n\t\t\tworkingDirectory.toFile().deleteOnExit();\n\t\t}\n\n\t\treturn workingDirectory;\n\t}\n\n\tprivate static class TaskInstance implements Instance {\n\n\t\tprivate Process process;\n\n\t\tprivate final Path workDir;\n\n\t\tprivate File stdout;\n\n\t\tprivate File stderr;\n\n\t\tprivate final URL baseUrl;\n\n\t\tprivate boolean cancelled;\n\n\t\tprivate TaskInstance(ProcessBuilder builder, Path workDir, int port) throws IOException {\n\t\t\tbuilder.directory(workDir.toFile());\n\t\t\tthis.workDir = workDir;\n\t\t\tthis.baseUrl = new URL(\"http\", Inet4Address.getLocalHost().getHostAddress(), port, \"\");\n\t\t\tif (logger.isDebugEnabled()) {\n\t\t\t\tlogger.debug(\"Local Task Launcher Commands: \" + String.join(\",\", builder.command())\n\t\t\t\t\t\t+ \", Environment: \" + builder.environment());\n\t\t\t}\n\t\t}\n\n\t\t@Override\n\t\tpublic URL getBaseUrl() {\n\t\t\treturn this.baseUrl;\n\t\t}\n\n\t\t@Override\n\t\tpublic Process getProcess() {\n\t\t\treturn this.process;\n\t\t}\n\n\t\tpublic LaunchState getState() {\n\t\t\tif (cancelled) {\n\t\t\t\treturn LaunchState.cancelled;\n\t\t\t}\n\t\t\tInteger exit = getProcessExitValue(process);\n\t\t\t// TODO: consider using exit code mapper concept from batch\n\t\t\tif (exit != null) {\n\t\t\t\tif (exit == 0) {\n\t\t\t\t\treturn LaunchState.complete;\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\treturn LaunchState.failed;\n\t\t\t\t}\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tHttpURLConnection urlConnection = (HttpURLConnection) baseUrl.openConnection();\n\t\t\t\turlConnection.setConnectTimeout(100);\n\t\t\t\turlConnection.connect();\n\t\t\t\turlConnection.disconnect();\n\t\t\t\treturn LaunchState.running;\n\t\t\t}\n\t\t\tcatch (IOException e) {\n\t\t\t\treturn LaunchState.launching;\n\t\t\t}\n\t\t}\n\n\t\tpublic String getStdOut() {\n\t\t\ttry {\n\t\t\t\treturn FileCopyUtils.copyToString(new InputStreamReader(new FileInputStream(this.stdout)));\n\t\t\t}\n\t\t\tcatch (IOException e) {\n\t\t\t\treturn \"Log retrieval returned \" + e.getMessage();\n\t\t\t}\n\t\t}\n\n\t\tpublic String getStdErr() {\n\t\t\ttry {\n\t\t\t\treturn FileCopyUtils.copyToString(new InputStreamReader(new FileInputStream(this.stderr)));\n\t\t\t}\n\t\t\tcatch (IOException e) {\n\t\t\t\treturn \"Log retrieval returned \" + e.getMessage();\n\t\t\t}\n\t\t}\n\n\t\t/**\n\t\t * Will start the process while redirecting 'out' and 'err' streams to the 'out' and 'err'\n\t\t * streams of this process.\n\t\t */\n\t\tprivate void start(ProcessBuilder builder) throws IOException {\n\t\t\tif (logger.isDebugEnabled()) {\n\t\t\t\tlogger.debug(\"Local Task Launcher Commands: \" + String.join(\",\", builder.command())\n\t\t\t\t\t\t+ \", Environment: \" + builder.environment());\n\t\t\t}\n\t\t\tthis.process = builder.start();\n\t\t}\n\n\t\tprivate void start(ProcessBuilder builder, boolean deleteOnExit) throws IOException {\n\t\t\tString workDirPath = workDir.toFile().getAbsolutePath();\n\t\t\tthis.stdout = Files.createFile(Paths.get(workDirPath, \"stdout.log\")).toFile();\n\t\t\tthis.stderr = Files.createFile(Paths.get(workDirPath, \"stderr.log\")).toFile();\n\t\t\tbuilder.redirectOutput(this.stdout);\n\t\t\tbuilder.redirectError(this.stderr);\n\t\t\tthis.process = builder.start();\n\t\t\tif(deleteOnExit) {\n\t\t\t\tthis.stdout.deleteOnExit();\n\t\t\t\tthis.stderr.deleteOnExit();\n\t\t\t}\n\t\t}\n\n\t\tprivate Map<String, String> getAttributes() {\n\t\t\tHashMap<String, String> result = new HashMap<>();\n\t\t\tresult.put(\"working.dir\", workDir.toFile().getAbsolutePath());\n\t\t\tif(this.stdout != null) {\n\t\t\t\tresult.put(\"stdout\", stdout.getAbsolutePath());\n\t\t\t}\n\t\t\tif(this.stderr != null) {\n\t\t\t\tresult.put(\"stderr\", stderr.getAbsolutePath());\n\t\t\t}\n\t\t\tresult.put(\"url\", baseUrl.toString());\n\t\t\treturn result;\n\t\t}\n\t}\n\n\t/**\n\t * Returns the process exit value. We explicitly use Integer instead of int\n\t * to indicate that if {@code NULL} is returned, the process is still running.\n\t *\n\t * @param process the process\n\t * @return the process exit value or {@code NULL} if process is still alive\n\t */\n\tprivate static Integer getProcessExitValue(Process process) {\n\t\ttry {\n\t\t\treturn process.exitValue();\n\t\t}\n\t\tcatch (IllegalThreadStateException e) {\n\t\t\t// process is still alive\n\t\t\treturn null;\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "src/main/resources/META-INF/additional-spring-configuration-metadata.json",
    "content": "{\n  \"hints\": [\n    {\n      \"name\": \"spring.cloud.deployer.local.debug-suspend\",\n      \"values\": [\n        {\n          \"value\": \"y\"\n        },\n        {\n          \"value\": \"n\"\n        }\n      ]\n    }\n  ]\n}\n"
  },
  {
    "path": "src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports",
    "content": "org.springframework.cloud.deployer.spi.local.LocalDeployerAutoConfiguration\n"
  },
  {
    "path": "src/main/resources/META-INF/spring.factories",
    "content": ""
  },
  {
    "path": "src/scripts/next-minor-parent-snapshot-version",
    "content": "#!/bin/sh\n\n# ----------------------------------------------------------------------------\n# Script maintaining versions\n#\n# Bump up next minor development version for parents.\n# ----------------------------------------------------------------------------\n\nfind_basedir() {\n  local basedir=$(cd -P -- \"$(dirname -- \"$0\")\" && cd .. && cd .. && pwd -P)\n  echo \"${basedir}\"\n}\n\nexport PROJECTBASEDIR=$(find_basedir)\n\n(cd $PROJECTBASEDIR && ./mvnw build-helper:parse-version versions:update-parent -DgenerateBackupPoms=false -DallowSnapshots=true -DparentVersion='${parsedVersion.majorVersion}.${parsedVersion.nextMinorVersion}')\n\n"
  },
  {
    "path": "src/scripts/next-minor-snapshot-version",
    "content": "#!/bin/sh\n\n# ----------------------------------------------------------------------------\n# Script maintaining versions\n#\n# Bump up next minor development version.\n# ----------------------------------------------------------------------------\n\nfind_basedir() {\n  local basedir=$(cd -P -- \"$(dirname -- \"$0\")\" && cd .. && cd .. && pwd -P)\n  echo \"${basedir}\"\n}\n\nexport PROJECTBASEDIR=$(find_basedir)\n\n(cd $PROJECTBASEDIR && ./mvnw build-helper:parse-version versions:set -DprocessAllModules=false -DgenerateBackupPoms=false -DnewVersion='${parsedVersion.majorVersion}.${parsedVersion.nextMinorVersion}.0-SNAPSHOT' && ./mvnw -pl spring-cloud-deployer-dependencies build-helper:parse-version versions:set -DprocessAllModules=false -DgenerateBackupPoms=false -DnewVersion='${parsedVersion.majorVersion}.${parsedVersion.nextMinorVersion}.0-SNAPSHOT')\n\n"
  },
  {
    "path": "src/scripts/next-parent-snapshot-version",
    "content": "#!/bin/sh\n\n# ----------------------------------------------------------------------------\n# Script maintaining versions\n#\n# Bump up next build development version for parents.\n# ----------------------------------------------------------------------------\n\nfind_basedir() {\n  local basedir=$(cd -P -- \"$(dirname -- \"$0\")\" && cd .. && cd .. && pwd -P)\n  echo \"${basedir}\"\n}\n\nexport PROJECTBASEDIR=$(find_basedir)\n\n(cd $PROJECTBASEDIR && ./mvnw build-helper:parse-version versions:update-parent -DgenerateBackupPoms=false -DallowSnapshots=true -DparentVersion='[${parsedVersion.majorVersion}.${parsedVersion.minorVersion},${parsedVersion.majorVersion}.${parsedVersion.nextMinorVersion})')\n"
  },
  {
    "path": "src/scripts/next-snapshot-version",
    "content": "#!/bin/sh\n\n# ----------------------------------------------------------------------------\n# Script maintaining versions\n#\n# Bump up next build development version.\n# ----------------------------------------------------------------------------\n\nfind_basedir() {\n  local basedir=$(cd -P -- \"$(dirname -- \"$0\")\" && cd .. && cd .. && pwd -P)\n  echo \"${basedir}\"\n}\n\nexport PROJECTBASEDIR=$(find_basedir)\n\n(cd $PROJECTBASEDIR && ./mvnw build-helper:parse-version versions:set -DprocessAllModules=false -DgenerateBackupPoms=false -DnewVersion='${parsedVersion.majorVersion}.${parsedVersion.minorVersion}.${parsedVersion.nextIncrementalVersion}-SNAPSHOT' && ./mvnw -pl spring-cloud-deployer-dependencies build-helper:parse-version versions:set -DprocessAllModules=false -DgenerateBackupPoms=false -DnewVersion='${parsedVersion.majorVersion}.${parsedVersion.minorVersion}.${parsedVersion.nextIncrementalVersion}-SNAPSHOT')\n\n"
  },
  {
    "path": "src/test/java/org/springframework/cloud/deployer/spi/kubernetes/DefaultContainerFactoryTests.java",
    "content": "/*\n * Copyright 2016-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.kubernetes;\n\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.UUID;\nimport java.util.stream.Collectors;\nimport java.util.stream.Stream;\n\nimport com.fasterxml.jackson.core.JsonProcessingException;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport io.fabric8.kubernetes.api.model.Container;\nimport io.fabric8.kubernetes.api.model.ContainerPort;\nimport io.fabric8.kubernetes.api.model.ContainerPortBuilder;\nimport io.fabric8.kubernetes.api.model.EnvVar;\nimport io.fabric8.kubernetes.api.model.HTTPHeader;\nimport io.fabric8.kubernetes.api.model.ObjectMeta;\nimport io.fabric8.kubernetes.api.model.Probe;\nimport io.fabric8.kubernetes.api.model.Secret;\nimport io.fabric8.kubernetes.api.model.VolumeMount;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\n\nimport org.springframework.boot.test.context.SpringBootTest;\nimport org.springframework.cloud.deployer.resource.docker.DockerResource;\nimport org.springframework.cloud.deployer.spi.core.AppDefinition;\nimport org.springframework.cloud.deployer.spi.core.AppDeploymentRequest;\nimport org.springframework.core.io.Resource;\nimport org.springframework.test.context.junit.jupiter.SpringExtension;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.assertThatThrownBy;\n\n/**\n * Unit tests for {@link DefaultContainerFactory}.\n *\n * @author Will Kennedy\n * @author Donovan Muller\n * @author Chris Schaefer\n * @author David Turanski\n * @author Ilayaperumal Gopinathan\n * @author Glenn Renfro\n */\n@ExtendWith(SpringExtension.class)\n@SpringBootTest(classes = {KubernetesAutoConfiguration.class})\npublic class DefaultContainerFactoryTests {\n\n    @Test\n    public void create() {\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n        DefaultContainerFactory defaultContainerFactory = new DefaultContainerFactory(\n                kubernetesDeployerProperties);\n\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        Resource resource = getResource();\n        Map<String, String> props = new HashMap<>();\n        props.put(\"spring.cloud.deployer.kubernetes.limits.memory\", \"128Mi\");\n        props.put(\"spring.cloud.deployer.kubernetes.environment-variables\",\n                \"JAVA_OPTIONS=-Xmx64m,KUBERNETES_NAMESPACE=test-space\");\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition,\n                resource, props);\n\n        ContainerConfiguration containerConfiguration = new ContainerConfiguration(\"app-test\", appDeploymentRequest);\n        Container container = defaultContainerFactory.create(containerConfiguration);\n        assertThat(container).isNotNull();\n        assertThat(container.getEnv().size()).isEqualTo(3);\n        EnvVar envVar1 = container.getEnv().get(0);\n        EnvVar envVar2 = container.getEnv().get(1);\n        assertThat(envVar1.getName()).isEqualTo(\"JAVA_OPTIONS\");\n        assertThat(envVar1.getValue()).isEqualTo(\"-Xmx64m\");\n        assertThat(envVar2.getName()).isEqualTo(\"KUBERNETES_NAMESPACE\");\n        assertThat(envVar2.getValue()).isEqualTo(\"test-space\");\n    }\n\n    @Test\n    public void createWithContainerCommand() {\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n        DefaultContainerFactory defaultContainerFactory = new DefaultContainerFactory(\n                kubernetesDeployerProperties);\n\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        Resource resource = getResource();\n        Map<String, String> props = new HashMap<>();\n        props.put(\"spring.cloud.deployer.kubernetes.containerCommand\",\n                \"echo arg1 'arg2'\");\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition,\n                resource, props);\n\n        ContainerConfiguration containerConfiguration = new ContainerConfiguration(\"app-test\", appDeploymentRequest);\n        Container container = defaultContainerFactory.create(containerConfiguration);\n        assertThat(container).isNotNull();\n        assertThat(container.getCommand()).containsExactly(\"echo\", \"arg1\", \"arg2\");\n    }\n\n    @Test\n    public void createWithPorts() {\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n        DefaultContainerFactory defaultContainerFactory = new DefaultContainerFactory(\n                kubernetesDeployerProperties);\n\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        Resource resource = getResource();\n        Map<String, String> props = new HashMap<>();\n        props.put(\"spring.cloud.deployer.kubernetes.containerPorts\",\n                \"8081, 8082, 65535\");\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition,\n                resource, props);\n\n        ContainerConfiguration containerConfiguration = new ContainerConfiguration(\"app-test\", appDeploymentRequest);\n        Container container = defaultContainerFactory.create(containerConfiguration);\n        assertThat(container).isNotNull();\n        List<ContainerPort> containerPorts = container.getPorts();\n        assertThat(containerPorts).isNotNull();\n        assertThat(containerPorts.size()).isEqualTo(3);\n        assertThat(containerPorts.get(0).getContainerPort()).isEqualTo(8081);\n        assertThat(containerPorts.get(1).getContainerPort()).isEqualTo(8082);\n        assertThat(containerPorts.get(2).getContainerPort()).isEqualTo(65535);\n    }\n\n    @Test\n    public void createWithInvalidPort() {\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n        DefaultContainerFactory defaultContainerFactory = new DefaultContainerFactory(\n                kubernetesDeployerProperties);\n\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        Resource resource = getResource();\n        Map<String, String> props = new HashMap<>();\n        props.put(\"spring.cloud.deployer.kubernetes.containerPorts\",\n                \"8081, 8082, invalid, 9212\");\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition,\n                resource, props);\n\n        //Attempting to create with an invalid integer set for a port should cause an exception to bubble up.\n        ContainerConfiguration containerConfiguration = new ContainerConfiguration(\"app-test\", appDeploymentRequest);\n        assertThatThrownBy(() -> {\n            defaultContainerFactory.create(containerConfiguration);\n        }).isInstanceOf(NumberFormatException.class);\n    }\n\n    @Test\n    public void createWithPortAndHostNetwork() {\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n        DefaultContainerFactory defaultContainerFactory = new DefaultContainerFactory(\n                kubernetesDeployerProperties);\n\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        Resource resource = getResource();\n        Map<String, String> props = new HashMap<>();\n        props.put(\"spring.cloud.deployer.kubernetes.containerPorts\",\n                \"8081, 8082, 65535\");\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition,\n                resource, props);\n\n        ContainerConfiguration containerConfiguration = new ContainerConfiguration(\"app-test\", appDeploymentRequest)\n                .withHostNetwork(true);\n        Container container = defaultContainerFactory.create(containerConfiguration);\n        assertThat(container).isNotNull();\n        List<ContainerPort> containerPorts = container.getPorts();\n        assertThat(containerPorts).isNotNull();\n        assertThat(containerPorts.size()).isEqualTo(3);\n        assertThat(containerPorts.get(0).getContainerPort()).isEqualTo(8081);\n        assertThat(containerPorts.get(0).getHostPort()).isEqualTo(8081);\n        assertThat(containerPorts.get(1).getContainerPort()).isEqualTo(8082);\n        assertThat(containerPorts.get(1).getHostPort()).isEqualTo(8082);\n        assertThat(containerPorts.get(2).getContainerPort()).isEqualTo(65535);\n        assertThat(containerPorts.get(2).getHostPort()).isEqualTo(65535);\n    }\n\n    @Test\n    public void createWithEntryPointStyle() throws JsonProcessingException {\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n        DefaultContainerFactory defaultContainerFactory = new DefaultContainerFactory(\n                kubernetesDeployerProperties);\n\n        Map<String, String> appProps = new HashMap<>();\n        appProps.put(\"foo.bar.baz\", \"test\");\n        AppDefinition definition = new AppDefinition(\"app-test\", appProps);\n        Resource resource = getResource();\n        Map<String, String> props = new HashMap<>();\n\n        props.put(\"spring.cloud.deployer.kubernetes.entryPointStyle\", \"shell\");\n        AppDeploymentRequest appDeploymentRequestShell = new AppDeploymentRequest(definition,\n                resource, props);\n        ContainerConfiguration shellContainerConfiguration = new ContainerConfiguration(\"app-test\",\n                appDeploymentRequestShell);\n        Container containerShell = defaultContainerFactory.create(shellContainerConfiguration);\n        assertThat(containerShell).isNotNull();\n\n        assertThat(containerShell.getEnv().get(0).getName()).isEqualTo(\"FOO_BAR_BAZ\");\n        assertThat(containerShell.getArgs()).isEmpty();\n\n        List<String> cmdLineArgs = new ArrayList<>();\n        cmdLineArgs.add(\"--foo.bar=value1\");\n        cmdLineArgs.add(\"--spring.cloud.task.executionid=1\");\n        cmdLineArgs.add(\"--spring.cloud.data.flow.platformname=platform1\");\n        cmdLineArgs.add(\"--spring.cloud.data.flow.taskappname==a1\");\n        cmdLineArgs.add(\"blah=chacha\");\n        appDeploymentRequestShell = new AppDeploymentRequest(definition,\n                resource, props, cmdLineArgs);\n        shellContainerConfiguration = new ContainerConfiguration(\"app-test\",\n                appDeploymentRequestShell);\n        containerShell = defaultContainerFactory.create(shellContainerConfiguration);\n        assertThat(containerShell).isNotNull();\n        assertThat(containerShell.getEnv()).hasSize(7);\n        assertThat(containerShell.getArgs()).isEmpty();\n        String envVarString = containerShell.getEnv().toString();\n        assertThat(envVarString.contains(\"name=FOO_BAR_BAZ, value=test\")).isTrue();\n        assertThat(envVarString.contains(\"name=FOO_BAR, value=value1\")).isTrue();\n        assertThat(envVarString.contains(\"name=SPRING_CLOUD_TASK_EXECUTIONID, value=1\")).isTrue();\n        assertThat(envVarString.contains(\"name=SPRING_CLOUD_DATA_FLOW_TASKAPPNAME, value==a1\")).isTrue();\n        assertThat(envVarString.contains(\"name=SPRING_CLOUD_DATA_FLOW_PLATFORMNAME, value=platform1\")).isTrue();\n        assertThat(envVarString.contains(\"name=BLAH, value=chacha\")).isTrue();\n\n        props.put(\"spring.cloud.deployer.kubernetes.entryPointStyle\", \"exec\");\n        AppDeploymentRequest appDeploymentRequestExec = new AppDeploymentRequest(definition,\n                resource, props);\n        ContainerConfiguration execContainerConfiguration = new ContainerConfiguration(\"app-test\",\n                appDeploymentRequestExec);\n        Container containerExec = defaultContainerFactory.create(execContainerConfiguration);\n        assertThat(containerExec).isNotNull();\n        assertThat(containerExec.getEnv()).hasSize(1);\n        assertThat(containerExec.getArgs().get(0)).isEqualTo(\"--foo.bar.baz=test\");\n\n        props.put(\"spring.cloud.deployer.kubernetes.entryPointStyle\", \"boot\");\n        AppDeploymentRequest appDeploymentRequestBoot = new AppDeploymentRequest(definition,\n                resource, props, Arrays.asList(\"--arg1=val1\", \"--arg2=val2\"));\n        ContainerConfiguration bootContainerConfiguration = new ContainerConfiguration(\"app-test\",\n                appDeploymentRequestBoot);\n        Container containerBoot = defaultContainerFactory.create(bootContainerConfiguration);\n        assertThat(containerBoot).isNotNull();\n        assertThat(containerBoot.getEnv().get(0).getName()).isEqualTo(\"SPRING_APPLICATION_JSON\");\n        assertThat(containerBoot.getEnv().get(0).getValue()).isEqualTo(new ObjectMapper().writeValueAsString(appProps));\n        assertThat(containerBoot.getArgs()).hasSize(2);\n        assertThat(containerBoot.getArgs().get(0)).isEqualTo(\"--arg1=val1\");\n        assertThat(containerBoot.getArgs().get(1)).isEqualTo(\"--arg2=val2\");\n    }\n\n    @Test\n    public void createWithVolumeMounts() {\n        // test volume mounts defined as deployer properties\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n        DefaultContainerFactory defaultContainerFactory = new DefaultContainerFactory(kubernetesDeployerProperties);\n\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        Resource resource = getResource();\n        Map<String, String> props = new HashMap<>();\n        props.put(\"spring.cloud.deployer.kubernetes.volumeMounts\",\n                \"[\"\n                        + \"{name: 'testhostpath', mountPath: '/test/hostPath'}, \"\n                        + \"{name: 'testpvc', mountPath: '/test/pvc', readOnly: 'true'}, \"\n                        + \"{name: 'testnfs', mountPath: '/test/nfs'}\"\n                        + \"]\");\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, resource, props);\n\n        ContainerConfiguration containerConfiguration = new ContainerConfiguration(\"app-test\", appDeploymentRequest);\n        Container container = defaultContainerFactory.create(containerConfiguration);\n\n        assertThat(container.getVolumeMounts()).containsOnly(\n                new VolumeMount(\"/test/hostPath\", null, \"testhostpath\", null, null, null),\n                new VolumeMount(\"/test/pvc\", null, \"testpvc\", true, null, null),\n                new VolumeMount(\"/test/nfs\", null, \"testnfs\", null, null, null));\n\n        // test volume mounts defined as app deployment property, overriding the deployer property\n        kubernetesDeployerProperties = new KubernetesDeployerProperties();\n        kubernetesDeployerProperties\n                .setVolumeMounts(Stream.of(\n                                new VolumeMount(\"/test/hostPath\", null, \"testhostpath\", false, null, null),\n                                new VolumeMount(\"/test/pvc\", null, \"testpvc\", true, null, null),\n                                new VolumeMount(\"/test/nfs\", null, \"testnfs\", false, null, null))\n                        .collect(Collectors.toList()));\n        defaultContainerFactory = new DefaultContainerFactory(kubernetesDeployerProperties);\n\n        props.clear();\n        props.put(\"spring.cloud.deployer.kubernetes.volumeMounts\",\n                \"[\"\n                        + \"{name: 'testpvc', mountPath: '/test/pvc/overridden'}, \"\n                        + \"{name: 'testnfs', mountPath: '/test/nfs/overridden', readOnly: 'true'}\"\n                        + \"]\");\n\n        containerConfiguration = new ContainerConfiguration(\"app-test\", appDeploymentRequest);\n        container = defaultContainerFactory.create(containerConfiguration);\n\n        assertThat(container.getVolumeMounts()).containsOnly(\n                new VolumeMount(\"/test/hostPath\", null, \"testhostpath\", false, null, null),\n                new VolumeMount(\"/test/pvc/overridden\", null, \"testpvc\", null, null, null),\n                new VolumeMount(\"/test/nfs/overridden\", null, \"testnfs\", true, null, null));\n    }\n\n    /**\n     * @deprecated {@see {@link #createCustomLivenessHttpPortFromProperties()}}\n     */\n    @Test\n    @Deprecated\n    public void createCustomLivenessPortFromProperties() {\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n        kubernetesDeployerProperties.setLivenessProbePort(8090);\n        DefaultContainerFactory defaultContainerFactory = new DefaultContainerFactory(\n                kubernetesDeployerProperties);\n\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        Resource resource = getResource();\n        Map<String, String> props = new HashMap<>();\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition,\n                resource, props);\n\n        ContainerConfiguration containerConfiguration = new ContainerConfiguration(\"app-test\", appDeploymentRequest)\n                .withExternalPort(8080)\n                .withHostNetwork(true);\n        Container container = defaultContainerFactory.create(containerConfiguration);\n        assertThat(container).isNotNull();\n\n        List<ContainerPort> containerPorts = container.getPorts();\n        assertThat(containerPorts).isNotNull();\n\n        assertThat(containerPorts).as(\"Only two container ports should be set\").hasSize(2);\n        assertThat((int) containerPorts.get(0).getContainerPort()).isEqualTo(8080);\n        assertThat((int) containerPorts.get(0).getHostPort()).isEqualTo(8080);\n        assertThat((int) containerPorts.get(1).getContainerPort()).isEqualTo(8090);\n        assertThat((int) containerPorts.get(1).getHostPort()).isEqualTo(8090);\n        assertThat((int) container.getLivenessProbe().getHttpGet().getPort().getIntVal()).isEqualTo(8090);\n    }\n\n    @Test\n    public void createCustomLivenessHttpPortFromProperties() {\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n        kubernetesDeployerProperties.setLivenessHttpProbePort(8090);\n        kubernetesDeployerProperties.setStartupHttpProbePort(kubernetesDeployerProperties.getLivenessHttpProbePort());\n        DefaultContainerFactory defaultContainerFactory = new DefaultContainerFactory(\n                kubernetesDeployerProperties);\n\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        Resource resource = getResource();\n        Map<String, String> props = new HashMap<>();\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition,\n                resource, props);\n\n        ContainerConfiguration containerConfiguration = new ContainerConfiguration(\"app-test\", appDeploymentRequest)\n                .withExternalPort(8080)\n                .withHostNetwork(true);\n        Container container = defaultContainerFactory.create(containerConfiguration);\n        assertThat(container).isNotNull();\n\n        List<ContainerPort> containerPorts = container.getPorts();\n        assertThat(containerPorts).isNotNull();\n\n        assertThat(containerPorts).as(\"Only two container ports should be set\").hasSize(2);\n        assertThat((int) containerPorts.get(0).getContainerPort()).isEqualTo(8080);\n        assertThat((int) containerPorts.get(0).getHostPort()).isEqualTo(8080);\n        assertThat((int) containerPorts.get(1).getContainerPort()).isEqualTo(8090);\n        assertThat((int) containerPorts.get(1).getHostPort()).isEqualTo(8090);\n        assertThat((int) container.getLivenessProbe().getHttpGet().getPort().getIntVal()).isEqualTo(8090);\n    }\n\n    @Test\n    public void createCustomLivenessHttpProbeSchemeFromProperties() {\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n        kubernetesDeployerProperties.setLivenessHttpProbeScheme(\"HTTPS\");\n        DefaultContainerFactory defaultContainerFactory = new DefaultContainerFactory(\n                kubernetesDeployerProperties);\n\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        Resource resource = getResource();\n        Map<String, String> props = new HashMap<>();\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition,\n                resource, props);\n\n        ContainerConfiguration containerConfiguration = new ContainerConfiguration(\"app-test\", appDeploymentRequest)\n                .withExternalPort(8080)\n                .withHostNetwork(true);\n        Container container = defaultContainerFactory.create(containerConfiguration);\n        assertThat(container).isNotNull();\n\n        List<ContainerPort> containerPorts = container.getPorts();\n        assertThat(containerPorts).isNotNull();\n\n        assertThat((int) containerPorts.get(0).getContainerPort()).isEqualTo(8080);\n        assertThat((int) containerPorts.get(0).getHostPort()).isEqualTo(8080);\n        assertThat((int) container.getLivenessProbe().getHttpGet().getPort().getIntVal()).isEqualTo(8080);\n        assertThat(container.getLivenessProbe().getHttpGet().getScheme()).isEqualTo(\"HTTPS\");\n    }\n\n    @Test\n    public void createCustomReadinessHttpSchemeFromProperties() {\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n        kubernetesDeployerProperties.setReadinessHttpProbeScheme(\"HTTPS\");\n        DefaultContainerFactory defaultContainerFactory = new DefaultContainerFactory(\n                kubernetesDeployerProperties);\n\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        Resource resource = getResource();\n        Map<String, String> props = new HashMap<>();\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition,\n                resource, props);\n\n        ContainerConfiguration containerConfiguration = new ContainerConfiguration(\"app-test\", appDeploymentRequest)\n                .withHostNetwork(true)\n                .withExternalPort(8080);\n        Container container = defaultContainerFactory.create(containerConfiguration);\n        assertThat(container).isNotNull();\n\n        List<ContainerPort> containerPorts = container.getPorts();\n        assertThat(containerPorts).isNotNull();\n\n        assertThat((int) containerPorts.get(0).getContainerPort()).isEqualTo(8080);\n        assertThat((int) containerPorts.get(0).getHostPort()).isEqualTo(8080);\n        assertThat((int) container.getReadinessProbe().getHttpGet().getPort().getIntVal()).isEqualTo(8080);\n        assertThat(container.getReadinessProbe().getHttpGet().getScheme()).isEqualTo(\"HTTPS\");\n    }\n\n    @Test\n    public void createCustomLivenessAndReadinessHttpProbeSchemeFromAppRequest() {\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n        DefaultContainerFactory defaultContainerFactory = new DefaultContainerFactory(\n                kubernetesDeployerProperties);\n\n        Map<String, String> appProperties = new HashMap<>();\n        appProperties.put(\"spring.cloud.deployer.kubernetes.livenessHttpProbeScheme\", \"HTTPS\");\n        appProperties.put(\"spring.cloud.deployer.kubernetes.readinessHttpProbeScheme\", \"HTTPS\");\n\n        AppDefinition definition = new AppDefinition(\"app-test\", appProperties);\n        Resource resource = getResource();\n\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition,\n                resource, appProperties);\n\n        ContainerConfiguration containerConfiguration = new ContainerConfiguration(\"app-test\", appDeploymentRequest)\n                .withHostNetwork(true)\n                .withExternalPort(8080);\n\n        Container container = defaultContainerFactory.create(containerConfiguration);\n\n        assertThat(container).isNotNull();\n\n        assertThat(container.getReadinessProbe().getHttpGet().getPath()).isNotNull();\n        assertThat(container.getReadinessProbe().getHttpGet().getScheme()).isEqualTo(\"HTTPS\");\n\n        assertThat(container.getLivenessProbe().getHttpGet().getPath()).isNotNull();\n        assertThat(container.getLivenessProbe().getHttpGet().getScheme()).isEqualTo(\"HTTPS\");\n    }\n\n    /**\n     * @deprecated {@see {@link #createCustomLivenessHttpPortFromAppRequest()}}\n     */\n    @Test\n    @Deprecated\n    public void createCustomLivenessPortFromAppRequest() {\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n        DefaultContainerFactory defaultContainerFactory = new DefaultContainerFactory(\n                kubernetesDeployerProperties);\n\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        Resource resource = getResource();\n        Map<String, String> props = new HashMap<>();\n        props.put(\"spring.cloud.deployer.kubernetes.liveness-probe-port\", Integer.toString(8090));\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition,\n                resource, props);\n\n        ContainerConfiguration containerConfiguration = new ContainerConfiguration(\"app-test\", appDeploymentRequest)\n                .withHostNetwork(true)\n                .withExternalPort(8080);\n        Container container = defaultContainerFactory.create(containerConfiguration);\n        assertThat(container).isNotNull();\n\n        List<ContainerPort> containerPorts = container.getPorts();\n        assertThat(containerPorts).isNotNull();\n\n        assertThat(containerPorts).hasSize(2);\n        assertThat(containerPorts.get(0).getContainerPort()).isEqualTo(8080);\n        assertThat(containerPorts.get(0).getHostPort()).isEqualTo(8080);\n        assertThat(containerPorts.get(1).getContainerPort()).isEqualTo(8090);\n        assertThat(containerPorts.get(1).getHostPort()).isEqualTo(8090);\n        assertThat(container.getLivenessProbe().getHttpGet().getPort().getIntVal()).isEqualTo(8090);\n    }\n\n    @Test\n    public void createCustomLivenessHttpPortFromAppRequest() {\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n        DefaultContainerFactory defaultContainerFactory = new DefaultContainerFactory(\n                kubernetesDeployerProperties);\n\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        Resource resource = getResource();\n        Map<String, String> props = new HashMap<>();\n        props.put(\"spring.cloud.deployer.kubernetes.liveness-http-probe-port\", Integer.toString(8090));\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition,\n                resource, props);\n\n        ContainerConfiguration containerConfiguration = new ContainerConfiguration(\"app-test\", appDeploymentRequest)\n                .withHostNetwork(true)\n                .withExternalPort(8080);\n        Container container = defaultContainerFactory.create(containerConfiguration);\n        assertThat(container).isNotNull();\n\n        List<ContainerPort> containerPorts = container.getPorts();\n        assertThat(containerPorts).isNotNull();\n\n        assertThat(containerPorts).hasSize(2);\n        assertThat(containerPorts.get(0).getContainerPort()).isEqualTo(8080);\n        assertThat(containerPorts.get(0).getHostPort()).isEqualTo(8080);\n        assertThat(containerPorts.get(1).getContainerPort()).isEqualTo(8090);\n        assertThat(containerPorts.get(1).getHostPort()).isEqualTo(8090);\n        assertThat(container.getLivenessProbe().getHttpGet().getPort().getIntVal()).isEqualTo(8090);\n    }\n\n    /**\n     * @deprecated {@see {@link #createCustomReadinessHttpPortFromAppRequest()}}\n     */\n    @Test\n    @Deprecated\n    public void createCustomReadinessPortFromAppRequest() {\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n        DefaultContainerFactory defaultContainerFactory = new DefaultContainerFactory(\n                kubernetesDeployerProperties);\n\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        Resource resource = getResource();\n        Map<String, String> props = new HashMap<>();\n        props.put(\"spring.cloud.deployer.kubernetes.readinessProbePort\", Integer.toString(8090));\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition,\n                resource, props);\n\n        ContainerConfiguration containerConfiguration = new ContainerConfiguration(\"app-test\", appDeploymentRequest)\n                .withHostNetwork(true)\n                .withExternalPort(8080);\n        Container container = defaultContainerFactory.create(containerConfiguration);\n        assertThat(container).isNotNull();\n\n        List<ContainerPort> containerPorts = container.getPorts();\n        assertThat(containerPorts).isNotNull();\n\n        assertThat(containerPorts).hasSize(2);\n        assertThat(containerPorts.get(0).getContainerPort()).isEqualTo(8080);\n        assertThat(containerPorts.get(0).getHostPort()).isEqualTo(8080);\n        assertThat(containerPorts.get(1).getContainerPort()).isEqualTo(8090);\n        assertThat(containerPorts.get(1).getHostPort()).isEqualTo(8090);\n        assertThat(container.getReadinessProbe().getHttpGet().getPort().getIntVal()).isEqualTo(8090);\n    }\n\n    @Test\n    public void createCustomReadinessHttpPortFromAppRequest() {\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n        DefaultContainerFactory defaultContainerFactory = new DefaultContainerFactory(\n                kubernetesDeployerProperties);\n\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        Resource resource = getResource();\n        Map<String, String> props = new HashMap<>();\n        props.put(\"spring.cloud.deployer.kubernetes.readinessHttpProbePort\", Integer.toString(8090));\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition,\n                resource, props);\n\n        ContainerConfiguration containerConfiguration = new ContainerConfiguration(\"app-test\", appDeploymentRequest)\n                .withHostNetwork(true)\n                .withExternalPort(8080);\n        Container container = defaultContainerFactory.create(containerConfiguration);\n        assertThat(container).isNotNull();\n\n        List<ContainerPort> containerPorts = container.getPorts();\n        assertThat(containerPorts).isNotNull();\n\n        assertThat(containerPorts).hasSize(2);\n        assertThat(containerPorts.get(0).getContainerPort()).isEqualTo(8080);\n        assertThat(containerPorts.get(0).getHostPort()).isEqualTo(8080);\n        assertThat(containerPorts.get(1).getContainerPort()).isEqualTo(8090);\n        assertThat(containerPorts.get(1).getHostPort()).isEqualTo(8090);\n        assertThat(container.getReadinessProbe().getHttpGet().getPort().getIntVal()).isEqualTo(8090);\n    }\n\n    /**\n     * @deprecated {@see {@link #createCustomReadinessHttpPortFromProperties()}}\n     */\n    @Test\n    @Deprecated\n    public void createCustomReadinessPortFromProperties() {\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n        kubernetesDeployerProperties.setReadinessProbePort(8090);\n        DefaultContainerFactory defaultContainerFactory = new DefaultContainerFactory(\n                kubernetesDeployerProperties);\n\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        Resource resource = getResource();\n        Map<String, String> props = new HashMap<>();\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition,\n                resource, props);\n\n        ContainerConfiguration containerConfiguration = new ContainerConfiguration(\"app-test\", appDeploymentRequest)\n                .withHostNetwork(true)\n                .withExternalPort(8080);\n        Container container = defaultContainerFactory.create(containerConfiguration);\n        assertThat(container).isNotNull();\n\n        List<ContainerPort> containerPorts = container.getPorts();\n        assertThat(containerPorts).isNotNull();\n\n        assertThat(containerPorts).hasSize(2);\n        assertThat(containerPorts.get(0).getContainerPort()).isEqualTo(8080);\n        assertThat(containerPorts.get(0).getHostPort()).isEqualTo(8080);\n        assertThat(containerPorts.get(1).getContainerPort()).isEqualTo(8090);\n        assertThat(containerPorts.get(1).getHostPort()).isEqualTo(8090);\n        assertThat(container.getReadinessProbe().getHttpGet().getPort().getIntVal()).isEqualTo(8090);\n    }\n\n    @Test\n    public void createCustomReadinessHttpPortFromProperties() {\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n        kubernetesDeployerProperties.setReadinessHttpProbePort(8090);\n        DefaultContainerFactory defaultContainerFactory = new DefaultContainerFactory(\n                kubernetesDeployerProperties);\n\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        Resource resource = getResource();\n        Map<String, String> props = new HashMap<>();\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition,\n                resource, props);\n\n        ContainerConfiguration containerConfiguration = new ContainerConfiguration(\"app-test\", appDeploymentRequest)\n                .withHostNetwork(true)\n                .withExternalPort(8080);\n        Container container = defaultContainerFactory.create(containerConfiguration);\n        assertThat(container).isNotNull();\n\n        List<ContainerPort> containerPorts = container.getPorts();\n        assertThat(containerPorts).isNotNull();\n\n        assertThat(containerPorts).hasSize(2);\n        assertThat(containerPorts.get(0).getContainerPort()).isEqualTo(8080);\n        assertThat(containerPorts.get(0).getHostPort()).isEqualTo(8080);\n        assertThat(containerPorts.get(1).getContainerPort()).isEqualTo(8090);\n        assertThat(containerPorts.get(1).getHostPort()).isEqualTo(8090);\n        assertThat(container.getReadinessProbe().getHttpGet().getPort().getIntVal()).isEqualTo(8090);\n    }\n\n    @Test\n    public void createDefaultHttpProbePorts() {\n        int defaultPort = 8080;\n\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n        DefaultContainerFactory defaultContainerFactory = new DefaultContainerFactory(\n                kubernetesDeployerProperties);\n\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        Resource resource = getResource();\n        Map<String, String> props = new HashMap<>();\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition,\n                resource, props);\n\n        ContainerConfiguration containerConfiguration = new ContainerConfiguration(\"app-test\", appDeploymentRequest)\n                .withHostNetwork(true)\n                .withExternalPort(defaultPort);\n        Container container = defaultContainerFactory.create(containerConfiguration);\n        assertThat(container).isNotNull();\n        List<ContainerPort> containerPorts = container.getPorts();\n        assertThat(containerPorts).isNotNull();\n        assertThat(containerPorts).as(\"Only the default container port should set\").hasSize(1);\n        assertThat((int) containerPorts.get(0).getContainerPort()).isEqualTo(8080);\n        assertThat((int) containerPorts.get(0).getHostPort()).isEqualTo(8080);\n        assertThat((int) container.getLivenessProbe().getHttpGet().getPort().getIntVal()).isEqualTo(8080);\n        assertThat((int) container.getReadinessProbe().getHttpGet().getPort().getIntVal()).isEqualTo(8080);\n    }\n\n    @Test\n    public void createHttpProbesWithDefaultEndpoints() {\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n        DefaultContainerFactory defaultContainerFactory = new DefaultContainerFactory(\n                kubernetesDeployerProperties);\n\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        Resource resource = getResource();\n\n        Map<String, String> props = new HashMap<>();\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition,\n                resource, props);\n\n        ContainerConfiguration containerConfiguration = new ContainerConfiguration(\"app-test\", appDeploymentRequest)\n                .withHostNetwork(true)\n                .withExternalPort(8080);\n\n        Container container = defaultContainerFactory.create(containerConfiguration);\n\n        assertThat(container).isNotNull();\n\n        assertThat(container.getReadinessProbe().getHttpGet().getPath()).isNotNull();\n        assertThat(container.getReadinessProbe().getHttpGet().getPath()).isEqualTo(HttpProbeCreator.BOOT_2_READINESS_PROBE_PATH);\n\n        assertThat(container.getLivenessProbe().getHttpGet().getPath()).isNotNull();\n        assertThat(container.getLivenessProbe().getHttpGet().getPath()).isEqualTo(HttpProbeCreator.BOOT_2_LIVENESS_PROBE_PATH);\n    }\n\n    @Test\n    public void createHttpProbesWithBoot1Endpoints() {\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n        DefaultContainerFactory defaultContainerFactory = new DefaultContainerFactory(\n                kubernetesDeployerProperties);\n\n        Map<String, String> appProperties = new HashMap<>();\n        appProperties.put(\"spring.cloud.deployer.kubernetes.boot-major-version\", \"1\");\n\n        AppDefinition definition = new AppDefinition(\"app-test\", appProperties);\n        Resource resource = getResource();\n\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition,\n                resource, appProperties);\n\n        ContainerConfiguration containerConfiguration = new ContainerConfiguration(\"app-test\", appDeploymentRequest)\n                .withHostNetwork(true)\n                .withExternalPort(8080);\n\n        Container container = defaultContainerFactory.create(containerConfiguration);\n\n        assertThat(container).isNotNull();\n\n        assertThat(container.getReadinessProbe().getHttpGet().getPath()).isNotNull();\n        assertThat(container.getReadinessProbe().getHttpGet().getPath()).isEqualTo(HttpProbeCreator.BOOT_1_READINESS_PROBE_PATH);\n\n        assertThat(container.getLivenessProbe().getHttpGet().getPath()).isNotNull();\n        assertThat(container.getLivenessProbe().getHttpGet().getPath()).isEqualTo(HttpProbeCreator.BOOT_1_LIVENESS_PROBE_PATH);\n    }\n\n    /**\n     * @deprecated {@see {@link #createHttpProbesWithOverrides()}}\n     */\n    @Test\n    @Deprecated\n    public void createProbesWithOverrides() {\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n        DefaultContainerFactory defaultContainerFactory = new DefaultContainerFactory(\n                kubernetesDeployerProperties);\n\n        Map<String, String> appProperties = new HashMap<>();\n        appProperties.put(\"spring.cloud.deployer.kubernetes.livenessProbePath\", \"/liveness\");\n        appProperties.put(\"spring.cloud.deployer.kubernetes.readinessProbePath\", \"/readiness\");\n\n        AppDefinition definition = new AppDefinition(\"app-test\", appProperties);\n        Resource resource = getResource();\n\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition,\n                resource, appProperties);\n\n        ContainerConfiguration containerConfiguration = new ContainerConfiguration(\"app-test\", appDeploymentRequest)\n                .withHostNetwork(true)\n                .withExternalPort(8080);\n\n        Container container = defaultContainerFactory.create(containerConfiguration);\n\n        assertThat(container).isNotNull();\n\n        assertThat(container.getReadinessProbe().getHttpGet().getPath()).isNotNull();\n        assertThat(container.getReadinessProbe().getHttpGet().getPath()).isEqualTo(\"/readiness\");\n\n        assertThat(container.getLivenessProbe().getHttpGet().getPath()).isNotNull();\n        assertThat(container.getLivenessProbe().getHttpGet().getPath()).isEqualTo(\"/liveness\");\n    }\n\n    @Test\n    public void createHttpProbesWithOverrides() {\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n        DefaultContainerFactory defaultContainerFactory = new DefaultContainerFactory(\n                kubernetesDeployerProperties);\n\n        Map<String, String> appProperties = new HashMap<>();\n        appProperties.put(\"spring.cloud.deployer.kubernetes.livenessHttpProbePath\", \"/liveness\");\n        appProperties.put(\"spring.cloud.deployer.kubernetes.readinessHttpProbePath\", \"/readiness\");\n\n        AppDefinition definition = new AppDefinition(\"app-test\", appProperties);\n        Resource resource = getResource();\n\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition,\n                resource, appProperties);\n\n        ContainerConfiguration containerConfiguration = new ContainerConfiguration(\"app-test\", appDeploymentRequest)\n                .withHostNetwork(true)\n                .withExternalPort(8080);\n\n        Container container = defaultContainerFactory.create(containerConfiguration);\n\n        assertThat(container).isNotNull();\n\n        assertThat(container.getReadinessProbe().getHttpGet().getPath()).isNotNull();\n        assertThat(container.getReadinessProbe().getHttpGet().getPath()).isEqualTo(\"/readiness\");\n\n        assertThat(container.getLivenessProbe().getHttpGet().getPath()).isNotNull();\n        assertThat(container.getLivenessProbe().getHttpGet().getPath()).isEqualTo(\"/liveness\");\n    }\n\n    /**\n     * @deprecated {@see {@link #createHttpProbesWithPropertyOverrides()}}\n     */\n    @Test\n    @Deprecated\n    public void createProbesWithPropertyOverrides() {\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n        kubernetesDeployerProperties.setReadinessProbePath(\"/readiness\");\n        kubernetesDeployerProperties.setLivenessProbePath(\"/liveness\");\n        DefaultContainerFactory defaultContainerFactory = new DefaultContainerFactory(\n                kubernetesDeployerProperties);\n\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        Resource resource = getResource();\n\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition,\n                resource, null);\n\n        ContainerConfiguration containerConfiguration = new ContainerConfiguration(\"app-test\", appDeploymentRequest)\n                .withHostNetwork(true)\n                .withExternalPort(8080);\n\n        Container container = defaultContainerFactory.create(containerConfiguration);\n\n        assertThat(container).isNotNull();\n\n        assertThat(container.getReadinessProbe().getHttpGet().getPath()).isNotNull();\n        assertThat(container.getReadinessProbe().getHttpGet().getPath()).isEqualTo(\"/readiness\");\n\n        assertThat(container.getLivenessProbe().getHttpGet().getPath()).isNotNull();\n        assertThat(container.getLivenessProbe().getHttpGet().getPath()).isEqualTo(\"/liveness\");\n    }\n\n    @Test\n    public void createHttpProbesWithPropertyOverrides() {\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n        kubernetesDeployerProperties.setReadinessHttpProbePath(\"/readiness\");\n        kubernetesDeployerProperties.setLivenessHttpProbePath(\"/liveness\");\n        DefaultContainerFactory defaultContainerFactory = new DefaultContainerFactory(\n                kubernetesDeployerProperties);\n\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        Resource resource = getResource();\n\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition,\n                resource, null);\n\n        ContainerConfiguration containerConfiguration = new ContainerConfiguration(\"app-test\", appDeploymentRequest)\n                .withHostNetwork(true)\n                .withExternalPort(8080);\n\n        Container container = defaultContainerFactory.create(containerConfiguration);\n\n        assertThat(container).isNotNull();\n\n        assertThat(container.getReadinessProbe().getHttpGet().getPath()).isNotNull();\n        assertThat(container.getReadinessProbe().getHttpGet().getPath()).isEqualTo(\"/readiness\");\n\n        assertThat(container.getLivenessProbe().getHttpGet().getPath()).isNotNull();\n        assertThat(container.getLivenessProbe().getHttpGet().getPath()).isEqualTo(\"/liveness\");\n    }\n\n    @Test\n    public void testHttpProbeCredentialsSecret() {\n        Secret secret = randomSecret();\n        String secretName = secret.getMetadata().getName();\n\n        Map<String, String> appProperties = new HashMap<>();\n        appProperties.put(\"spring.cloud.deployer.kubernetes.probeCredentialsSecret\", secretName);\n\n        AppDefinition definition = new AppDefinition(\"app-test\", appProperties);\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), appProperties);\n\n        ContainerConfiguration containerConfiguration = new ContainerConfiguration(\"app-test\", appDeploymentRequest)\n                .withExternalPort(8080)\n                .withProbeCredentialsSecret(secret);\n\n        ContainerFactory containerFactory = new DefaultContainerFactory(new KubernetesDeployerProperties());\n        Container container = containerFactory.create(containerConfiguration);\n\n        String credentials = containerConfiguration.getProbeCredentialsSecret().getData()\n                .get(HttpProbeCreator.PROBE_CREDENTIALS_SECRET_KEY_NAME);\n\n        HTTPHeader livenessProbeHeader = container.getLivenessProbe().getHttpGet().getHttpHeaders().get(0);\n        assertThat(livenessProbeHeader.getName()).isEqualTo(HttpProbeCreator.AUTHORIZATION_HEADER_NAME);\n        assertThat(livenessProbeHeader.getValue()).isEqualTo(ProbeAuthenticationType.Basic.name() + \" \" + credentials);\n\n        HTTPHeader readinessProbeHeader = container.getReadinessProbe().getHttpGet().getHttpHeaders().get(0);\n        assertThat(readinessProbeHeader.getName()).isEqualTo(HttpProbeCreator.AUTHORIZATION_HEADER_NAME);\n        assertThat(readinessProbeHeader.getValue()).isEqualTo(ProbeAuthenticationType.Basic.name() + \" \" + credentials);\n    }\n\n    @Test\n    public void testHttpProbeCredentialsInvalidSecret() {\n        Secret secret = randomSecret();\n        secret.setData(Collections.singletonMap(\"unexpectedkey\", \"dXNlcjpwYXNz\"));\n\n        String secretName = secret.getMetadata().getName();\n\n        Map<String, String> appProperties = new HashMap<>();\n        appProperties.put(\"spring.cloud.deployer.kubernetes.probeCredentialsSecret\", secretName);\n\n        AppDefinition definition = new AppDefinition(\"app-test\", appProperties);\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), appProperties);\n\n        ContainerConfiguration containerConfiguration = new ContainerConfiguration(\"app-test\", appDeploymentRequest)\n                .withExternalPort(8080)\n                .withProbeCredentialsSecret(secret);\n\n        ContainerFactory containerFactory = new DefaultContainerFactory(new KubernetesDeployerProperties());\n        assertThatThrownBy(() -> {\n            containerFactory.create(containerConfiguration);\n        }).isInstanceOf(IllegalArgumentException.class);\n    }\n\n    @Test\n    public void testHttpProbeHeadersWithoutAuth() {\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource());\n\n        ContainerConfiguration containerConfiguration = new ContainerConfiguration(\"app-test\", appDeploymentRequest)\n                .withExternalPort(8080);\n\n        ContainerFactory containerFactory = new DefaultContainerFactory(new KubernetesDeployerProperties());\n        Container container = containerFactory.create(containerConfiguration);\n\n        assertThat(container.getLivenessProbe().getHttpGet().getHttpHeaders()).isEmpty();\n        assertThat(container.getReadinessProbe().getHttpGet().getHttpHeaders()).isEmpty();\n    }\n\n    @Test\n    public void createTcpProbe() {\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n        DefaultContainerFactory defaultContainerFactory = new DefaultContainerFactory(\n                kubernetesDeployerProperties);\n\n        Map<String, String> appProperties = new HashMap<>();\n        appProperties.put(\"spring.cloud.deployer.kubernetes.probeType\", ProbeType.TCP.name());\n        appProperties.put(\"spring.cloud.deployer.kubernetes.readinessTcpProbePort\", \"5050\");\n        appProperties.put(\"spring.cloud.deployer.kubernetes.livenessTcpProbePort\", \"9090\");\n        appProperties.put(\"spring.cloud.deployer.kubernetes.startupTcpProbePort\", \"9090\");\n\n        AppDefinition definition = new AppDefinition(\"app-test\", appProperties);\n        Resource resource = getResource();\n\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, resource, appProperties);\n\n        ContainerConfiguration containerConfiguration = new ContainerConfiguration(\"app-test\", appDeploymentRequest)\n                .withHostNetwork(true)\n                .withExternalPort(8080);\n\n        Container container = defaultContainerFactory.create(containerConfiguration);\n\n        assertThat(container).isNotNull();\n\n        assertThat(container.getPorts())\n                .contains(new ContainerPortBuilder().withHostPort(5050).withContainerPort(5050).build());\n        assertThat(container.getPorts())\n                .contains(new ContainerPortBuilder().withHostPort(9090).withContainerPort(9090).build());\n\n        Probe livenessProbe = container.getLivenessProbe();\n        Probe readinessProbe = container.getReadinessProbe();\n\n        assertThat(livenessProbe).isNotNull();\n        assertThat(readinessProbe).isNotNull();\n\n        Integer livenessTcpProbePort = livenessProbe.getTcpSocket().getPort().getIntVal();\n        assertThat(livenessTcpProbePort).isNotNull();\n        assertThat(livenessTcpProbePort.intValue()).isEqualTo(9090);\n\n        Integer readinessTcpProbePort = readinessProbe.getTcpSocket().getPort().getIntVal();\n        assertThat(readinessTcpProbePort).isNotNull();\n        assertThat(readinessTcpProbePort.intValue()).isEqualTo(5050);\n\n        assertThat(livenessProbe.getPeriodSeconds()).isNotNull();\n        assertThat(readinessProbe.getPeriodSeconds()).isNotNull();\n\n        assertThat(livenessProbe.getInitialDelaySeconds()).isNotNull();\n        assertThat(readinessProbe.getInitialDelaySeconds()).isNotNull();\n    }\n\n    @Test\n    public void createTcpProbeMissingLivenessPort() {\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n        DefaultContainerFactory defaultContainerFactory = new DefaultContainerFactory(\n                kubernetesDeployerProperties);\n\n        Map<String, String> appProperties = new HashMap<>();\n        appProperties.put(\"spring.cloud.deployer.kubernetes.probeType\", ProbeType.TCP.name());\n        appProperties.put(\"spring.cloud.deployer.kubernetes.readinessTcpProbePort\", \"5050\");\n        appProperties.put(\"spring.cloud.deployer.kubernetes.startupTcpProbePort\", \"9090\");\n\n        AppDefinition definition = new AppDefinition(\"app-test\", appProperties);\n        Resource resource = getResource();\n\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, resource, appProperties);\n\n        ContainerConfiguration containerConfiguration = new ContainerConfiguration(\"app-test\", appDeploymentRequest)\n                .withHostNetwork(true)\n                .withExternalPort(8080);\n\n        assertThatThrownBy(() -> {\n            defaultContainerFactory.create(containerConfiguration);\n        }).isInstanceOf(IllegalArgumentException.class)\n                .hasMessageContaining(\"The livenessTcpProbePort property must be set\");\n    }\n\n    @Test\n    public void createTcpProbeMissingReadinessPort() {\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n        DefaultContainerFactory defaultContainerFactory = new DefaultContainerFactory(\n                kubernetesDeployerProperties);\n\n        Map<String, String> appProperties = new HashMap<>();\n        appProperties.put(\"spring.cloud.deployer.kubernetes.probeType\", ProbeType.TCP.name());\n        appProperties.put(\"spring.cloud.deployer.kubernetes.livenessTcpProbePort\", \"5050\");\n        appProperties.put(\"spring.cloud.deployer.kubernetes.startupTcpProbePort\", \"5050\");\n\n        AppDefinition definition = new AppDefinition(\"app-test\", appProperties);\n        Resource resource = getResource();\n\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, resource, appProperties);\n\n        ContainerConfiguration containerConfiguration = new ContainerConfiguration(\"app-test\", appDeploymentRequest)\n                .withHostNetwork(true)\n                .withExternalPort(8080);\n\n        assertThatThrownBy(() -> {\n            defaultContainerFactory.create(containerConfiguration);\n        }).isInstanceOf(IllegalArgumentException.class)\n                .hasMessageContaining(\"A readinessTcpProbePort property must be set\");\n    }\n\n    @Test\n    public void createReadinessTcpProbeWithNonDigitPort() {\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n        DefaultContainerFactory defaultContainerFactory = new DefaultContainerFactory(\n                kubernetesDeployerProperties);\n\n        Map<String, String> appProperties = new HashMap<>();\n        appProperties.put(\"spring.cloud.deployer.kubernetes.probeType\", ProbeType.TCP.name());\n        appProperties.put(\"spring.cloud.deployer.kubernetes.livenessTcpProbePort\", \"9090\");\n        appProperties.put(\"spring.cloud.deployer.kubernetes.readinessTcpProbePort\", \"somePort\");\n        appProperties.put(\"spring.cloud.deployer.kubernetes.startupTcpProbePort\", \"9090\");\n\n        AppDefinition definition = new AppDefinition(\"app-test\", appProperties);\n        Resource resource = getResource();\n\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, resource, appProperties);\n\n        ContainerConfiguration containerConfiguration = new ContainerConfiguration(\"app-test\", appDeploymentRequest)\n                .withHostNetwork(true)\n                .withExternalPort(8080);\n\n        assertThatThrownBy(() -> {\n            defaultContainerFactory.create(containerConfiguration);\n        }).isInstanceOf(IllegalArgumentException.class)\n                .hasMessageContaining(\"ReadinessTcpProbePort must contain all digits\");\n    }\n\n    @Test\n    public void createLivenessTcpProbeWithNonDigitPort() {\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n        DefaultContainerFactory defaultContainerFactory = new DefaultContainerFactory(\n                kubernetesDeployerProperties);\n\n        Map<String, String> appProperties = new HashMap<>();\n        appProperties.put(\"spring.cloud.deployer.kubernetes.probeType\", ProbeType.TCP.name());\n        appProperties.put(\"spring.cloud.deployer.kubernetes.livenessTcpProbePort\", \"somePort\");\n        appProperties.put(\"spring.cloud.deployer.kubernetes.readinessTcpProbePort\", \"5050\");\n        appProperties.put(\"spring.cloud.deployer.kubernetes.startupTcpProbePort\", \"9090\");\n\n        AppDefinition definition = new AppDefinition(\"app-test\", appProperties);\n        Resource resource = getResource();\n\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, resource, appProperties);\n\n        ContainerConfiguration containerConfiguration = new ContainerConfiguration(\"app-test\", appDeploymentRequest)\n                .withHostNetwork(true)\n                .withExternalPort(8080);\n\n        assertThatThrownBy(() -> {\n            defaultContainerFactory.create(containerConfiguration);\n        }).isInstanceOf(IllegalArgumentException.class)\n                .hasMessageContaining(\"LivenessTcpProbePort must contain all digits\");\n    }\n\n    @Test\n    public void createTcpProbeGlobalProperties() {\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n        kubernetesDeployerProperties.setProbeType(ProbeType.TCP);\n        kubernetesDeployerProperties.setReadinessTcpProbePort(5050);\n        kubernetesDeployerProperties.setReadinessTcpProbeDelay(1);\n        kubernetesDeployerProperties.setReadinessTcpProbePeriod(2);\n        kubernetesDeployerProperties.setLivenessTcpProbePort(9090);\n        kubernetesDeployerProperties.setLivenessTcpProbeDelay(3);\n        kubernetesDeployerProperties.setLivenessTcpProbePeriod(4);\n        kubernetesDeployerProperties.setStartupTcpProbePort(9090);\n\n        DefaultContainerFactory defaultContainerFactory = new DefaultContainerFactory(\n                kubernetesDeployerProperties);\n\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        Resource resource = getResource();\n\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, resource, null);\n\n        ContainerConfiguration containerConfiguration = new ContainerConfiguration(\"app-test\", appDeploymentRequest)\n                .withHostNetwork(true)\n                .withExternalPort(8080);\n\n        Container container = defaultContainerFactory.create(containerConfiguration);\n\n        assertThat(container).isNotNull();\n\n        assertThat(container.getPorts())\n                .contains(new ContainerPortBuilder().withHostPort(5050).withContainerPort(5050).build());\n        assertThat(container.getPorts())\n                .contains(new ContainerPortBuilder().withHostPort(9090).withContainerPort(9090).build());\n\n        Probe livenessProbe = container.getLivenessProbe();\n        Probe readinessProbe = container.getReadinessProbe();\n\n        assertThat(livenessProbe).isNotNull();\n        assertThat(readinessProbe).isNotNull();\n\n        Integer livenessTcpProbePort = livenessProbe.getTcpSocket().getPort().getIntVal();\n        assertThat(livenessTcpProbePort).isNotNull();\n        assertThat(livenessTcpProbePort.intValue()).isEqualTo(9090);\n\n        Integer readinessTcpProbePort = readinessProbe.getTcpSocket().getPort().getIntVal();\n        assertThat(readinessTcpProbePort).isNotNull();\n        assertThat(readinessTcpProbePort.intValue()).isEqualTo(5050);\n\n        Integer livenessProbePeriodSeconds = livenessProbe.getPeriodSeconds();\n        assertThat(livenessProbePeriodSeconds).isNotNull();\n        assertThat(livenessProbePeriodSeconds.intValue()).isEqualTo(4);\n\n        Integer readinessProbePeriodSeconds = readinessProbe.getPeriodSeconds();\n        assertThat(readinessProbePeriodSeconds).isNotNull();\n        assertThat(readinessProbePeriodSeconds.intValue()).isEqualTo(2);\n\n        Integer livenessProbeInitialDelaySeconds = livenessProbe.getInitialDelaySeconds();\n        assertThat(livenessProbeInitialDelaySeconds).isNotNull();\n        assertThat(livenessProbeInitialDelaySeconds.intValue()).isEqualTo(3);\n\n        Integer readinessProbeInitialDelaySeconds = readinessProbe.getInitialDelaySeconds();\n        assertThat(readinessProbeInitialDelaySeconds).isNotNull();\n        assertThat(readinessProbeInitialDelaySeconds.intValue()).isEqualTo(1);\n    }\n\n    @Test\n    public void createTcpProbeGlobalPropertyOverride() {\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n        kubernetesDeployerProperties.setProbeType(ProbeType.TCP);\n        kubernetesDeployerProperties.setReadinessTcpProbePort(5050);\n        kubernetesDeployerProperties.setReadinessTcpProbeDelay(1);\n        kubernetesDeployerProperties.setReadinessTcpProbePeriod(2);\n        kubernetesDeployerProperties.setLivenessTcpProbePort(9090);\n        kubernetesDeployerProperties.setLivenessTcpProbeDelay(3);\n        kubernetesDeployerProperties.setLivenessTcpProbePeriod(4);\n\n        DefaultContainerFactory defaultContainerFactory = new DefaultContainerFactory(\n                kubernetesDeployerProperties);\n\n        Map<String, String> appProperties = new HashMap<>();\n        appProperties.put(\"spring.cloud.deployer.kubernetes.probeType\", ProbeType.TCP.name());\n        appProperties.put(\"spring.cloud.deployer.kubernetes.readinessTcpProbePort\", \"5050\");\n        appProperties.put(\"spring.cloud.deployer.kubernetes.readinessProbePeriod\", \"11\");\n        appProperties.put(\"spring.cloud.deployer.kubernetes.readinessProbeDelay\", \"12\");\n        appProperties.put(\"spring.cloud.deployer.kubernetes.livenessTcpProbePort\", \"9090\");\n        appProperties.put(\"spring.cloud.deployer.kubernetes.livenessProbePeriod\", \"13\");\n        appProperties.put(\"spring.cloud.deployer.kubernetes.livenessProbeDelay\", \"14\");\n        appProperties.put(\"spring.cloud.deployer.kubernetes.startupTcpProbePort\", \"9090\");\n\n        AppDefinition definition = new AppDefinition(\"app-test\", appProperties);\n        Resource resource = getResource();\n\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, resource, appProperties);\n\n        ContainerConfiguration containerConfiguration = new ContainerConfiguration(\"app-test\", appDeploymentRequest)\n                .withHostNetwork(true)\n                .withExternalPort(8080);\n\n        Container container = defaultContainerFactory.create(containerConfiguration);\n\n        assertThat(container).isNotNull();\n\n        assertThat(container.getPorts())\n                .contains(new ContainerPortBuilder().withHostPort(5050).withContainerPort(5050).build());\n        assertThat(container.getPorts())\n                .contains(new ContainerPortBuilder().withHostPort(9090).withContainerPort(9090).build());\n\n        Probe livenessProbe = container.getLivenessProbe();\n        Probe readinessProbe = container.getReadinessProbe();\n\n        assertThat(livenessProbe).isNotNull();\n        assertThat(readinessProbe).isNotNull();\n\n        Integer livenessTcpProbePort = livenessProbe.getTcpSocket().getPort().getIntVal();\n        assertThat(livenessTcpProbePort).isNotNull();\n        assertThat(livenessTcpProbePort.intValue()).isEqualTo(9090);\n\n        Integer readinessTcpProbePort = readinessProbe.getTcpSocket().getPort().getIntVal();\n        assertThat(readinessTcpProbePort).isNotNull();\n        assertThat(readinessTcpProbePort.intValue()).isEqualTo(5050);\n\n        Integer livenessProbePeriodSeconds = livenessProbe.getPeriodSeconds();\n        assertThat(livenessProbePeriodSeconds).isNotNull();\n        assertThat(livenessProbePeriodSeconds.intValue()).isEqualTo(13);\n\n        Integer readinessProbePeriodSeconds = readinessProbe.getPeriodSeconds();\n        assertThat(readinessProbePeriodSeconds).isNotNull();\n        assertThat(readinessProbePeriodSeconds.intValue()).isEqualTo(11);\n\n        Integer livenessProbeInitialDelaySeconds = livenessProbe.getInitialDelaySeconds();\n        assertThat(livenessProbeInitialDelaySeconds).isNotNull();\n        assertThat(livenessProbeInitialDelaySeconds.intValue()).isEqualTo(14);\n\n        Integer readinessProbeInitialDelaySeconds = readinessProbe.getInitialDelaySeconds();\n        assertThat(readinessProbeInitialDelaySeconds).isNotNull();\n        assertThat(readinessProbeInitialDelaySeconds.intValue()).isEqualTo(12);\n    }\n\n    @Test\n    public void createCommandProbe() {\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n        DefaultContainerFactory defaultContainerFactory = new DefaultContainerFactory(\n                kubernetesDeployerProperties);\n\n        Map<String, String> appProperties = new HashMap<>();\n        appProperties.put(\"spring.cloud.deployer.kubernetes.probeType\", ProbeType.COMMAND.name());\n        appProperties.put(\"spring.cloud.deployer.kubernetes.readinessCommandProbeCommand\", \"ls /\");\n        appProperties.put(\"spring.cloud.deployer.kubernetes.livenessCommandProbeCommand\", \"ls /dev\");\n        appProperties.put(\"spring.cloud.deployer.kubernetes.startupCommandProbeCommand\", \"ls /dev\");\n\n        AppDefinition definition = new AppDefinition(\"app-test\", appProperties);\n        Resource resource = getResource();\n\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, resource, appProperties);\n\n        ContainerConfiguration containerConfiguration = new ContainerConfiguration(\"app-test\", appDeploymentRequest)\n                .withHostNetwork(true)\n                .withExternalPort(8080);\n\n        Container container = defaultContainerFactory.create(containerConfiguration);\n\n        assertThat(container).isNotNull();\n\n        Probe livenessProbe = container.getLivenessProbe();\n        Probe readinessProbe = container.getReadinessProbe();\n\n        assertThat(livenessProbe).isNotNull();\n        assertThat(readinessProbe).isNotNull();\n\n        List<String> livenessCommandProbeCommand = livenessProbe.getExec().getCommand();\n        assertThat(livenessCommandProbeCommand).containsExactly(\"ls\", \"/dev\");\n\n        List<String> readinessCommandProbeCommand = readinessProbe.getExec().getCommand();\n        assertThat(readinessCommandProbeCommand).containsExactly(\"ls\", \"/\");\n\n        assertThat(livenessProbe.getPeriodSeconds()).isNotNull();\n        assertThat(readinessProbe.getPeriodSeconds()).isNotNull();\n\n        assertThat(livenessProbe.getInitialDelaySeconds()).isNotNull();\n        assertThat(readinessProbe.getInitialDelaySeconds()).isNotNull();\n    }\n\n    @Test\n    public void createCommandProbeMissingCommand() {\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n        DefaultContainerFactory defaultContainerFactory = new DefaultContainerFactory(\n                kubernetesDeployerProperties);\n\n        Map<String, String> appProperties = new HashMap<>();\n        appProperties.put(\"spring.cloud.deployer.kubernetes.probeType\", ProbeType.COMMAND.name());\n        appProperties.put(\"spring.cloud.deployer.kubernetes.startupCommandProbeCommand\", \"ls /\");\n\n        AppDefinition definition = new AppDefinition(\"app-test\", appProperties);\n        Resource resource = getResource();\n\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, resource, appProperties);\n\n        ContainerConfiguration containerConfiguration = new ContainerConfiguration(\"app-test\", appDeploymentRequest)\n                .withHostNetwork(true)\n                .withExternalPort(8080);\n\n        assertThatThrownBy(() -> {\n            defaultContainerFactory.create(containerConfiguration);\n        }).isInstanceOf(IllegalArgumentException.class)\n                .hasMessageContaining(\"The readinessCommandProbeCommand property must be set\");\n    }\n\n    @Test\n    public void createCommandProbeGlobalProperties() {\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n        kubernetesDeployerProperties.setProbeType(ProbeType.COMMAND);\n        kubernetesDeployerProperties.setReadinessCommandProbeCommand(\"ls /\");\n        kubernetesDeployerProperties.setReadinessCommandProbeDelay(1);\n        kubernetesDeployerProperties.setReadinessCommandProbePeriod(2);\n        kubernetesDeployerProperties.setLivenessCommandProbeCommand(\"ls /dev\");\n        kubernetesDeployerProperties.setLivenessCommandProbeDelay(3);\n        kubernetesDeployerProperties.setLivenessCommandProbePeriod(4);\n        kubernetesDeployerProperties.setStartupCommandProbeCommand(\"ls /dev\");\n\n        DefaultContainerFactory defaultContainerFactory = new DefaultContainerFactory(\n                kubernetesDeployerProperties);\n\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        Resource resource = getResource();\n\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, resource, null);\n\n        ContainerConfiguration containerConfiguration = new ContainerConfiguration(\"app-test\", appDeploymentRequest)\n                .withHostNetwork(true)\n                .withExternalPort(8080);\n\n        Container container = defaultContainerFactory.create(containerConfiguration);\n\n        assertThat(container).isNotNull();\n\n        Probe livenessProbe = container.getLivenessProbe();\n        Probe readinessProbe = container.getReadinessProbe();\n\n        assertThat(livenessProbe).isNotNull();\n        assertThat(readinessProbe).isNotNull();\n\n        String livenessTcpProbeCommand = String.join(\" \", livenessProbe.getExec().getCommand());\n        assertThat(livenessTcpProbeCommand).isNotNull();\n        assertThat(livenessTcpProbeCommand).isEqualTo(\"ls /dev\");\n\n        String readinessTcpProbeCommand = String.join(\" \", readinessProbe.getExec().getCommand());\n        assertThat(readinessTcpProbeCommand).isNotNull();\n        assertThat(readinessTcpProbeCommand).isEqualTo(\"ls /\");\n\n        Integer livenessProbePeriodSeconds = livenessProbe.getPeriodSeconds();\n        assertThat(livenessProbePeriodSeconds).isNotNull();\n        assertThat(livenessProbePeriodSeconds.intValue()).isEqualTo(4);\n\n        Integer readinessProbePeriodSeconds = readinessProbe.getPeriodSeconds();\n        assertThat(readinessProbePeriodSeconds).isNotNull();\n        assertThat(readinessProbePeriodSeconds.intValue()).isEqualTo(2);\n\n        Integer livenessProbeInitialDelaySeconds = livenessProbe.getInitialDelaySeconds();\n        assertThat(livenessProbeInitialDelaySeconds).isNotNull();\n        assertThat(livenessProbeInitialDelaySeconds.intValue()).isEqualTo(3);\n\n        Integer readinessProbeInitialDelaySeconds = readinessProbe.getInitialDelaySeconds();\n        assertThat(readinessProbeInitialDelaySeconds).isNotNull();\n        assertThat(readinessProbeInitialDelaySeconds.intValue()).isEqualTo(1);\n    }\n\n    @Test\n    public void createCommandProbeGlobalPropertyOverride() {\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n        kubernetesDeployerProperties.setProbeType(ProbeType.COMMAND);\n        kubernetesDeployerProperties.setReadinessCommandProbeCommand(\"ls /\");\n        kubernetesDeployerProperties.setReadinessCommandProbeDelay(1);\n        kubernetesDeployerProperties.setReadinessCommandProbePeriod(2);\n        kubernetesDeployerProperties.setLivenessCommandProbeCommand(\"ls /dev\");\n        kubernetesDeployerProperties.setLivenessCommandProbeDelay(3);\n        kubernetesDeployerProperties.setLivenessCommandProbePeriod(4);\n\n        DefaultContainerFactory defaultContainerFactory = new DefaultContainerFactory(\n                kubernetesDeployerProperties);\n\n        Map<String, String> appProperties = new HashMap<>();\n        appProperties.put(\"spring.cloud.deployer.kubernetes.probeType\", ProbeType.COMMAND.name());\n        appProperties.put(\"spring.cloud.deployer.kubernetes.readinessCommandProbeCommand\", \"ls /\");\n        appProperties.put(\"spring.cloud.deployer.kubernetes.readinessProbePeriod\", \"11\");\n        appProperties.put(\"spring.cloud.deployer.kubernetes.readinessProbeDelay\", \"12\");\n        appProperties.put(\"spring.cloud.deployer.kubernetes.livenessCommandProbeCommand\", \"ls /dev\");\n        appProperties.put(\"spring.cloud.deployer.kubernetes.livenessProbePeriod\", \"13\");\n        appProperties.put(\"spring.cloud.deployer.kubernetes.livenessProbeDelay\", \"14\");\n        appProperties.put(\"spring.cloud.deployer.kubernetes.startupCommandProbeCommand\", \"ls /dev\");\n\n        AppDefinition definition = new AppDefinition(\"app-test\", appProperties);\n        Resource resource = getResource();\n\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, resource, appProperties);\n\n        ContainerConfiguration containerConfiguration = new ContainerConfiguration(\"app-test\", appDeploymentRequest)\n                .withHostNetwork(true)\n                .withExternalPort(8080);\n\n        Container container = defaultContainerFactory.create(containerConfiguration);\n\n        assertThat(container).isNotNull();\n\n        Probe livenessProbe = container.getLivenessProbe();\n        Probe readinessProbe = container.getReadinessProbe();\n\n        assertThat(livenessProbe).isNotNull();\n        assertThat(readinessProbe).isNotNull();\n\n        String livenessTcpProbeCommand = String.join(\" \", livenessProbe.getExec().getCommand());\n        assertThat(livenessTcpProbeCommand).isNotNull();\n        assertThat(livenessTcpProbeCommand).isEqualTo(\"ls /dev\");\n\n        String readinessTcpProbeCommand = String.join(\" \", readinessProbe.getExec().getCommand());\n        assertThat(readinessTcpProbeCommand).isNotNull();\n        assertThat(readinessTcpProbeCommand).isEqualTo(\"ls /\");\n\n        Integer livenessProbePeriodSeconds = livenessProbe.getPeriodSeconds();\n        assertThat(livenessProbePeriodSeconds).isNotNull();\n        assertThat(livenessProbePeriodSeconds.intValue()).isEqualTo(13);\n\n        Integer readinessProbePeriodSeconds = readinessProbe.getPeriodSeconds();\n        assertThat(readinessProbePeriodSeconds).isNotNull();\n        assertThat(readinessProbePeriodSeconds.intValue()).isEqualTo(11);\n\n        Integer livenessProbeInitialDelaySeconds = livenessProbe.getInitialDelaySeconds();\n        assertThat(livenessProbeInitialDelaySeconds).isNotNull();\n        assertThat(livenessProbeInitialDelaySeconds.intValue()).isEqualTo(14);\n\n        Integer readinessProbeInitialDelaySeconds = readinessProbe.getInitialDelaySeconds();\n        assertThat(readinessProbeInitialDelaySeconds).isNotNull();\n        assertThat(readinessProbeInitialDelaySeconds.intValue()).isEqualTo(12);\n    }\n\n    @Test\n    public void testCommandLineArgsOverridesExistingProperties() {\n        AppDefinition definition = new AppDefinition(\"app-test\", Collections.singletonMap(\"foo\", \"bar\"));\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), null,\n                Collections.singletonList(\"--foo=newValue\"));\n\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n        DefaultContainerFactory defaultContainerFactory = new DefaultContainerFactory(\n                kubernetesDeployerProperties);\n        assertThat(defaultContainerFactory.createCommandArgs(appDeploymentRequest)).containsExactly(\"--foo=newValue\");\n    }\n\n    @Test\n    public void testCommandLineArgsNoAssignment() {\n        List<String> args = new ArrayList();\n        args.add(\"a\");\n        args.add(\"--b = c\");\n        args.add(\"d=e\");\n        args.add(\"f = g\");\n        AppDefinition definition = new AppDefinition(\"app-test\", Collections.singletonMap(\"b\", \"d\"));\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), null,\n                args);\n\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n        DefaultContainerFactory defaultContainerFactory = new DefaultContainerFactory(\n                kubernetesDeployerProperties);\n        assertThat(defaultContainerFactory.createCommandArgs(appDeploymentRequest)).containsExactly(\"a\", \"--b = c\", \"d=e\", \"f = g\");\n    }\n\n    @Test\n    public void testCommandLineArgsExcludesMalformedProperties() {\n        Map<String, String> properties = new HashMap<>();\n        properties.put(\"sun.cpu.isalist\", \"\");\n        properties.put(\"foo\", \"bar\");\n        AppDefinition definition = new AppDefinition(\"app-test\", properties);\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource());\n\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n        DefaultContainerFactory defaultContainerFactory = new DefaultContainerFactory(\n                kubernetesDeployerProperties);\n        assertThat(defaultContainerFactory.createCommandArgs(appDeploymentRequest)).containsExactly(\"--foo=bar\");\n    }\n\n    private Resource getResource() {\n        return new DockerResource(\n                \"springcloud/spring-cloud-deployer-spi-test-app:latest\");\n    }\n\n    private Secret randomSecret() {\n        String secretName = \"secret-\" + UUID.randomUUID().toString().substring(0, 18);\n        String secretValue = \"dXNlcjpwYXNz\"; // base64 encoded string of: user:pass\n\n        ObjectMeta objectMeta = new ObjectMeta();\n        objectMeta.setName(secretName);\n\n        Secret secret = new Secret();\n        secret.setData(Collections.singletonMap(HttpProbeCreator.PROBE_CREDENTIALS_SECRET_KEY_NAME, secretValue));\n        secret.setMetadata(objectMeta);\n\n        return secret;\n    }\n}\n"
  },
  {
    "path": "src/test/java/org/springframework/cloud/deployer/spi/kubernetes/KubernetesAppDeployerIntegrationIT.java",
    "content": "/*\n * Copyright 2016-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.kubernetes;\n\nimport java.io.ByteArrayOutputStream;\nimport java.io.IOException;\nimport java.lang.reflect.Type;\nimport java.time.Duration;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Base64;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.LinkedHashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.UUID;\nimport java.util.concurrent.CountDownLatch;\nimport java.util.stream.Collectors;\nimport java.util.stream.Stream;\n\nimport io.fabric8.kubernetes.api.model.ConfigMap;\nimport io.fabric8.kubernetes.api.model.Container;\nimport io.fabric8.kubernetes.api.model.ContainerBuilder;\nimport io.fabric8.kubernetes.api.model.EnvFromSource;\nimport io.fabric8.kubernetes.api.model.EnvVar;\nimport io.fabric8.kubernetes.api.model.HostPathVolumeSource;\nimport io.fabric8.kubernetes.api.model.HostPathVolumeSourceBuilder;\nimport io.fabric8.kubernetes.api.model.ObjectMeta;\nimport io.fabric8.kubernetes.api.model.PersistentVolumeClaim;\nimport io.fabric8.kubernetes.api.model.PersistentVolumeClaimSpec;\nimport io.fabric8.kubernetes.api.model.Pod;\nimport io.fabric8.kubernetes.api.model.PodSecurityContext;\nimport io.fabric8.kubernetes.api.model.PodSecurityContextBuilder;\nimport io.fabric8.kubernetes.api.model.PodSpec;\nimport io.fabric8.kubernetes.api.model.SELinuxOptions;\nimport io.fabric8.kubernetes.api.model.Secret;\nimport io.fabric8.kubernetes.api.model.SecurityContext;\nimport io.fabric8.kubernetes.api.model.SecurityContextBuilder;\nimport io.fabric8.kubernetes.api.model.Service;\nimport io.fabric8.kubernetes.api.model.ServiceAccount;\nimport io.fabric8.kubernetes.api.model.ServiceAccountBuilder;\nimport io.fabric8.kubernetes.api.model.ServicePort;\nimport io.fabric8.kubernetes.api.model.Volume;\nimport io.fabric8.kubernetes.api.model.VolumeBuilder;\nimport io.fabric8.kubernetes.api.model.VolumeMount;\nimport io.fabric8.kubernetes.api.model.apps.Deployment;\nimport io.fabric8.kubernetes.api.model.apps.StatefulSet;\nimport io.fabric8.kubernetes.api.model.apps.StatefulSetSpec;\nimport io.fabric8.kubernetes.client.KubernetesClient;\nimport io.fabric8.kubernetes.client.dsl.ExecListener;\nimport io.fabric8.kubernetes.client.dsl.ExecWatch;\nimport org.assertj.core.api.Assertions;\nimport org.assertj.core.api.InstanceOfAssertFactories;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Disabled;\nimport org.junit.jupiter.api.Nested;\nimport org.junit.jupiter.api.Test;\nimport org.mockito.Mockito;\nimport org.mockito.stubbing.Answer;\n\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.boot.test.context.SpringBootTest;\nimport org.springframework.cloud.deployer.resource.docker.DockerResource;\nimport org.springframework.cloud.deployer.spi.app.AppAdmin;\nimport org.springframework.cloud.deployer.spi.app.AppDeployer;\nimport org.springframework.cloud.deployer.spi.app.AppScaleRequest;\nimport org.springframework.cloud.deployer.spi.app.AppStatus;\nimport org.springframework.cloud.deployer.spi.app.DeploymentState;\nimport org.springframework.cloud.deployer.spi.core.AppDefinition;\nimport org.springframework.cloud.deployer.spi.core.AppDeploymentRequest;\nimport org.springframework.cloud.deployer.spi.test.AbstractAppDeployerIntegrationJUnit5Tests;\nimport org.springframework.cloud.deployer.spi.test.Timeout;\nimport org.springframework.core.ParameterizedTypeReference;\nimport org.springframework.core.io.Resource;\nimport org.springframework.http.HttpEntity;\nimport org.springframework.http.HttpMethod;\nimport org.springframework.http.ResponseEntity;\nimport org.springframework.util.StringUtils;\nimport org.springframework.web.client.ResourceAccessException;\nimport org.springframework.web.client.RestTemplate;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.assertThatThrownBy;\nimport static org.assertj.core.api.Assertions.entry;\nimport static org.awaitility.Awaitility.await;\nimport static org.mockito.Mockito.doAnswer;\n\n/**\n * Integration tests for {@link KubernetesAppDeployer}.\n *\n * @author Thomas Risberg\n * @author Donovan Muller\n * @author David Turanski\n * @author Chris Schaefer\n * @author Christian Tzolov\n * @author Chris Bono\n */\n@SpringBootTest(classes = {KubernetesAutoConfiguration.class}, properties = {\n        \"logging.level.org.springframework.cloud.deployer=DEBUG\"\n})\npublic class KubernetesAppDeployerIntegrationIT extends AbstractAppDeployerIntegrationJUnit5Tests {\n\n    @Autowired\n    private AppDeployer appDeployer;\n\n    @Autowired\n    private KubernetesClient kubernetesClient;\n\n    @Override\n    protected AppDeployer provideAppDeployer() {\n        return appDeployer;\n    }\n\n    @BeforeEach\n    public void setup() {\n        if (kubernetesClient.getNamespace() == null) {\n            kubernetesClient.getConfiguration().setNamespace(\"default\");\n        }\n    }\n\n    @Test\n    public void testScaleStatefulSet() {\n        logger.info(\"Testing {}...\", \"ScaleStatefulSet\");\n        KubernetesAppDeployer appDeployer = kubernetesAppDeployer();\n\n        AppDefinition definition = new AppDefinition(randomName(), null);\n        Resource resource = testApplication();\n\n        Map<String, String> props = new HashMap<>();\n        props.put(AppDeployer.COUNT_PROPERTY_KEY, \"3\");\n        props.put(AppDeployer.INDEXED_PROPERTY_KEY, \"true\");\n\n        AppDeploymentRequest request = new AppDeploymentRequest(definition, resource, props);\n\n        logger.info(\"Deploying {}...\", request.getDefinition().getName());\n        Timeout timeout = deploymentTimeout();\n        String deploymentId = appDeployer.deploy(request);\n\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> {\n                    assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.deployed);\n                });\n\n        // assertThat(deploymentId, eventually(appInstanceCount(is(3))));\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> {\n                    assertThat(appDeployer().status(deploymentId).getInstances()).hasSize(3);\n                });\n\n        // Ensure that a StatefulSet is deployed\n        Map<String, String> selector = Collections.singletonMap(AbstractKubernetesDeployer.SPRING_APP_KEY, deploymentId);\n        List<StatefulSet> statefulSets = kubernetesClient.apps().statefulSets().withLabels(selector).list().getItems();\n        assertThat(statefulSets).isNotNull();\n        assertThat(statefulSets).hasSize(1);\n        StatefulSet statefulSet = statefulSets.get(0);\n        StatefulSetSpec statefulSetSpec = statefulSet.getSpec();\n        Assertions.assertThat(statefulSetSpec.getPodManagementPolicy()).isEqualTo(\"Parallel\");\n        Assertions.assertThat(statefulSetSpec.getReplicas()).isEqualTo(3);\n        Assertions.assertThat(statefulSetSpec.getServiceName()).isEqualTo(deploymentId);\n        Assertions.assertThat(statefulSet.getMetadata().getName()).isEqualTo(deploymentId);\n\n        logger.info(\"Scale Down {}...\", request.getDefinition().getName());\n        appDeployer.scale(new AppScaleRequest(deploymentId, 1));\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> {\n                    assertThat(appDeployer().status(deploymentId).getInstances()).hasSize(1);\n                });\n\n        statefulSets = kubernetesClient.apps().statefulSets().withLabels(selector).list().getItems();\n        assertThat(statefulSets).hasSize(1);\n        statefulSetSpec = statefulSets.get(0).getSpec();\n        Assertions.assertThat(statefulSetSpec.getReplicas()).isEqualTo(1);\n        Assertions.assertThat(statefulSetSpec.getServiceName()).isEqualTo(deploymentId);\n        Assertions.assertThat(statefulSet.getMetadata().getName()).isEqualTo(deploymentId);\n\n        appDeployer.undeploy(deploymentId);\n    }\n\n    @Test\n    public void testScaleDeployment() {\n        logger.info(\"Testing {}...\", \"ScaleDeployment\");\n        KubernetesAppDeployer appDeployer = kubernetesAppDeployer();\n\n        AppDefinition definition = new AppDefinition(randomName(), null);\n        Resource resource = testApplication();\n\n        AppDeploymentRequest request = new AppDeploymentRequest(definition, resource, Collections.emptyMap());\n\n        logger.info(\"Deploying {}...\", request.getDefinition().getName());\n        Timeout timeout = deploymentTimeout();\n        String deploymentId = appDeployer.deploy(request);\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> {\n                    assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.deployed);\n                });\n\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> {\n                    assertThat(appDeployer().status(deploymentId).getInstances()).hasSize(1);\n                });\n\n        logger.info(\"Scale Up {}...\", request.getDefinition().getName());\n        appDeployer.scale(new AppScaleRequest(deploymentId, 3));\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> {\n                    assertThat(appDeployer().status(deploymentId).getInstances()).hasSize(3);\n                });\n\n        logger.info(\"Scale Down {}...\", request.getDefinition().getName());\n        appDeployer.scale(new AppScaleRequest(deploymentId, 1));\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> {\n                    assertThat(appDeployer().status(deploymentId).getInstances()).hasSize(1);\n                });\n\n        appDeployer.undeploy(deploymentId);\n    }\n\n    @Test\n    public void testScaleWithNonExistingApps() {\n        assertThatThrownBy(() -> {\n            appDeployer.scale(new AppScaleRequest(\"Fake App\", 10));\n        }).isInstanceOf(IllegalStateException.class);\n    }\n\n    @Test\n    public void testFailedDeploymentWithLoadBalancer() {\n        logger.info(\"Testing {}...\", \"FailedDeploymentWithLoadBalancer\");\n        KubernetesDeployerProperties deployProperties = new KubernetesDeployerProperties();\n        deployProperties.setCreateLoadBalancer(true);\n        deployProperties.setLivenessHttpProbePeriod(10);\n        deployProperties.setMaxTerminatedErrorRestarts(1);\n        deployProperties.setMaxCrashLoopBackOffRestarts(1);\n        KubernetesAppDeployer lbAppDeployer = kubernetesAppDeployer(deployProperties);\n\n        AppDefinition definition = new AppDefinition(randomName(), null);\n        Resource resource = testApplication();\n\n        Map<String, String> props = new HashMap<>();\n        props.put(\"spring.cloud.deployer.kubernetes.livenessHttpProbePath\", \"/invalidpath\");\n        props.put(\"spring.cloud.deployer.kubernetes.livenessHttpProbeDelay\", \"1\");\n        props.put(\"spring.cloud.deployer.kubernetes.livenessHttpProbePeriod\", \"1\");\n\n        AppDeploymentRequest request = new AppDeploymentRequest(definition, resource, props);\n\n        logger.info(\"Deploying {}...\", request.getDefinition().getName());\n\n        String deploymentId = lbAppDeployer.deploy(request);\n        Timeout timeout = deploymentTimeout();\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> {\n                    assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.failed);\n                });\n\n        logger.info(\"Undeploying {}...\", deploymentId);\n        timeout = undeploymentTimeout();\n        lbAppDeployer.undeploy(deploymentId);\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> {\n                    assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.unknown);\n                });\n    }\n\n    @Test\n    public void testGoodDeploymentWithLoadBalancer() {\n        logger.info(\"Testing {}...\", \"GoodDeploymentWithLoadBalancer\");\n        KubernetesDeployerProperties deployProperties = new KubernetesDeployerProperties();\n        deployProperties.setCreateLoadBalancer(true);\n        deployProperties.setMinutesToWaitForLoadBalancer(1);\n        KubernetesAppDeployer lbAppDeployer = kubernetesAppDeployer(deployProperties);\n\n        AppDefinition definition = new AppDefinition(randomName(), null);\n        Resource resource = testApplication();\n        AppDeploymentRequest request = new AppDeploymentRequest(definition, resource);\n\n        logger.info(\"Deploying {}...\", request.getDefinition().getName());\n        String deploymentId = lbAppDeployer.deploy(request);\n        Timeout timeout = deploymentTimeout();\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> {\n                    assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.deployed);\n                });\n\n        logger.info(\"Undeploying {}...\", deploymentId);\n        timeout = undeploymentTimeout();\n        lbAppDeployer.undeploy(deploymentId);\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> {\n                    assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.unknown);\n                });\n\n    }\n\n    @Test\n    @Disabled(\"Disabled until we can test lbs\")\n    public void testDeploymentWithLoadBalancerHasUrlAndAnnotation() {\n        logger.info(\"Testing {}...\", \"DeploymentWithLoadBalancerShowsUrl\");\n        KubernetesDeployerProperties deployProperties = new KubernetesDeployerProperties();\n        deployProperties.setCreateLoadBalancer(true);\n        deployProperties.setMinutesToWaitForLoadBalancer(1);\n        KubernetesAppDeployer lbAppDeployer = kubernetesAppDeployer(deployProperties);\n\n        AppDefinition definition = new AppDefinition(randomName(), null);\n        Resource resource = testApplication();\n        AppDeploymentRequest request = new AppDeploymentRequest(definition, resource,\n                Collections.singletonMap(\"spring.cloud.deployer.kubernetes.serviceAnnotations\", \"foo:bar,fab:baz\"));\n\n        logger.info(\"Deploying {}...\", request.getDefinition().getName());\n        String deploymentId = lbAppDeployer.deploy(request);\n        Timeout timeout = deploymentTimeout();\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> {\n                    assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.deployed);\n                });\n\n        logger.info(\"Checking instance attributes of {}...\", request.getDefinition().getName());\n        AppStatus status = lbAppDeployer.status(deploymentId);\n        for (String inst : status.getInstances().keySet()) {\n            appDeployer().status(deploymentId).getInstances().get(inst);\n            await().pollInterval(Duration.ofMillis(timeout.pause))\n                    .atMost(Duration.ofMillis(timeout.totalTime))\n                    .untilAsserted(() -> {\n                        assertThat(appDeployer().status(deploymentId).getInstances().get(inst).getAttributes()).containsKey(\"url\");\n                    });\n\n        }\n        logger.info(\"Checking service annotations of {}...\", request.getDefinition().getName());\n        Map<String, String> annotations = kubernetesClient.services().withName(request.getDefinition().getName()).get()\n                .getMetadata().getAnnotations();\n        assertThat(annotations).isNotNull();\n        assertThat(annotations).hasSize(2);\n        assertThat(annotations).containsKey(\"foo\");\n        assertThat(annotations.get(\"foo\")).isEqualTo(\"bar\");\n        assertThat(annotations).containsKey(\"fab\");\n        assertThat(annotations.get(\"fab\")).isEqualTo(\"baz\");\n\n        logger.info(\"Undeploying {}...\", deploymentId);\n        timeout = undeploymentTimeout();\n        lbAppDeployer.undeploy(deploymentId);\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> {\n                    assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.unknown);\n                });\n    }\n\n    @Test\n    public void testAttributes() {\n        logger.info(\"Testing {}...\", \"Attributes\");\n        KubernetesDeployerProperties properties = new KubernetesDeployerProperties();\n        AppAdmin appAdmin = new AppAdmin();\n        appAdmin.setUser(\"user\");\n        appAdmin.setPassword(\"password\");\n        properties.setAppAdmin(appAdmin);\n        KubernetesAppDeployer appDeployer = kubernetesAppDeployer(properties);\n\n        AppDefinition definition = new AppDefinition(randomName(), null);\n        Resource resource = testApplication();\n        AppDeploymentRequest request = new AppDeploymentRequest(definition, resource);\n\n        logger.info(\"Deploying {}...\", request.getDefinition().getName());\n        String deploymentId = appDeployer.deploy(request);\n        Timeout timeout = deploymentTimeout();\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> {\n                    assertThat(appDeployer.status(deploymentId).getInstances().values().stream()\n                            .map(ais -> ais.getAttributes()).filter(a ->\n                                    StringUtils.hasText(a.get(\"pod.ip\")))\n                            .findFirst()).isPresent();\n                    assertThat(appDeployer.status(deploymentId).getInstances().values().stream()\n                            .map(ais -> ais.getAttributes()).filter(a ->\n                                    StringUtils.hasText(a.get(\"actuator.path\")))\n                            .map(a -> a.get(\"actuator.path\"))\n                            .findFirst().get()).isEqualTo(\"/actuator\");\n\n                    assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.deployed);\n                });\n\n        logger.info(\"Undeploying {}...\", deploymentId);\n        timeout = undeploymentTimeout();\n        appDeployer.undeploy(deploymentId);\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> {\n                    assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.unknown);\n                });\n    }\n\n    @Test\n    public void testDeploymentWithPodAnnotation() {\n        logger.info(\"Testing {}...\", \"DeploymentWithPodAnnotation\");\n        KubernetesAppDeployer appDeployer = kubernetesAppDeployer();\n\n        AppDefinition definition = new AppDefinition(randomName(), null);\n        Resource resource = testApplication();\n        AppDeploymentRequest request = new AppDeploymentRequest(definition, resource,\n                Collections.singletonMap(\"spring.cloud.deployer.kubernetes.podAnnotations\",\n                        \"iam.amazonaws.com/role:role-arn,foo:bar\"));\n\n        logger.info(\"Deploying {}...\", request.getDefinition().getName());\n        String deploymentId = appDeployer.deploy(request);\n        Timeout timeout = deploymentTimeout();\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> {\n                    assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.deployed);\n                });\n\n        logger.info(\"Checking pod spec annotations of {}...\", request.getDefinition().getName());\n\n        List<Pod> pods = kubernetesClient.pods().withLabel(\"spring-deployment-id\", request.getDefinition()\n                .getName()).list().getItems();\n\n        assertThat(pods).hasSize(1);\n\n        Pod pod = pods.get(0);\n        Map<String, String> annotations = pod.getMetadata().getAnnotations();\n        logger.info(\"Number of annotations found\" + annotations.size());\n        for (Map.Entry<String, String> annotationsEntry : annotations.entrySet()) {\n            logger.info(\"Annotation key: \" + annotationsEntry.getKey());\n        }\n        assertThat(annotations.get(\"iam.amazonaws.com/role\")).isEqualTo(\"role-arn\");\n        assertThat(annotations.get(\"foo\")).isEqualTo(\"bar\");\n\n        logger.info(\"Undeploying {}...\", deploymentId);\n        timeout = undeploymentTimeout();\n        appDeployer.undeploy(deploymentId);\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> {\n                    assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.unknown);\n                });\n    }\n\n    @Test\n    public void testDeploymentWithMountedHostPathVolume() throws IOException {\n        logger.info(\"Testing {}...\", \"DeploymentWithMountedVolume\");\n        String containerPath = \"/tmp/\";\n        String subPath = randomName();\n        String mountName = \"mount\";\n\n        HostPathVolumeSource hostPathVolumeSource = new HostPathVolumeSourceBuilder()\n                .withPath(\"/tmp/\" + randomName() + '/').build();\n\n        KubernetesDeployerProperties deployProperties = new KubernetesDeployerProperties();\n        deployProperties.setVolumes(Collections.singletonList(new VolumeBuilder()\n                .withHostPath(hostPathVolumeSource)\n                .withName(mountName)\n                .build()));\n        deployProperties.setVolumeMounts(Collections.singletonList(new VolumeMount(hostPathVolumeSource.getPath(), null,\n                mountName, false, null, null)));\n        KubernetesAppDeployer lbAppDeployer = kubernetesAppDeployer(deployProperties);\n\n        AppDefinition definition = new AppDefinition(randomName(),\n                Collections.singletonMap(\"logging.file\", containerPath + subPath));\n        Resource resource = testApplication();\n        AppDeploymentRequest request = new AppDeploymentRequest(definition, resource);\n\n        logger.info(\"Deploying {}...\", request.getDefinition().getName());\n        String deploymentId = lbAppDeployer.deploy(request);\n        Timeout timeout = deploymentTimeout();\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> {\n                    assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.deployed);\n                });\n\n        Map<String, String> selector = Collections.singletonMap(AbstractKubernetesDeployer.SPRING_APP_KEY, deploymentId);\n        PodSpec spec = kubernetesClient.pods().withLabels(selector).list().getItems().get(0).getSpec();\n        assertThat(spec.getVolumes()).isNotNull();\n        Volume volume = spec.getVolumes().stream()\n                .filter(v -> mountName.equals(v.getName()))\n                .findAny()\n                .orElseThrow(() -> new AssertionError(\"Volume not mounted\"));\n        assertThat(volume.getHostPath()).isNotNull();\n        assertThat(hostPathVolumeSource.getPath()).isEqualTo(volume.getHostPath().getPath());\n\n        logger.info(\"Undeploying {}...\", deploymentId);\n        timeout = undeploymentTimeout();\n        lbAppDeployer.undeploy(deploymentId);\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> {\n                    assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.unknown);\n                });\n    }\n\n    private void verifyAppEnv(String appId) {\n        String ip = \"\";\n        int port = 0;\n\n        KubernetesDeployerProperties properties = new KubernetesDeployerProperties();\n        boolean success = false;\n\n        Service svc = kubernetesClient.services().withName(appId).get();\n        RestTemplate restTemplate = new RestTemplate();\n\n        if (svc != null && \"LoadBalancer\".equals(svc.getSpec().getType())) {\n            int tries = 0;\n            int maxWait = properties.getMinutesToWaitForLoadBalancer() * 6; // we check 6 times per minute\n            while (tries++ < maxWait && !success) {\n                if (svc.getStatus() != null && svc.getStatus().getLoadBalancer() != null &&\n                        svc.getStatus().getLoadBalancer().getIngress() != null &&\n                        !(svc.getStatus().getLoadBalancer().getIngress().isEmpty())) {\n                    ip = svc.getStatus().getLoadBalancer().getIngress().get(0).getIp();\n                    if (ip == null) {\n                        ip = svc.getStatus().getLoadBalancer().getIngress().get(0).getHostname();\n                    }\n                    port = svc.getSpec().getPorts().get(0).getPort();\n                    success = true;\n                    try {\n                        String url = String.format(\"http://%s:%d/actuator/env\", ip, port);\n                        restTemplate.exchange(url,\n                                HttpMethod.GET, HttpEntity.EMPTY,\n                                new ParameterizedTypeReference<LinkedHashMap<String, ArrayList<LinkedHashMap>>>() {\n                                    @Override\n                                    public Type getType() {\n                                        return Map.class;\n                                    }\n                                });\n                    } catch (ResourceAccessException rae) {\n                        success = false;\n                        try {\n                            Thread.sleep(5000L);\n                        } catch (InterruptedException e) {\n                        }\n                    }\n                } else {\n                    try {\n                        Thread.sleep(5000L);\n                    } catch (InterruptedException e) {\n                    }\n                    svc = kubernetesClient.services().withName(appId).get();\n                }\n            }\n            logger.debug(String.format(\"LoadBalancer Ingress: %s\",\n                    svc.getStatus().getLoadBalancer().getIngress().toString()));\n        }\n\n        assertThat(success).as(\"cannot get service information for \" + appId).isFalse();\n\n        String url = String.format(\"http://%s:%d/actuator/env\", ip, port);\n        logger.debug(\"getting app environment from \" + url);\n        restTemplate = new RestTemplate();\n\n        ResponseEntity<LinkedHashMap<String, ArrayList<LinkedHashMap>>> response = restTemplate.exchange(url,\n                HttpMethod.GET, HttpEntity.EMPTY,\n                new ParameterizedTypeReference<LinkedHashMap<String, ArrayList<LinkedHashMap>>>() {\n                    @Override\n                    public Type getType() {\n                        return Map.class;\n                    }\n                });\n\n        LinkedHashMap<String, ArrayList<LinkedHashMap>> env = response.getBody();\n        ArrayList<LinkedHashMap> propertySources = env.get(\"propertySources\");\n\n        String hostName = null;\n        String instanceIndex = null;\n\n        for (LinkedHashMap propertySource : propertySources) {\n            if (propertySource.get(\"name\").equals(\"systemEnvironment\")) {\n                LinkedHashMap s = (LinkedHashMap) propertySource.get(\"properties\");\n                hostName = (String) ((LinkedHashMap) s.get(\"HOSTNAME\")).get(\"value\");\n            }\n\n            if (propertySource.get(\"name\").equals(\"applicationConfig: [file:./config/application.properties]\")) {\n                LinkedHashMap s = (LinkedHashMap) propertySource.get(\"properties\");\n                instanceIndex = (String) ((LinkedHashMap) s.get(\"INSTANCE_INDEX\")).get(\"value\");\n            }\n        }\n\n        assertThat(hostName).as(\"Hostname is null\").isNotNull();\n        assertThat(instanceIndex).as(\"Instance index is null\").isNotNull();\n\n        String expectedIndex = hostName.substring(hostName.lastIndexOf(\"-\") + 1);\n        assertThat(instanceIndex).isEqualTo(expectedIndex);\n    }\n\n    @Test\n    @Disabled(\"Disabled until we can test lbs\")\n    public void testDeploymentWithGroupAndIndex() throws IOException {\n        logger.info(\"Testing {}...\", \"DeploymentWithWithGroupAndIndex\");\n        KubernetesDeployerProperties deployProperties = new KubernetesDeployerProperties();\n        deployProperties.setCreateLoadBalancer(true);\n        deployProperties.setMinutesToWaitForLoadBalancer(1);\n        KubernetesAppDeployer testAppDeployer = kubernetesAppDeployer(deployProperties);\n\n        Map<String, String> appProperties = new HashMap<>();\n        appProperties.put(\"security.basic.enabled\", \"false\");\n        appProperties.put(\"management.security.enabled\", \"false\");\n        AppDefinition definition = new AppDefinition(randomName(), appProperties);\n        Resource resource = testApplication();\n        Map<String, String> props = new HashMap<>();\n        props.put(AppDeployer.GROUP_PROPERTY_KEY, \"foo\");\n        props.put(AppDeployer.INDEXED_PROPERTY_KEY, \"true\");\n\n        AppDeploymentRequest request = new AppDeploymentRequest(definition, resource, props);\n\n        logger.info(\"Deploying {}...\", request.getDefinition().getName());\n        String deploymentId = testAppDeployer.deploy(request);\n        Timeout timeout = deploymentTimeout();\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> {\n                    assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.deployed);\n                });\n\n        Map<String, String> selector = Collections.singletonMap(AbstractKubernetesDeployer.SPRING_APP_KEY, deploymentId);\n        PodSpec spec = kubernetesClient.pods().withLabels(selector).list().getItems().get(0).getSpec();\n\n        Map<String, String> envVars = new HashMap<>();\n        for (EnvVar e : spec.getContainers().get(0).getEnv()) {\n            envVars.put(e.getName(), e.getValue());\n        }\n        assertThat(envVars).contains(entry(\"SPRING_CLOUD_APPLICATION_GROUP\", \"foo\"));\n        verifyAppEnv(deploymentId);\n\n        logger.info(\"Undeploying {}...\", deploymentId);\n        timeout = undeploymentTimeout();\n        testAppDeployer.undeploy(deploymentId);\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> {\n                    assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.unknown);\n                });\n    }\n\n    @Test\n    public void testDeploymentServiceAccountName() {\n        logger.info(\"Testing {}...\", \"DeploymentServiceAccountName\");\n\n        ServiceAccount deploymentServiceAccount = new ServiceAccountBuilder().withNewMetadata().withName(\"appsa\")\n                .endMetadata().build();\n\n        this.kubernetesClient.serviceAccounts().create(deploymentServiceAccount);\n\n        String serviceAccountName = deploymentServiceAccount.getMetadata().getName();\n\n        KubernetesDeployerProperties deployProperties = new KubernetesDeployerProperties();\n        deployProperties.setDeploymentServiceAccountName(serviceAccountName);\n\n        ContainerFactory containerFactory = new DefaultContainerFactory(deployProperties);\n        KubernetesAppDeployer appDeployer = kubernetesAppDeployer(deployProperties);\n\n        AppDefinition definition = new AppDefinition(randomName(), null);\n        AppDeploymentRequest request = new AppDeploymentRequest(definition, testApplication());\n\n        logger.info(\"Deploying {}...\", request.getDefinition().getName());\n        String deploymentId = appDeployer.deploy(request);\n        Timeout timeout = deploymentTimeout();\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> {\n                    assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.deployed);\n                });\n\n        logger.info(\"Undeploying {}...\", deploymentId);\n        timeout = undeploymentTimeout();\n        appDeployer.undeploy(deploymentId);\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> {\n                    assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.unknown);\n                });\n\n        kubernetesClient.serviceAccounts().delete(deploymentServiceAccount);\n    }\n\n    @Test\n    public void testCreateStatefulSet() throws Exception {\n        Map<String, String> props = new HashMap<>();\n        props.put(AppDeployer.COUNT_PROPERTY_KEY, \"3\");\n        props.put(AppDeployer.INDEXED_PROPERTY_KEY, \"true\");\n\n        AppDefinition definition = new AppDefinition(randomName(), null);\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, testApplication(), props);\n\n        KubernetesAppDeployer deployer = kubernetesAppDeployer();\n\n        logger.info(\"Deploying {}...\", appDeploymentRequest.getDefinition().getName());\n        String deploymentId = deployer.deploy(appDeploymentRequest);\n        Map<String, String> idMap = deployer.createIdMap(deploymentId, appDeploymentRequest);\n\n        Timeout timeout = deploymentTimeout();\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> {\n                    assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.deployed);\n                });\n\n        Map<String, String> selector = Collections.singletonMap(AbstractKubernetesDeployer.SPRING_APP_KEY, deploymentId);\n\n        List<StatefulSet> statefulSets = kubernetesClient.apps().statefulSets().withLabels(selector).list().getItems();\n\n        assertThat(statefulSets).hasSize(1);\n\n        StatefulSet statefulSet = statefulSets.get(0);\n\n        StatefulSetSpec statefulSetSpec = statefulSet.getSpec();\n\n        List<Container> statefulSetInitContainers = statefulSetSpec.getTemplate().getSpec().getInitContainers();\n        assertThat(statefulSetInitContainers).hasSize(1);\n        Container statefulSetInitContainer = statefulSetInitContainers.get(0);\n        assertThat(statefulSetInitContainer.getImage()).isEqualTo(DeploymentPropertiesResolver.STATEFUL_SET_IMAGE_NAME);\n\t\tassertThat(statefulSetInitContainer.getSecurityContext()).isNull();\n\n        Assertions.assertThat(statefulSetSpec.getPodManagementPolicy()).isEqualTo(\"Parallel\");\n        Assertions.assertThat(statefulSetSpec.getReplicas()).isEqualTo(3);\n        Assertions.assertThat(statefulSetSpec.getServiceName()).isEqualTo(deploymentId);\n\n        Assertions.assertThat(statefulSet.getMetadata().getName()).isEqualTo(deploymentId);\n\n        Assertions.assertThat(statefulSetSpec.getSelector().getMatchLabels())\n                .containsAllEntriesOf(deployer.createIdMap(deploymentId, appDeploymentRequest));\n        Assertions.assertThat(statefulSetSpec.getSelector().getMatchLabels())\n                .contains(entry(KubernetesAppDeployer.SPRING_MARKER_KEY, KubernetesAppDeployer.SPRING_MARKER_VALUE));\n\n        Assertions.assertThat(statefulSetSpec.getTemplate().getMetadata().getLabels()).containsAllEntriesOf(idMap);\n        Assertions.assertThat(statefulSetSpec.getTemplate().getMetadata().getLabels())\n                .contains(entry(KubernetesAppDeployer.SPRING_MARKER_KEY, KubernetesAppDeployer.SPRING_MARKER_VALUE));\n\n        Container container = statefulSetSpec.getTemplate().getSpec().getContainers().get(0);\n\n        Assertions.assertThat(container.getName()).isEqualTo(deploymentId);\n        Assertions.assertThat(container.getPorts().get(0).getContainerPort()).isEqualTo(8080);\n        Assertions.assertThat(container.getImage()).isEqualTo(testApplication().getURI().getSchemeSpecificPart());\n\n        PersistentVolumeClaim pvc = statefulSetSpec.getVolumeClaimTemplates().get(0);\n        Assertions.assertThat(pvc.getMetadata().getName()).isEqualTo(deploymentId);\n\n        PersistentVolumeClaimSpec pvcSpec = pvc.getSpec();\n        Assertions.assertThat(pvcSpec.getAccessModes()).containsOnly(\"ReadWriteOnce\");\n        Assertions.assertThat(pvcSpec.getStorageClassName()).isNull();\n\n        Assertions.assertThat(pvcSpec.getResources().getLimits().get(\"storage\").getAmount()).isEqualTo(\"10\");\n        Assertions.assertThat(pvcSpec.getResources().getRequests().get(\"storage\").getAmount()).isEqualTo(\"10\");\n\n        Assertions.assertThat(pvcSpec.getResources().getLimits().get(\"storage\").getFormat()).isEqualTo(\"Mi\");\n        Assertions.assertThat(pvcSpec.getResources().getRequests().get(\"storage\").getFormat()).isEqualTo(\"Mi\");\n\n        logger.info(\"Undeploying {}...\", deploymentId);\n        timeout = undeploymentTimeout();\n        appDeployer.undeploy(deploymentId);\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> {\n                    assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.unknown);\n                });\n    }\n\n    @Test\n    public void testCreateStatefulSetInitContainerImageNamePropOverride() throws Exception {\n        Map<String, String> props = new HashMap<>();\n        props.put(AppDeployer.COUNT_PROPERTY_KEY, \"3\");\n        props.put(AppDeployer.INDEXED_PROPERTY_KEY, \"true\");\n\n        String imageName = testApplication().getURI().getSchemeSpecificPart();\n\n        props.put(\"spring.cloud.deployer.kubernetes.statefulSetInitContainerImageName\", imageName);\n\n        AppDefinition definition = new AppDefinition(randomName(), null);\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, testApplication(), props);\n\n        KubernetesAppDeployer deployer = kubernetesAppDeployer();\n\n        logger.info(\"Deploying {}...\", appDeploymentRequest.getDefinition().getName());\n        String deploymentId = deployer.deploy(appDeploymentRequest);\n\n        Timeout timeout = deploymentTimeout();\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> {\n                    assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.deployed);\n                });\n\n        Map<String, String> selector = Collections.singletonMap(AbstractKubernetesDeployer.SPRING_APP_KEY, deploymentId);\n\n        List<StatefulSet> statefulSets = kubernetesClient.apps().statefulSets().withLabels(selector).list().getItems();\n\n        assertThat(statefulSets).hasSize(1);\n\n        StatefulSet statefulSet = statefulSets.get(0);\n\n        StatefulSetSpec statefulSetSpec = statefulSet.getSpec();\n\n        List<Container> statefulSetInitContainers = statefulSetSpec.getTemplate().getSpec().getInitContainers();\n        assertThat(statefulSetInitContainers).hasSize(1);\n        Container statefulSetInitContainer = statefulSetInitContainers.get(0);\n        assertThat(statefulSetInitContainer.getImage()).isEqualTo(imageName);\n\n        logger.info(\"Undeploying {}...\", deploymentId);\n        timeout = undeploymentTimeout();\n        appDeployer.undeploy(deploymentId);\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> {\n                    assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.unknown);\n                });\n    }\n\n    @Test\n    public void createStatefulSetInitContainerImageNameGlobalOverride() throws Exception {\n        Map<String, String> props = new HashMap<>();\n        props.put(AppDeployer.COUNT_PROPERTY_KEY, \"3\");\n        props.put(AppDeployer.INDEXED_PROPERTY_KEY, \"true\");\n\n        AppDefinition definition = new AppDefinition(randomName(), null);\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, testApplication(), props);\n\n        String imageName = testApplication().getURI().getSchemeSpecificPart();\n\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n        kubernetesDeployerProperties.setStatefulSetInitContainerImageName(imageName);\n\n        KubernetesAppDeployer deployer = kubernetesAppDeployer(kubernetesDeployerProperties);\n\n        logger.info(\"Deploying {}...\", appDeploymentRequest.getDefinition().getName());\n        String deploymentId = deployer.deploy(appDeploymentRequest);\n\n        Timeout timeout = deploymentTimeout();\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> {\n                    assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.deployed);\n                });\n\n        Map<String, String> selector = Collections.singletonMap(AbstractKubernetesDeployer.SPRING_APP_KEY, deploymentId);\n\n        List<StatefulSet> statefulSets = kubernetesClient.apps().statefulSets().withLabels(selector).list().getItems();\n\n        assertThat(statefulSets).hasSize(1);\n\n        StatefulSet statefulSet = statefulSets.get(0);\n\n        StatefulSetSpec statefulSetSpec = statefulSet.getSpec();\n\n        List<Container> statefulSetInitContainers = statefulSetSpec.getTemplate().getSpec().getInitContainers();\n        assertThat(statefulSetInitContainers).hasSize(1);\n        Container statefulSetInitContainer = statefulSetInitContainers.get(0);\n        assertThat(statefulSetInitContainer.getImage()).isEqualTo(imageName);\n\n        logger.info(\"Undeploying {}...\", deploymentId);\n        timeout = undeploymentTimeout();\n        appDeployer.undeploy(deploymentId);\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> {\n                    assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.unknown);\n                });\n    }\n\n    @Test\n    public void createStatefulSetWithOverridingRequest() throws Exception {\n        Map<String, String> props = new HashMap<>();\n        props.put(AppDeployer.COUNT_PROPERTY_KEY, \"3\");\n        props.put(AppDeployer.INDEXED_PROPERTY_KEY, \"true\");\n        props.put(\"spring.cloud.deployer.kubernetes.statefulSet.volumeClaimTemplate.name\", \"mystorage\");\n        props.put(\"spring.cloud.deployer.kubernetes.statefulSet.volumeClaimTemplate.storage\", \"1g\");\n\n        AppDefinition definition = new AppDefinition(randomName(), null);\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, testApplication(), props);\n\n        KubernetesAppDeployer deployer = kubernetesAppDeployer();\n\n        logger.info(\"Deploying {}...\", appDeploymentRequest.getDefinition().getName());\n        String deploymentId = deployer.deploy(appDeploymentRequest);\n        Map<String, String> idMap = deployer.createIdMap(deploymentId, appDeploymentRequest);\n\n        Timeout timeout = deploymentTimeout();\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> {\n                    assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.deployed);\n                });\n\n        Map<String, String> selector = Collections.singletonMap(AbstractKubernetesDeployer.SPRING_APP_KEY, deploymentId);\n\n        StatefulSet statefulSet = kubernetesClient.apps().statefulSets().withLabels(selector).list().getItems().get(0);\n        StatefulSetSpec statefulSetSpec = statefulSet.getSpec();\n\n        Assertions.assertThat(statefulSetSpec.getPodManagementPolicy()).isEqualTo(\"Parallel\");\n        Assertions.assertThat(statefulSetSpec.getReplicas()).isEqualTo(3);\n        Assertions.assertThat(statefulSetSpec.getServiceName()).isEqualTo(deploymentId);\n        Assertions.assertThat(statefulSet.getMetadata().getName()).isEqualTo(deploymentId);\n\n        Assertions.assertThat(statefulSetSpec.getSelector().getMatchLabels())\n                .containsAllEntriesOf(deployer.createIdMap(deploymentId, appDeploymentRequest));\n        Assertions.assertThat(statefulSetSpec.getSelector().getMatchLabels())\n                .contains(entry(KubernetesAppDeployer.SPRING_MARKER_KEY, KubernetesAppDeployer.SPRING_MARKER_VALUE));\n\n        Assertions.assertThat(statefulSetSpec.getTemplate().getMetadata().getLabels()).containsAllEntriesOf(idMap);\n        Assertions.assertThat(statefulSetSpec.getTemplate().getMetadata().getLabels())\n                .contains(entry(KubernetesAppDeployer.SPRING_MARKER_KEY, KubernetesAppDeployer.SPRING_MARKER_VALUE));\n\n        Container container = statefulSetSpec.getTemplate().getSpec().getContainers().get(0);\n\n        Assertions.assertThat(container.getName()).isEqualTo(deploymentId);\n        Assertions.assertThat(container.getPorts().get(0).getContainerPort()).isEqualTo(8080);\n        Assertions.assertThat(container.getImage()).isEqualTo(testApplication().getURI().getSchemeSpecificPart());\n\n        PersistentVolumeClaim pvc = statefulSetSpec.getVolumeClaimTemplates().get(0);\n        Assertions.assertThat(pvc.getMetadata().getName()).isEqualTo(\"mystorage\");\n\n        PersistentVolumeClaimSpec pvcSpec = pvc.getSpec();\n        Assertions.assertThat(pvcSpec.getAccessModes()).containsOnly(\"ReadWriteOnce\");\n\n        Assertions.assertThat(pvcSpec.getResources().getLimits().get(\"storage\").getAmount()).isEqualTo(\"1\");\n        Assertions.assertThat(pvcSpec.getResources().getRequests().get(\"storage\").getAmount()).isEqualTo(\"1\");\n\n        Assertions.assertThat(pvcSpec.getResources().getLimits().get(\"storage\").getFormat()).isEqualTo(\"Gi\");\n        Assertions.assertThat(pvcSpec.getResources().getRequests().get(\"storage\").getFormat()).isEqualTo(\"Gi\");\n\n        logger.info(\"Undeploying {}...\", deploymentId);\n        timeout = undeploymentTimeout();\n        appDeployer.undeploy(deploymentId);\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> {\n                    assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.unknown);\n                });\n    }\n\n    @Test\n    public void createStatefulSetWithPVCDefaultName() throws Exception {\n        Map<String, String> props = new HashMap<>();\n        props.put(AppDeployer.COUNT_PROPERTY_KEY, \"3\");\n        props.put(AppDeployer.INDEXED_PROPERTY_KEY, \"true\");\n        AppDefinition definition = new AppDefinition(randomName(), null);\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, testApplication(), props);\n\n        KubernetesAppDeployer deployer = kubernetesAppDeployer();\n\n        logger.info(\"Deploying {}...\", appDeploymentRequest.getDefinition().getName());\n        String deploymentId = deployer.deploy(appDeploymentRequest);\n        Timeout timeout = deploymentTimeout();\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> {\n                    assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.deployed);\n                });\n\n        Map<String, String> selector = Collections.singletonMap(AbstractKubernetesDeployer.SPRING_APP_KEY, deploymentId);\n\n        StatefulSet statefulSet = kubernetesClient.apps().statefulSets().withLabels(selector).list().getItems().get(0);\n        StatefulSetSpec statefulSetSpec = statefulSet.getSpec();\n        Container container = statefulSetSpec.getTemplate().getSpec().getContainers().get(0);\n\n        Assertions.assertThat(container.getName()).isEqualTo(deploymentId);\n\n        PersistentVolumeClaim pvc = statefulSetSpec.getVolumeClaimTemplates().get(0);\n        Assertions.assertThat(pvc.getMetadata().getName()).isEqualTo(deploymentId);\n\n        logger.info(\"Undeploying {}...\", deploymentId);\n        timeout = undeploymentTimeout();\n        appDeployer.undeploy(deploymentId);\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> {\n                    assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.unknown);\n                });\n    }\n\n    @Test\n    public void testStatefulSetPodAnnotations() {\n        logger.info(\"Testing {}...\", \"StatefulSetPodAnnotations\");\n\n        KubernetesAppDeployer appDeployer = kubernetesAppDeployer();\n\n        AppDefinition definition = new AppDefinition(randomName(), null);\n        Resource resource = testApplication();\n\n        Map<String, String> props = new HashMap<>();\n        props.put(AppDeployer.COUNT_PROPERTY_KEY, \"3\");\n        props.put(AppDeployer.INDEXED_PROPERTY_KEY, \"true\");\n        props.put(\"spring.cloud.deployer.kubernetes.podAnnotations\",\n                \"iam.amazonaws.com/role:role-arn,foo:bar\");\n        AppDeploymentRequest request = new AppDeploymentRequest(definition, resource, props);\n\n        logger.info(\"Deploying {}...\", request.getDefinition().getName());\n        Timeout timeout = deploymentTimeout();\n        String deploymentId = appDeployer.deploy(request);\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> {\n                    assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.deployed);\n                });\n\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> {\n                    assertThat(appDeployer().status(deploymentId).getInstances()).hasSize(3);\n                });\n\n        // Ensure that a StatefulSet is deployed\n        Map<String, String> selector = Collections.singletonMap(AbstractKubernetesDeployer.SPRING_APP_KEY, deploymentId);\n        List<StatefulSet> statefulSets = kubernetesClient.apps().statefulSets().withLabels(selector).list().getItems();\n        assertThat(statefulSets).isNotNull();\n        assertThat(statefulSets).hasSize(1);\n        StatefulSet statefulSet = statefulSets.get(0);\n        StatefulSetSpec statefulSetSpec = statefulSet.getSpec();\n\n        Map<String, String> annotations = statefulSetSpec.getTemplate().getMetadata().getAnnotations();\n        assertThat(annotations.get(\"iam.amazonaws.com/role\")).isEqualTo(\"role-arn\");\n        assertThat(annotations.get(\"foo\")).isEqualTo(\"bar\");\n        appDeployer.undeploy(deploymentId);\n    }\n\n    @Test\n    public void testDeploymentLabels() {\n        Map<String, String> props = Collections.singletonMap(\"spring.cloud.deployer.kubernetes.deploymentLabels\",\n                \"label1:value1,label2:value2\");\n\n        AppDefinition definition = new AppDefinition(randomName(), null);\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, testApplication(), props);\n\n        KubernetesAppDeployer deployer = kubernetesAppDeployer();\n\n        logger.info(\"Deploying {}...\", appDeploymentRequest.getDefinition().getName());\n\n        String deploymentId = deployer.deploy(appDeploymentRequest);\n\n        Timeout timeout = deploymentTimeout();\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> {\n                    assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.deployed);\n                });\n\n        Map<String, String> selector = Collections.singletonMap(AbstractKubernetesDeployer.SPRING_APP_KEY, deploymentId);\n\n        List<Deployment> deployments = kubernetesClient.apps().deployments().withLabels(selector).list().getItems();\n\n        Map<String, String> specLabels = deployments.get(0).getSpec().getTemplate().getMetadata().getLabels();\n\n        assertThat(specLabels.containsKey(\"label1\")).as(\"Label 'label1' not found in deployment spec\").isTrue();\n        assertThat(specLabels.get(\"label1\")).as(\"Unexpected value for label1\").isEqualTo(\"value1\");\n        assertThat(specLabels).as(\"Label 'label2' not found in deployment spec\").containsKey(\"label2\");\n        assertThat(specLabels.get(\"label2\")).as(\"Unexpected value for label1\").isEqualTo(\"value2\");\n\n        logger.info(\"Undeploying {}...\", deploymentId);\n        timeout = undeploymentTimeout();\n        appDeployer.undeploy(deploymentId);\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> {\n                    assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.unknown);\n                });\n    }\n\n    @Test\n    public void testDeploymentLabelsStatefulSet() {\n        logger.info(\"Testing {}...\", \"DeploymentLabelsForStatefulSet\");\n        Map<String, String> props = new HashMap<>();\n        props.put(AppDeployer.COUNT_PROPERTY_KEY, \"2\");\n        props.put(AppDeployer.INDEXED_PROPERTY_KEY, \"true\");\n        props.put(\"spring.cloud.deployer.kubernetes.deploymentLabels\",\n                \"stateful-label1:stateful-value1,stateful-label2:stateful-value2\");\n        AppDefinition definition = new AppDefinition(randomName(), null);\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, testApplication(), props);\n\n        KubernetesAppDeployer deployer = kubernetesAppDeployer();\n\n        logger.info(\"Deploying {}...\", appDeploymentRequest.getDefinition().getName());\n\n        String deploymentId = deployer.deploy(appDeploymentRequest);\n\n        Timeout timeout = deploymentTimeout();\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> {\n                    assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.deployed);\n                });\n        Map<String, String> idMap = deployer.createIdMap(deploymentId, appDeploymentRequest);\n        Map<String, String> selector = Collections.singletonMap(AbstractKubernetesDeployer.SPRING_APP_KEY, deploymentId);\n\n        StatefulSet statefulSet = kubernetesClient.apps().statefulSets().withLabels(selector).list().getItems().get(0);\n        StatefulSetSpec statefulSetSpec = statefulSet.getSpec();\n        Assertions.assertThat(statefulSetSpec.getReplicas()).isEqualTo(2);\n        Assertions.assertThat(statefulSetSpec.getTemplate().getMetadata().getLabels()).containsAllEntriesOf(idMap);\n\n        //verify stateful set match labels\n        Map<String, String> setLabels = statefulSet.getMetadata().getLabels();\n        assertThat(setLabels).contains(entry(\"stateful-label1\", \"stateful-value1\"), entry(\"stateful-label2\", \"stateful-value2\"));\n\n        //verify pod template labels\n        Map<String, String> specLabels = statefulSetSpec.getTemplate().getMetadata().getLabels();\n        assertThat(specLabels).contains(entry(\"stateful-label1\", \"stateful-value1\"), entry(\"stateful-label2\", \"stateful-value2\"));\n\n        //verify that labels got replicated to one of the deployments\n        List<Pod> pods = kubernetesClient.pods().withLabels(selector).list().getItems();\n        Map<String, String> podLabels = pods.get(0).getMetadata().getLabels();\n\n        assertThat(podLabels).contains(entry(\"stateful-label1\", \"stateful-value1\"), entry(\"stateful-label2\", \"stateful-value2\"));\n\n        logger.info(\"Undeploying {}...\", deploymentId);\n        timeout = undeploymentTimeout();\n        appDeployer.undeploy(deploymentId);\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> {\n                    assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.unknown);\n                });\n    }\n\n    @Test\n    public void testCleanupOnDeployFailure() throws InterruptedException {\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, testApplication(), null);\n\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n\n        // simulate a pod going into an un-schedulable state\n        KubernetesDeployerProperties.LimitsResources resources = new KubernetesDeployerProperties.LimitsResources();\n        resources.setCpu(\"9000000\");\n\n        kubernetesDeployerProperties.setLimits(resources);\n\n        KubernetesAppDeployer deployer = kubernetesAppDeployer(kubernetesDeployerProperties);\n\n        logger.info(\"Deploying {}...\", appDeploymentRequest.getDefinition().getName());\n\n        String deploymentId = deployer.deploy(appDeploymentRequest);\n\n        Timeout timeout = deploymentTimeout();\n\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> {\n                    assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.unknown);\n                });\n\n        // attempt to undeploy the failed deployment\n        logger.info(\"Undeploying {}...\", deploymentId);\n\n        try {\n            appDeployer.undeploy(deploymentId);\n        } catch (Exception e) {\n            logger.info(\"Got expected not not deployed exception on undeployment: \" + e.getMessage());\n        }\n\n        deployer = kubernetesAppDeployer();\n\n        logger.info(\"Deploying {}... again\", deploymentId);\n\n        // ensure a previous failed deployment with the same name was cleaned up and can be deployed again\n        deployer.deploy(appDeploymentRequest);\n\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> {\n                    assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.deployed);\n                });\n\n        appDeployer.undeploy(deploymentId);\n\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> {\n                    assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.unknown);\n                });\n    }\n\n    @Test\n    public void testMultipleContainersInPod() {\n        logger.info(\"Testing {}...\", \"MultipleContainersInPod\");\n\n        KubernetesAppDeployer kubernetesAppDeployer = Mockito.spy(kubernetesAppDeployer());\n\t\tAppDefinition definition = new AppDefinition(randomName(), Collections.singletonMap(\"server.port\", \"9090\"));\n        Resource resource = testApplication();\n\n        AppDeploymentRequest request = new AppDeploymentRequest(definition, resource);\n\n        doAnswer((Answer<PodSpec>) invocationOnMock -> {\n            PodSpec podSpec = (PodSpec) invocationOnMock.callRealMethod();\n\n            Container container = new ContainerBuilder().withName(\"asecondcontainer\")\n                    .withImage(resource.getURI().getSchemeSpecificPart()).build();\n\n            podSpec.getContainers().add(container);\n\n            return podSpec;\n        }).when(kubernetesAppDeployer).createPodSpec(Mockito.any(AppDeploymentRequest.class));\n\n        logger.info(\"Deploying {}...\", request.getDefinition().getName());\n\n        String deploymentId = kubernetesAppDeployer.deploy(request);\n        Timeout timeout = deploymentTimeout();\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> {\n                    assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.deployed);\n                });\n\n        logger.info(\"Undeploying {}...\", deploymentId);\n        timeout = undeploymentTimeout();\n        kubernetesAppDeployer.undeploy(deploymentId);\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> {\n                    assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.unknown);\n                });\n    }\n\n    @Test\n    public void testDefaultServicePort() {\n        logger.info(\"Testing {}...\", \"DefaultServicePort\");\n        KubernetesAppDeployer kubernetesAppDeployer = kubernetesAppDeployer();\n\n        AppDefinition definition = new AppDefinition(randomName(), null);\n        Resource resource = testApplication();\n        AppDeploymentRequest request = new AppDeploymentRequest(definition, resource);\n\n        logger.info(\"Deploying {}...\", request.getDefinition().getName());\n        String deploymentId = kubernetesAppDeployer.deploy(request);\n        Timeout timeout = deploymentTimeout();\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> {\n                    assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.deployed);\n                });\n\n        List<ServicePort> servicePorts = kubernetesClient.services().withName(request.getDefinition().getName()).get()\n                .getSpec().getPorts();\n\n        assertThat(servicePorts).hasSize(1);\n        assertThat(servicePorts.get(0).getPort()).isEqualTo(8080);\n        assertThat(servicePorts.get(0).getName()).isEqualTo(\"port-8080\");\n\n        logger.info(\"Undeploying {}...\", deploymentId);\n        timeout = undeploymentTimeout();\n        kubernetesAppDeployer.undeploy(deploymentId);\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> {\n                    assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.unknown);\n                });\n    }\n\n    @Test\n    public void testDefaultServicePortOverride() {\n        logger.info(\"Testing {}...\", \"DefaultServicePortOverride\");\n        KubernetesAppDeployer kubernetesAppDeployer = kubernetesAppDeployer();\n\n        AppDefinition definition = new AppDefinition(randomName(), Collections.singletonMap(\"server.port\", \"9090\"));\n        Resource resource = testApplication();\n        AppDeploymentRequest request = new AppDeploymentRequest(definition, resource);\n\n        logger.info(\"Deploying {}...\", request.getDefinition().getName());\n        String deploymentId = kubernetesAppDeployer.deploy(request);\n        Timeout timeout = deploymentTimeout();\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> {\n                    assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.deployed);\n                });\n\n        List<ServicePort> servicePorts = kubernetesClient.services().withName(request.getDefinition().getName()).get()\n                .getSpec().getPorts();\n\n        assertThat(servicePorts).hasSize(1);\n        assertThat(servicePorts.get(0).getPort()).isEqualTo(9090);\n        assertThat(servicePorts.get(0).getName()).isEqualTo(\"port-9090\");\n\n        logger.info(\"Undeploying {}...\", deploymentId);\n        timeout = undeploymentTimeout();\n        kubernetesAppDeployer.undeploy(deploymentId);\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> {\n                    assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.unknown);\n                });\n    }\n\n    @Test\n    public void testServiceWithMultiplePorts() {\n        logger.info(\"Testing {}...\", \"ServiceWithMultiplePorts\");\n        KubernetesAppDeployer kubernetesAppDeployer = kubernetesAppDeployer();\n\n        AppDefinition definition = new AppDefinition(randomName(), null);\n        Resource resource = testApplication();\n        AppDeploymentRequest request = new AppDeploymentRequest(definition, resource,\n                Collections.singletonMap(\"spring.cloud.deployer.kubernetes.servicePorts\", \"8080,9090\"));\n\n        logger.info(\"Deploying {}...\", request.getDefinition().getName());\n        String deploymentId = kubernetesAppDeployer.deploy(request);\n        Timeout timeout = deploymentTimeout();\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> {\n                    assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.deployed);\n                });\n\n        List<ServicePort> servicePorts = kubernetesClient.services().withName(request.getDefinition().getName()).get()\n                .getSpec().getPorts();\n\n        assertThat(servicePorts).hasSize(2);\n        assertThat(servicePorts.stream().anyMatch(o -> o.getPort().equals(8080))).isTrue();\n        assertThat(servicePorts.stream().anyMatch(o -> o.getName().equals(\"port-8080\"))).isTrue();\n        assertThat(servicePorts.stream().anyMatch(o -> o.getPort().equals(9090))).isTrue();\n        assertThat(servicePorts.stream().anyMatch(o -> o.getName().equals(\"port-9090\"))).isTrue();\n\n        logger.info(\"Undeploying {}...\", deploymentId);\n        timeout = undeploymentTimeout();\n        kubernetesAppDeployer.undeploy(deploymentId);\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> {\n                    assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.unknown);\n                });\n    }\n\n    @Test\n    public void testCreateInitContainer() {\n        logger.info(\"Testing {}...\", \"CreateInitContainer\");\n        KubernetesAppDeployer kubernetesAppDeployer = kubernetesAppDeployer();\n\n        Map<String, String> props = Collections.singletonMap(\"spring.cloud.deployer.kubernetes.initContainer\",\n                \"{containerName: 'test', imageName: 'busybox:latest', commands: ['sh', '-c', 'echo hello']}\");\n\n        AppDefinition definition = new AppDefinition(randomName(), null);\n        AppDeploymentRequest request = new AppDeploymentRequest(definition, testApplication(), props);\n\n        logger.info(\"Deploying {}...\", request.getDefinition().getName());\n        String deploymentId = kubernetesAppDeployer.deploy(request);\n        Timeout timeout = deploymentTimeout();\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> {\n                    assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.deployed);\n                });\n\n        Deployment deployment = kubernetesClient.apps().deployments().withName(request.getDefinition().getName()).get();\n        List<Container> initContainers = deployment.getSpec().getTemplate().getSpec().getInitContainers();\n\n        Optional<Container> initContainer = initContainers.stream().filter(i -> i.getName().equals(\"test\")).findFirst();\n        assertThat(initContainer.isPresent()).as(\"Init container not found\").isTrue();\n\n        Container testInitContainer = initContainer.get();\n\n        assertThat(testInitContainer.getName()).as(\"Unexpected init container name\").isEqualTo(\"test\");\n        assertThat(testInitContainer.getImage()).as(\"Unexpected init container image\").isEqualTo(\"busybox:latest\");\n\n        List<String> commands = testInitContainer.getCommand();\n\n        assertThat(commands).contains(\"sh\", \"-c\", \"echo hello\");\n\n        logger.info(\"Undeploying {}...\", deploymentId);\n        timeout = undeploymentTimeout();\n        kubernetesAppDeployer.undeploy(deploymentId);\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> {\n                    assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.unknown);\n                });\n    }\n\n    @Test\n    public void testCreateInitContainerWithEnvVariables() {\n        logger.info(\"Testing {}...\", \"CreateInitContainer\");\n        KubernetesAppDeployer kubernetesAppDeployer = kubernetesAppDeployer();\n\n        Map<String, String> props = Collections.singletonMap(\"spring.cloud.deployer.kubernetes.initContainer\",\n                \"{containerName: 'test', imageName: 'busybox:latest', commands: ['sh', '-c', 'echo hello'], environmentVariables: ['KEY1=VAL1', 'KEY2=VAL2']}\");\n\n        AppDefinition definition = new AppDefinition(randomName(), null);\n        AppDeploymentRequest request = new AppDeploymentRequest(definition, testApplication(), props);\n\n        logger.info(\"Deploying {}...\", request.getDefinition().getName());\n        String deploymentId = kubernetesAppDeployer.deploy(request);\n        Timeout timeout = deploymentTimeout();\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> {\n                    assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.deployed);\n                });\n\n        Deployment deployment = kubernetesClient.apps().deployments().withName(request.getDefinition().getName()).get();\n        List<Container> initContainers = deployment.getSpec().getTemplate().getSpec().getInitContainers();\n\n        Optional<Container> initContainer = initContainers.stream().filter(i -> i.getName().equals(\"test\")).findFirst();\n        assertThat(initContainer.isPresent()).as(\"Init container not found\").isTrue();\n\n        Container testInitContainer = initContainer.get();\n\n        List<EnvVar> containerEnvs = testInitContainer.getEnv();\n\n        assertThat(containerEnvs).hasSize(2);\n        assertThat(containerEnvs.stream().map(EnvVar::getName).collect(Collectors.toList())).contains(\"KEY1\", \"KEY2\");\n        assertThat(containerEnvs.stream().map(EnvVar::getValue).collect(Collectors.toList())).contains(\"VAL1\", \"VAL2\");\n\n        logger.info(\"Undeploying {}...\", deploymentId);\n        timeout = undeploymentTimeout();\n        kubernetesAppDeployer.undeploy(deploymentId);\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> {\n                    assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.unknown);\n                });\n    }\n\n    @Test\n    public void testCreateInitContainerWithVolumeMounts() {\n        logger.info(\"Testing {}...\", \"CreateInitContainerWithVolumeMounts\");\n        KubernetesAppDeployer kubernetesAppDeployer = kubernetesAppDeployer();\n\n        Map<String, String> props = Stream.of(new String[][]{\n                {\n                        \"spring.cloud.deployer.kubernetes.volumes\",\n                        \"[{name: 'test-volume', emptyDir: {}}]\",\n                },\n                {\n                        \"spring.cloud.deployer.kubernetes.volumeMounts\",\n                        \"[{name: 'test-volume', mountPath: '/tmp'}]\",\n                },\n                {\n                        \"spring.cloud.deployer.kubernetes.initContainer\",\n                        \"{containerName: 'test', imageName: 'busybox:latest', commands: ['sh', '-c', 'echo hello'], \" +\n                                \"volumeMounts: [{name: 'test-volume', mountPath: '/tmp', readOnly: true}]}\",\n                }\n        }).collect(Collectors.toMap(data -> data[0], data -> data[1]));\n\n        AppDefinition definition = new AppDefinition(randomName(), null);\n        AppDeploymentRequest request = new AppDeploymentRequest(definition, testApplication(), props);\n\n        logger.info(\"Deploying {}...\", request.getDefinition().getName());\n        String deploymentId = kubernetesAppDeployer.deploy(request);\n        Timeout timeout = deploymentTimeout();\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> {\n                    assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.deployed);\n                });\n\n        Deployment deployment = kubernetesClient.apps().deployments().withName(request.getDefinition().getName()).get();\n        List<Container> initContainers = deployment.getSpec().getTemplate().getSpec().getInitContainers();\n\n        Optional<Container> initContainer = initContainers.stream().filter(i -> i.getName().equals(\"test\")).findFirst();\n        assertThat(initContainer.isPresent()).as(\"Init container not found\").isTrue();\n\n        Container testInitContainer = initContainer.get();\n\n        assertThat(testInitContainer.getName()).as(\"Unexpected init container name\").isEqualTo(\"test\");\n        assertThat(testInitContainer.getImage()).as(\"Unexpected init container image\").isEqualTo(\"busybox:latest\");\n\n        List<String> commands = testInitContainer.getCommand();\n\n        assertThat(commands != null && !commands.isEmpty()).as(\"Init container commands missing\").isTrue();\n        assertThat(commands).hasSize(3);\n        assertThat(commands).contains(\"sh\", \"-c\", \"echo hello\");\n\n        List<VolumeMount> volumeMounts = testInitContainer.getVolumeMounts();\n        assertThat(volumeMounts != null && !volumeMounts.isEmpty()).as(\"Init container volumeMounts missing\").isTrue();\n        assertThat(volumeMounts).hasSize(1);\n\n        VolumeMount vm = volumeMounts.get(0);\n        assertThat(vm.getName()).as(\"Unexpected init container volume mount name\").isEqualTo(\"test-volume\");\n        assertThat(vm.getMountPath()).as(\"Unexpected init container volume mount path\").isEqualTo(\"/tmp\");\n        assertThat(vm.getReadOnly()).as(\"Expected read only volume mount\").isTrue();\n\n        logger.info(\"Undeploying {}...\", deploymentId);\n        timeout = undeploymentTimeout();\n        kubernetesAppDeployer.undeploy(deploymentId);\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> {\n                    assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.unknown);\n                });\n    }\n\n    @Test\n    public void testCreateAdditionalContainers() {\n        logger.info(\"Testing {}...\", \"CreateAdditionalContainers\");\n        KubernetesAppDeployer kubernetesAppDeployer = kubernetesAppDeployer();\n\n        Map<String, String> props = Stream.of(new String[][]{\n                {\n                        \"spring.cloud.deployer.kubernetes.volumes\",\n                        \"[{name: 'test-volume', emptyDir: {}}]\",\n                },\n                {\n                        \"spring.cloud.deployer.kubernetes.volumeMounts\",\n                        \"[{name: 'test-volume', mountPath: '/tmp'}]\",\n                },\n                {\n                        \"spring.cloud.deployer.kubernetes.additional-containers\",\n                        \"[{name: 'c1', image: 'busybox:latest', command: ['sh', '-c', 'echo hello1'], volumeMounts: [{name: 'test-volume', mountPath: '/tmp', readOnly: true}]},\"\n                                + \"{name: 'c2', image: 'busybox:1.26.1', command: ['sh', '-c', 'echo hello2']}]\"\n                }}).collect(Collectors.toMap(data -> data[0], data -> data[1]));\n\n        AppDefinition definition = new AppDefinition(randomName(), null);\n        AppDeploymentRequest request = new AppDeploymentRequest(definition, testApplication(), props);\n\n        logger.info(\"Deploying {}...\", request.getDefinition().getName());\n        String deploymentId = kubernetesAppDeployer.deploy(request);\n        Timeout timeout = deploymentTimeout();\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> {\n                    assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.deployed);\n                });\n\n        Deployment deployment = kubernetesClient.apps().deployments().withName(request.getDefinition().getName()).get();\n        List<Container> containers = deployment.getSpec().getTemplate().getSpec().getContainers();\n\n        assertThat(containers).hasSize(3);\n\n        Optional<Container> additionalContainer1 = containers.stream().filter(i -> i.getName().equals(\"c1\")).findFirst();\n        assertThat(additionalContainer1.isPresent()).isTrue();\n\n        Container testAdditionalContainer1 = additionalContainer1.get();\n\n        assertThat(testAdditionalContainer1.getName()).as(\"Unexpected additional container name\").isEqualTo(\"c1\");\n        assertThat(testAdditionalContainer1.getImage()).as(\"Unexpected additional container image\").isEqualTo(\"busybox:latest\");\n\n        List<String> commands = testAdditionalContainer1.getCommand();\n\n        assertThat(commands).contains(\"sh\", \"-c\", \"echo hello1\");\n\n        List<VolumeMount> volumeMounts = testAdditionalContainer1.getVolumeMounts();\n\n        assertThat(volumeMounts).hasSize(1);\n        assertThat(volumeMounts.get(0).getName()).isEqualTo(\"test-volume\");\n        assertThat(volumeMounts.get(0).getMountPath()).isEqualTo(\"/tmp\");\n        assertThat(volumeMounts.get(0).getReadOnly()).isTrue();\n\n        Optional<Container> additionalContainer2 = containers.stream().filter(i -> i.getName().equals(\"c2\")).findFirst();\n        assertThat(additionalContainer2.isPresent()).as(\"Additional container c2 not found\").isTrue();\n\n        Container testAdditionalContainer2 = additionalContainer2.get();\n\n        assertThat(testAdditionalContainer2.getName()).as(\"Unexpected additional container name\").isEqualTo(\"c2\");\n        assertThat(testAdditionalContainer2.getImage()).as(\"Unexpected additional container image\").isEqualTo(\"busybox:1.26.1\");\n\n        List<String> container2Commands = testAdditionalContainer2.getCommand();\n\n        assertThat(container2Commands).contains(\"sh\", \"-c\", \"echo hello2\");\n\n        logger.info(\"Undeploying {}...\", deploymentId);\n        timeout = undeploymentTimeout();\n        kubernetesAppDeployer.undeploy(deploymentId);\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> {\n                    assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.unknown);\n                });\n    }\n\n    @Test\n    public void testCreateAdditionalContainersOverride() {\n        logger.info(\"Testing {}...\", \"CreateAdditionalContainersOverride\");\n        KubernetesDeployerProperties.Container container1 = new KubernetesDeployerProperties.Container();\n        container1.setName(\"c1\");\n        container1.setImage(\"busybox:1.31.0\");\n        container1.setCommand(Arrays.asList(\"sh\", \"-c\", \"echo hello-from-original-properties\"));\n        KubernetesDeployerProperties.Container container2 = new KubernetesDeployerProperties.Container();\n        container2.setName(\"container2\");\n        container2.setImage(\"busybox:1.31.0\");\n        container2.setCommand(Arrays.asList(\"sh\", \"-c\", \"echo hello-from-original-properties\"));\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n        kubernetesDeployerProperties.setAdditionalContainers(Arrays.asList(container1, container2));\n        KubernetesAppDeployer kubernetesAppDeployer = kubernetesAppDeployer(kubernetesDeployerProperties);\n\n\n        Map<String, String> props = Stream.of(new String[][]{\n                {\n                        \"spring.cloud.deployer.kubernetes.volumes\",\n                        \"[{name: 'test-volume', emptyDir: {}}]\",\n                },\n                {\n                        \"spring.cloud.deployer.kubernetes.volumeMounts\",\n                        \"[{name: 'test-volume', mountPath: '/tmp'}]\",\n                },\n                {\n                        \"spring.cloud.deployer.kubernetes.additional-containers\",\n                        \"[{name: 'c1', image: 'busybox:latest', command: ['sh', '-c', 'echo hello1'], volumeMounts: [{name: 'test-volume', mountPath: '/tmp', readOnly: true}]},\"\n                                + \"{name: 'c2', image: 'busybox:1.26.1', command: ['sh', '-c', 'echo hello2']}]\"\n                }}).collect(Collectors.toMap(data -> data[0], data -> data[1]));\n\n        AppDefinition definition = new AppDefinition(randomName(), null);\n        AppDeploymentRequest request = new AppDeploymentRequest(definition, testApplication(), props);\n\n        logger.info(\"Deploying {}...\", request.getDefinition().getName());\n        String deploymentId = kubernetesAppDeployer.deploy(request);\n        Timeout timeout = deploymentTimeout();\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> {\n                    assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.deployed);\n                });\n\n        Deployment deployment = kubernetesClient.apps().deployments().withName(request.getDefinition().getName()).get();\n        List<Container> containers = deployment.getSpec().getTemplate().getSpec().getContainers();\n\n        assertThat(containers).hasSize(4);\n\n        // c1 from the deployment properties should have overridden the c1 from the original deployer properties\n        Optional<Container> additionalContainer1 = containers.stream().filter(i -> i.getName().equals(\"c1\")).findFirst();\n        assertThat(additionalContainer1.isPresent()).as(\"Additional container c1 not found\").isTrue();\n\n        Container testAdditionalContainer1 = additionalContainer1.get();\n\n        assertThat(testAdditionalContainer1.getName()).as(\"Unexpected additional container name\").isEqualTo(\"c1\");\n        assertThat(testAdditionalContainer1.getImage()).as(\"Unexpected additional container image\").isEqualTo(\"busybox:latest\");\n\n        List<String> commands = testAdditionalContainer1.getCommand();\n\n        assertThat(commands).contains(\"sh\", \"-c\", \"echo hello1\");\n\n        List<VolumeMount> volumeMounts = testAdditionalContainer1.getVolumeMounts();\n\n        assertThat(volumeMounts).hasSize(1);\n        assertThat(volumeMounts.get(0).getName()).isEqualTo(\"test-volume\");\n        assertThat(volumeMounts.get(0).getMountPath()).isEqualTo(\"/tmp\");\n        assertThat(volumeMounts.get(0).getReadOnly()).isTrue();\n\n        Optional<Container> additionalContainer2 = containers.stream().filter(i -> i.getName().equals(\"c2\")).findFirst();\n        assertThat(additionalContainer2.isPresent()).as(\"Additional container c2 not found\").isTrue();\n\n        Container testAdditionalContainer2 = additionalContainer2.get();\n\n        assertThat(testAdditionalContainer2.getName()).as(\"Unexpected additional container name\").isEqualTo(\"c2\");\n        assertThat(testAdditionalContainer2.getImage()).as(\"Unexpected additional container image\").isEqualTo(\"busybox:1.26.1\");\n\n        List<String> container2Commands = testAdditionalContainer2.getCommand();\n\n        assertThat(container2Commands).contains(\"sh\", \"-c\", \"echo hello2\");\n\n        // Verifying the additional container passed from the root deployer properties\n        Optional<Container> additionalContainer3 = containers.stream().filter(i -> i.getName().equals(\"container2\")).findFirst();\n        assertThat(additionalContainer3.isPresent()).as(\"Additional container c2 not found\").isTrue();\n\n        Container testAdditionalContainer3 = additionalContainer3.get();\n\n        assertThat(testAdditionalContainer3.getName()).as(\"Unexpected additional container name\").isEqualTo(\"container2\");\n        assertThat(testAdditionalContainer3.getImage()).as(\"Unexpected additional container image\").isEqualTo(\"busybox:1.31.0\");\n\n        List<String> container3Commands = testAdditionalContainer3.getCommand();\n\n        assertThat(container3Commands).contains(\"sh\", \"-c\", \"echo hello-from-original-properties\");\n\n        logger.info(\"Undeploying {}...\", deploymentId);\n        timeout = undeploymentTimeout();\n        kubernetesAppDeployer.undeploy(deploymentId);\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> {\n                    assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.unknown);\n                });\n    }\n\n\t@Nested\n\tclass SecurityContextITs {\n\n\t\t@Test\n\t\tvoid podWithInitContainerAndAdditionalContainers() {\n\n\t\t\tMap<String, String> deploymentProps = new HashMap<>();\n\t\t\tdeploymentProps.put(\"spring.cloud.deployer.kubernetes.initContainer\",\n\t\t\t\t\t\"{ containerName: 'init-container-5150', imageName: 'busybox:latest', commands: ['sh', '-c', 'echo hello']}\");\n\t\t\tdeploymentProps.put(\"spring.cloud.deployer.kubernetes.additional-containers\",\n\t\t\t\t\t\"[\" +\n\t\t\t\t\t\t\"{ name: 'extra-container-5150', image: 'busybox:latest', command: ['sh', '-c'], args: [\\\"while true; do echo ‘hello 5150’ & sleep 2; done\\\"]},\" +\n\t\t\t\t\t\t\"{ name: 'extra-container-6160', image: 'busybox:latest', command: ['sh', '-c'], args: [\\\"while true; do echo ‘hello 6160’ & sleep 2; done\\\"]}\" +\n\t\t\t\t\t\"]\");\n\t\t\tdeploymentProps.put(\"spring.cloud.deployer.kubernetes.podSecurityContext\",\n\t\t\t\t\t\"{ fsGroup: 65534\" +\n\t\t\t\t\t\t\", fsGroupChangePolicy: Always\" +\n\t\t\t\t\t\t\", runAsUser: 65534\" +\n\t\t\t\t\t\t\", runAsGroup: 65534\" +\n\t\t\t\t\t\t\", seLinuxOptions: { level: \\\"s0:c123,c456\\\" }\" +\n\t\t\t\t\t\"}\");\n\t\t\tdeploymentProps.put(\"spring.cloud.deployer.kubernetes.containerSecurityContext\",\n\t\t\t\t\t\"{ allowPrivilegeEscalation: true\" +\n\t\t\t\t\t\t\", runAsUser: 65534\" +\n\t\t\t\t\t\t\", runAsGroup: 65534\" +\n\t\t\t\t\t\t\", seLinuxOptions: { level: \\\"s0:c123,c777\\\" }\" +\n\t\t\t\t\t\"}\");\n\n\t\t\tAppDefinition definition = new AppDefinition(randomName(), null);\n\t\t\tAppDeploymentRequest request = new AppDeploymentRequest(definition, testApplication(), deploymentProps);\n\n\t\t\tKubernetesDeployerProperties deployProperties = new KubernetesDeployerProperties();\n\t\t\tdeployProperties.setCreateLoadBalancer(true);\n\t\t\tdeployProperties.setMinutesToWaitForLoadBalancer(1);\n\t\t\tKubernetesAppDeployer kubernetesAppDeployer = kubernetesAppDeployer(deployProperties);\n\n\t\t\tlogger.info(\"Deploying {}...\", request.getDefinition().getName());\n\t\t\tString deploymentId = kubernetesAppDeployer.deploy(request);\n\n\t\t\tTimeout timeout = deploymentTimeout();\n\t\t\tawait().pollInterval(Duration.ofMillis(timeout.pause))\n\t\t\t\t\t.atMost(Duration.ofMillis(timeout.totalTime))\n\t\t\t\t\t.untilAsserted(() -> assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.deployed));\n\n\t\t\tPodSecurityContext expectedPodSecurityContext = new PodSecurityContextBuilder()\n\t\t\t\t\t.withFsGroup(65534L)\n\t\t\t\t\t.withFsGroupChangePolicy(\"Always\")\n\t\t\t\t\t.withRunAsUser(65534L)\n\t\t\t\t\t.withRunAsGroup(65534L)\n\t\t\t\t\t.withSeLinuxOptions(new SELinuxOptions(\"s0:c123,c456\", null, null, null))\n\t\t\t\t\t.build();\n\n\t\t\tSecurityContext expectedContainerSecurityContext = new SecurityContextBuilder()\n\t\t\t\t\t.withAllowPrivilegeEscalation(true)\n\t\t\t\t\t.withRunAsUser(65534L)\n\t\t\t\t\t.withRunAsGroup(65534L)\n\t\t\t\t\t.withSeLinuxOptions(new SELinuxOptions(\"s0:c123,c777\", null, null, null))\n\t\t\t\t\t.build();\n\n\t\t\tDeployment deployment = kubernetesClient.apps().deployments().withName(request.getDefinition().getName()).get();\n\n\t\t\tassertThat(deployment.getSpec().getTemplate().getSpec().getSecurityContext())\n\t\t\t\t\t.isEqualTo(expectedPodSecurityContext);\n\n\t\t\tassertThatContainerExistsWithSecurityContext(deployment.getSpec().getTemplate().getSpec().getInitContainers(),\n\t\t\t\t\t\"init-container-5150\", expectedContainerSecurityContext);\n\n\t\t\tassertThatContainerExistsWithSecurityContext(deployment.getSpec().getTemplate().getSpec().getContainers(),\n\t\t\t\t\t\"extra-container-5150\", expectedContainerSecurityContext);\n\n\t\t\tassertThatContainerExistsWithSecurityContext(deployment.getSpec().getTemplate().getSpec().getContainers(),\n\t\t\t\t\t\"extra-container-6160\", expectedContainerSecurityContext);\n\n\t\t\tlogger.info(\"Undeploying {}...\", deploymentId);\n\t\t\ttimeout = undeploymentTimeout();\n\t\t\tkubernetesAppDeployer.undeploy(deploymentId);\n\t\t\tawait().pollInterval(Duration.ofMillis(timeout.pause))\n\t\t\t\t\t.atMost(Duration.ofMillis(timeout.totalTime))\n\t\t\t\t\t.untilAsserted(() -> assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.unknown));\n\t\t}\n\n\t\t@Test\n\t\tvoid statefulSetWithInitContainerAndAdditionalContainers() throws IOException {\n\n\t\t\tMap<String, String> deploymentProps = new HashMap<>();\n\t\t\tdeploymentProps.put(AppDeployer.COUNT_PROPERTY_KEY, \"2\");\n\t\t\tdeploymentProps.put(AppDeployer.INDEXED_PROPERTY_KEY, \"true\");\n\t\t\tdeploymentProps.put(\"spring.cloud.deployer.kubernetes.podSecurityContext\",\n\t\t\t\t\t\"{ fsGroup: 65534\" +\n\t\t\t\t\t\t\t\", fsGroupChangePolicy: Always\" +\n\t\t\t\t\t\t\t\", runAsUser: 65534\" +\n\t\t\t\t\t\t\t\", runAsGroup: 65534\" +\n\t\t\t\t\t\t\t\", seLinuxOptions: { level: \\\"s0:c123,c456\\\" }\" +\n\t\t\t\t\t\t\t\"}\");\n\t\t\tdeploymentProps.put(\"spring.cloud.deployer.kubernetes.containerSecurityContext\",\n\t\t\t\t\t\"{ allowPrivilegeEscalation: true\" +\n\t\t\t\t\t\t\t\", runAsUser: 65534\" +\n\t\t\t\t\t\t\t\", runAsGroup: 65534\" +\n\t\t\t\t\t\t\t\", seLinuxOptions: { level: \\\"s0:c123,c777\\\" }\" +\n\t\t\t\t\t\t\t\"}\");\n\n\t\t\tAppDefinition definition = new AppDefinition(randomName(), null);\n\t\t\tAppDeploymentRequest request = new AppDeploymentRequest(definition, testApplication(), deploymentProps);\n\n\t\t\tString imageName = testApplication().getURI().getSchemeSpecificPart();\n\t\t\tKubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n\t\t\tkubernetesDeployerProperties.setStatefulSetInitContainerImageName(imageName);\n\t\t\tKubernetesAppDeployer kubernetesAppDeployer = kubernetesAppDeployer(kubernetesDeployerProperties);\n\n\t\t\tlogger.info(\"Deploying {}...\", request.getDefinition().getName());\n\t\t\tString deploymentId = kubernetesAppDeployer.deploy(request);\n\n\t\t\tTimeout timeout = deploymentTimeout();\n\t\t\tawait().pollInterval(Duration.ofMillis(timeout.pause))\n\t\t\t\t\t.atMost(Duration.ofMillis(timeout.totalTime))\n\t\t\t\t\t.untilAsserted(() -> assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.deployed));\n\n\t\t\tPodSecurityContext expectedPodSecurityContext = new PodSecurityContextBuilder()\n\t\t\t\t\t.withFsGroup(65534L)\n\t\t\t\t\t.withFsGroupChangePolicy(\"Always\")\n\t\t\t\t\t.withRunAsUser(65534L)\n\t\t\t\t\t.withRunAsGroup(65534L)\n\t\t\t\t\t.withSeLinuxOptions(new SELinuxOptions(\"s0:c123,c456\", null, null, null))\n\t\t\t\t\t.build();\n\n\t\t\tSecurityContext expectedContainerSecurityContext = new SecurityContextBuilder()\n\t\t\t\t\t.withAllowPrivilegeEscalation(true)\n\t\t\t\t\t.withRunAsUser(65534L)\n\t\t\t\t\t.withRunAsGroup(65534L)\n\t\t\t\t\t.withSeLinuxOptions(new SELinuxOptions(\"s0:c123,c777\", null, null, null))\n\t\t\t\t\t.build();\n\n\t\t\tMap<String, String> selector = Collections.singletonMap(AbstractKubernetesDeployer.SPRING_APP_KEY, deploymentId);\n\t\t\tList<StatefulSet> statefulSets = kubernetesClient.apps().statefulSets().withLabels(selector).list().getItems();\n\t\t\tassertThat(statefulSets).hasSize(1)\n\t\t\t\t\t.element(0, InstanceOfAssertFactories.type(StatefulSet.class))\n\t\t\t\t\t.extracting(\"spec.template.spec\", InstanceOfAssertFactories.type(PodSpec.class))\n\t\t\t\t\t.satisfies((podSpec) -> {\n\t\t\t\t\t\tassertThat(podSpec.getSecurityContext()).isEqualTo(expectedPodSecurityContext);\n\t\t\t\t\t\tassertThat(podSpec.getInitContainers())\n\t\t\t\t\t\t\t\t.hasSize(1)\n\t\t\t\t\t\t\t\t.element(0, InstanceOfAssertFactories.type(Container.class))\n\t\t\t\t\t\t\t\t.extracting(Container::getSecurityContext).isEqualTo(expectedContainerSecurityContext);\n\t\t\t\t\t});\n\n\t\t\tlogger.info(\"Undeploying {}...\", deploymentId);\n\t\t\ttimeout = undeploymentTimeout();\n\t\t\tkubernetesAppDeployer.undeploy(deploymentId);\n\t\t\tawait().pollInterval(Duration.ofMillis(timeout.pause))\n\t\t\t\t\t.atMost(Duration.ofMillis(timeout.totalTime))\n\t\t\t\t\t.untilAsserted(() -> assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.unknown));\n\t\t}\n\n\t}\n\n\tprivate void assertThatContainerExistsWithSecurityContext(List<Container> containers, String expectedName, SecurityContext expectedSecurityContext) {\n\t\tassertThat(containers\n\t\t\t\t.stream().filter(c -> c.getName().equals(expectedName)).findFirst())\n\t\t\t\t.hasValueSatisfying((initContainer) -> assertThat(initContainer.getSecurityContext()).isEqualTo(expectedSecurityContext));\n\t}\n\n    @Test\n    public void testUnknownStatusOnPendingResources() throws InterruptedException {\n        logger.info(\"Testing {}...\", \"UnknownStatusOnPendingResources\");\n        KubernetesAppDeployer kubernetesAppDeployer = kubernetesAppDeployer();\n\n        AppDefinition definition = new AppDefinition(randomName(), null);\n        Resource resource = testApplication();\n\n        Map<String, String> props = new HashMap<>();\n        // requests.cpu mirrors limits.cpu when only limits is set avoiding need to set both here\n        props.put(\"spring.cloud.deployer.kubernetes.limits.cpu\", \"5000\");\n\n        AppDeploymentRequest request = new AppDeploymentRequest(definition, resource, props);\n\n        logger.info(\"Deploying {}...\", request.getDefinition().getName());\n        String deploymentId = kubernetesAppDeployer.deploy(request);\n\n        while (kubernetesClient.pods().withLabel(\"spring-deployment-id\", deploymentId).list().getItems().isEmpty()) {\n            logger.info(\"Waiting for deployed pod\");\n            Thread.sleep(500);\n        }\n\n        Pod pod = kubernetesClient.pods().withLabel(\"spring-deployment-id\", deploymentId).list().getItems().get(0);\n\n        while (pod.getStatus().getConditions().isEmpty()) {\n            logger.info(\"Waiting for pod conditions to be set\");\n            Thread.sleep(500);\n        }\n\n        while (!\"Unschedulable\".equals(pod.getStatus().getConditions().get(0).getReason())) {\n            logger.info(\"Waiting for deployed pod to become Unschedulable\");\n        }\n\n        Timeout timeout = deploymentTimeout();\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> {\n                    assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.unknown);\n                });\n\n        logger.info(\"Undeploying {}...\", deploymentId);\n        timeout = undeploymentTimeout();\n\n        assertThatThrownBy(() -> {\n            kubernetesAppDeployer.undeploy(deploymentId);\n        }).isInstanceOf(IllegalStateException.class)\n                .hasMessage(\"App '%s' is not deployed\", deploymentId);\n\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> {\n                    assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.unknown);\n                });\n    }\n\n    @Test\n    public void testSecretRef() throws InterruptedException {\n        logger.info(\"Testing {}...\", \"SecretRef\");\n\n        Secret secret = randomSecret();\n\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n        kubernetesDeployerProperties.setSecretRefs(Collections.singletonList(secret.getMetadata().getName()));\n\n        KubernetesAppDeployer kubernetesAppDeployer = kubernetesAppDeployer(kubernetesDeployerProperties);\n\n        AppDefinition definition = new AppDefinition(randomName(), null);\n        Resource resource = testApplication();\n        AppDeploymentRequest request = new AppDeploymentRequest(definition, resource);\n\n        logger.info(\"Deploying {}...\", request.getDefinition().getName());\n        String deploymentId = kubernetesAppDeployer.deploy(request);\n        Timeout timeout = deploymentTimeout();\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> {\n                    assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.deployed);\n                });\n\n        Container container = kubernetesClient.apps().deployments().withName(deploymentId).get().getSpec()\n                .getTemplate().getSpec().getContainers().get(0);\n\n        List<EnvFromSource> envFromSources = container.getEnvFrom();\n\n        assertThat(envFromSources).hasSize(1);\n\n        EnvFromSource envFromSource = envFromSources.get(0);\n        assertThat(envFromSource.getSecretRef().getName()).isEqualTo(secret.getMetadata().getName());\n\n        String podEnvironment = getPodEnvironment(deploymentId);\n\n        assertThat(secret.getData()).hasSize(2);\n\n        for (Map.Entry<String, String> secretData : secret.getData().entrySet()) {\n            String decodedValue = new String(Base64.getDecoder().decode(secretData.getValue()));\n            assertThat(podEnvironment).contains(secretData.getKey() + \"=\" + decodedValue);\n        }\n\n        logger.info(\"Undeploying {}...\", deploymentId);\n        timeout = undeploymentTimeout();\n        kubernetesAppDeployer.undeploy(deploymentId);\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> {\n                    assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.unknown);\n                });\n\n        kubernetesClient.secrets().delete(secret);\n    }\n\n    @Test\n    public void testSecretRefFromDeployerProperty() throws InterruptedException {\n        logger.info(\"Testing {}...\", \"SecretRefFromDeployerProperty\");\n\n        Secret secret = randomSecret();\n\n        KubernetesAppDeployer kubernetesAppDeployer = kubernetesAppDeployer();\n\n        Map<String, String> props = new HashMap<>();\n        props.put(\"spring.cloud.deployer.kubernetes.secretRefs\", secret.getMetadata().getName());\n\n        AppDefinition definition = new AppDefinition(randomName(), null);\n        Resource resource = testApplication();\n        AppDeploymentRequest request = new AppDeploymentRequest(definition, resource, props);\n\n        logger.info(\"Deploying {}...\", request.getDefinition().getName());\n        String deploymentId = kubernetesAppDeployer.deploy(request);\n        Timeout timeout = deploymentTimeout();\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> {\n                    assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.deployed);\n                });\n\n        Container container = kubernetesClient.apps().deployments().withName(deploymentId).get().getSpec()\n                .getTemplate().getSpec().getContainers().get(0);\n\n        List<EnvFromSource> envFromSources = container.getEnvFrom();\n\n        assertThat(envFromSources).hasSize(1);\n\n        EnvFromSource envFromSource = envFromSources.get(0);\n        assertThat(secret.getMetadata().getName()).isEqualTo(envFromSource.getSecretRef().getName());\n\n        String podEnvironment = getPodEnvironment(deploymentId);\n\n        assertThat(secret.getData()).hasSize(2);\n\n        for (Map.Entry<String, String> secretData : secret.getData().entrySet()) {\n            String decodedValue = new String(Base64.getDecoder().decode(secretData.getValue()));\n            assertThat(podEnvironment).contains(secretData.getKey() + \"=\" + decodedValue);\n        }\n\n        logger.info(\"Undeploying {}...\", deploymentId);\n        timeout = undeploymentTimeout();\n        kubernetesAppDeployer.undeploy(deploymentId);\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> {\n                    assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.unknown);\n                });\n\n        kubernetesClient.secrets().delete(secret);\n    }\n\n    @Test\n    public void testSecretRefFromDeployerPropertyOverride() throws IOException, InterruptedException {\n        logger.info(\"Testing {}...\", \"SecretRefFromDeployerPropertyOverride\");\n\n        Secret propertySecret = randomSecret();\n        Secret deployerPropertySecret = randomSecret();\n\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n        kubernetesDeployerProperties.setSecretRefs(Collections.singletonList(propertySecret.getMetadata().getName()));\n\n        KubernetesAppDeployer kubernetesAppDeployer = kubernetesAppDeployer(kubernetesDeployerProperties);\n\n        Map<String, String> props = new HashMap<>();\n        props.put(\"spring.cloud.deployer.kubernetes.secretRefs\", deployerPropertySecret.getMetadata().getName());\n\n        AppDefinition definition = new AppDefinition(randomName(), null);\n        Resource resource = testApplication();\n        AppDeploymentRequest request = new AppDeploymentRequest(definition, resource, props);\n\n        logger.info(\"Deploying {}...\", request.getDefinition().getName());\n        String deploymentId = kubernetesAppDeployer.deploy(request);\n        Timeout timeout = deploymentTimeout();\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> {\n                    assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.deployed);\n                });\n\n        Container container = kubernetesClient.apps().deployments().withName(deploymentId).get().getSpec()\n                .getTemplate().getSpec().getContainers().get(0);\n\n        List<EnvFromSource> envFromSources = container.getEnvFrom();\n\n        assertThat(envFromSources).hasSize(1);\n\n        EnvFromSource envFromSource = envFromSources.get(0);\n        assertThat(envFromSource.getSecretRef().getName()).isEqualTo(deployerPropertySecret.getMetadata().getName());\n\n        String podEnvironment = getPodEnvironment(deploymentId);\n\n        for (Map.Entry<String, String> deployerPropertySecretData : deployerPropertySecret.getData().entrySet()) {\n            String decodedValue = new String(Base64.getDecoder().decode(deployerPropertySecretData.getValue()));\n            assertThat(podEnvironment).contains(deployerPropertySecretData.getKey() + \"=\" + decodedValue);\n        }\n\n        for (Map.Entry<String, String> propertySecretData : propertySecret.getData().entrySet()) {\n            String decodedValue = new String(Base64.getDecoder().decode(propertySecretData.getValue()));\n            assertThat(podEnvironment).doesNotContain(propertySecretData.getKey() + \"=\" + decodedValue);\n        }\n\n        logger.info(\"Undeploying {}...\", deploymentId);\n        timeout = undeploymentTimeout();\n        kubernetesAppDeployer.undeploy(deploymentId);\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> {\n                    assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.unknown);\n                });\n\n        kubernetesClient.secrets().delete(propertySecret);\n        kubernetesClient.secrets().delete(deployerPropertySecret);\n    }\n\n    @Test\n    public void testSecretRefFromPropertyMultiple() throws InterruptedException {\n        logger.info(\"Testing {}...\", \"SecretRefFromPropertyMultiple\");\n\n        Secret secret1 = randomSecret();\n        Secret secret2 = randomSecret();\n\n        List<String> secrets = new ArrayList<>();\n        secrets.add(secret1.getMetadata().getName());\n        secrets.add(secret2.getMetadata().getName());\n\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n        kubernetesDeployerProperties.setSecretRefs(secrets);\n\n        KubernetesAppDeployer kubernetesAppDeployer = kubernetesAppDeployer(kubernetesDeployerProperties);\n\n        AppDefinition definition = new AppDefinition(randomName(), null);\n        Resource resource = testApplication();\n        AppDeploymentRequest request = new AppDeploymentRequest(definition, resource, null);\n\n        logger.info(\"Deploying {}...\", request.getDefinition().getName());\n        String deploymentId = kubernetesAppDeployer.deploy(request);\n        Timeout timeout = deploymentTimeout();\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> {\n                    assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.deployed);\n                });\n\n        Container container = kubernetesClient.apps().deployments().withName(deploymentId).get().getSpec()\n                .getTemplate().getSpec().getContainers().get(0);\n\n        List<EnvFromSource> envFromSources = container.getEnvFrom();\n\n        assertThat(envFromSources).hasSize(2);\n\n        String podEnvironment = getPodEnvironment(deploymentId);\n\n        EnvFromSource envFromSource1 = envFromSources.get(0);\n        assertThat(envFromSource1.getSecretRef().getName()).isEqualTo(secret1.getMetadata().getName());\n\n        EnvFromSource envFromSource2 = envFromSources.get(1);\n        assertThat(envFromSource2.getSecretRef().getName()).isEqualTo(secret2.getMetadata().getName());\n\n        Map<String, String> mergedSecretData = new HashMap<>();\n        mergedSecretData.putAll(secret1.getData());\n        mergedSecretData.putAll(secret2.getData());\n\n        assertThat(mergedSecretData).hasSize(4);\n\n        for (Map.Entry<String, String> secretData : secret1.getData().entrySet()) {\n            String decodedValue = new String(Base64.getDecoder().decode(secretData.getValue()));\n            assertThat(podEnvironment).contains(secretData.getKey() + \"=\" + decodedValue);\n        }\n\n        logger.info(\"Undeploying {}...\", deploymentId);\n        timeout = undeploymentTimeout();\n        kubernetesAppDeployer.undeploy(deploymentId);\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> {\n                    assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.unknown);\n                });\n\n        kubernetesClient.secrets().delete(secret1);\n        kubernetesClient.secrets().delete(secret2);\n    }\n\n    @Test\n    public void testSecretRefFromDeploymentPropertyMultiple() throws InterruptedException {\n        logger.info(\"Testing {}...\", \"SecretRefFromDeploymentPropertyMultiple\");\n\n        Secret secret1 = randomSecret();\n        Secret secret2 = randomSecret();\n\n        Map<String, String> props = new HashMap<>();\n        props.put(\"spring.cloud.deployer.kubernetes.secretRefs\", \"[\" + secret1.getMetadata().getName() + \",\" +\n                secret2.getMetadata().getName() + \"]\");\n\n        KubernetesAppDeployer kubernetesAppDeployer = kubernetesAppDeployer();\n\n        AppDefinition definition = new AppDefinition(randomName(), null);\n        Resource resource = testApplication();\n        AppDeploymentRequest request = new AppDeploymentRequest(definition, resource, props);\n\n        logger.info(\"Deploying {}...\", request.getDefinition().getName());\n        String deploymentId = kubernetesAppDeployer.deploy(request);\n        Timeout timeout = deploymentTimeout();\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> {\n                    assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.deployed);\n                });\n\n        Container container = kubernetesClient.apps().deployments().withName(deploymentId).get().getSpec()\n                .getTemplate().getSpec().getContainers().get(0);\n\n        List<EnvFromSource> envFromSources = container.getEnvFrom();\n\n        assertThat(envFromSources).hasSize(2);\n\n        String podEnvironment = getPodEnvironment(deploymentId);\n\n        EnvFromSource envFromSource1 = envFromSources.get(0);\n        assertThat(secret1.getMetadata().getName()).isEqualTo(envFromSource1.getSecretRef().getName());\n\n        EnvFromSource envFromSource2 = envFromSources.get(1);\n        assertThat(secret2.getMetadata().getName()).isEqualTo(envFromSource2.getSecretRef().getName());\n\n        Map<String, String> mergedSecretData = new HashMap<>();\n        mergedSecretData.putAll(secret1.getData());\n        mergedSecretData.putAll(secret2.getData());\n\n        assertThat(mergedSecretData).hasSize(4);\n\n        for (Map.Entry<String, String> secretData : secret1.getData().entrySet()) {\n            String decodedValue = new String(Base64.getDecoder().decode(secretData.getValue()));\n            assertThat(podEnvironment).contains(secretData.getKey() + \"=\" + decodedValue);\n        }\n\n        logger.info(\"Undeploying {}...\", deploymentId);\n        timeout = undeploymentTimeout();\n        kubernetesAppDeployer.undeploy(deploymentId);\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> {\n                    assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.unknown);\n                });\n\n        kubernetesClient.secrets().delete(secret1);\n        kubernetesClient.secrets().delete(secret2);\n    }\n\n    @Test\n    public void testConfigMapRef() throws InterruptedException {\n        logger.info(\"Testing {}...\", \"ConfigMapRef\");\n\n        ConfigMap configMap = randomConfigMap();\n\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n        kubernetesDeployerProperties.setConfigMapRefs(Collections.singletonList(configMap.getMetadata().getName()));\n\n        KubernetesAppDeployer kubernetesAppDeployer = kubernetesAppDeployer(kubernetesDeployerProperties);\n\n        AppDefinition definition = new AppDefinition(randomName(), null);\n        Resource resource = testApplication();\n        AppDeploymentRequest request = new AppDeploymentRequest(definition, resource);\n\n        logger.info(\"Deploying {}...\", request.getDefinition().getName());\n        String deploymentId = kubernetesAppDeployer.deploy(request);\n        Timeout timeout = deploymentTimeout();\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> {\n                    assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.deployed);\n                });\n\n        Container container = kubernetesClient.apps().deployments().withName(deploymentId).get().getSpec()\n                .getTemplate().getSpec().getContainers().get(0);\n\n        List<EnvFromSource> envFromSources = container.getEnvFrom();\n\n        assertThat(envFromSources).hasSize(1);\n\n        EnvFromSource envFromSource = envFromSources.get(0);\n        assertThat(envFromSource.getConfigMapRef().getName()).isEqualTo(configMap.getMetadata().getName());\n\n        String podEnvironment = getPodEnvironment(deploymentId);\n\n        assertThat(configMap.getData()).hasSize(2);\n\n        for (Map.Entry<String, String> configMapData : configMap.getData().entrySet()) {\n            assertThat(podEnvironment).contains(configMapData.getKey() + \"=\" + configMapData.getValue());\n        }\n\n        logger.info(\"Undeploying {}...\", deploymentId);\n        timeout = undeploymentTimeout();\n        kubernetesAppDeployer.undeploy(deploymentId);\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> {\n                    assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.unknown);\n                });\n\n        kubernetesClient.configMaps().delete(configMap);\n    }\n\n    @Test\n    public void testConfigMapRefFromDeployerProperty() throws InterruptedException {\n        logger.info(\"Testing {}...\", \"ConfigMapRefFromDeployerProperty\");\n\n        ConfigMap configMap = randomConfigMap();\n\n        KubernetesAppDeployer kubernetesAppDeployer = kubernetesAppDeployer();\n\n        Map<String, String> props = new HashMap<>();\n        props.put(\"spring.cloud.deployer.kubernetes.config-map-refs\", configMap.getMetadata().getName());\n\n        AppDefinition definition = new AppDefinition(randomName(), null);\n        Resource resource = testApplication();\n        AppDeploymentRequest request = new AppDeploymentRequest(definition, resource, props);\n\n        logger.info(\"Deploying {}...\", request.getDefinition().getName());\n        String deploymentId = kubernetesAppDeployer.deploy(request);\n        Timeout timeout = deploymentTimeout();\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> {\n                    assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.deployed);\n                });\n\n        Container container = kubernetesClient.apps().deployments().withName(deploymentId).get().getSpec()\n                .getTemplate().getSpec().getContainers().get(0);\n\n        List<EnvFromSource> envFromSources = container.getEnvFrom();\n\n        assertThat(envFromSources).hasSize(1);\n\n        EnvFromSource envFromSource = envFromSources.get(0);\n        assertThat(envFromSource.getConfigMapRef().getName()).isEqualTo(configMap.getMetadata().getName());\n\n        String podEnvironment = getPodEnvironment(deploymentId);\n\n        assertThat(configMap.getData()).hasSize(2);\n\n        for (Map.Entry<String, String> configMapData : configMap.getData().entrySet()) {\n            assertThat(podEnvironment).contains(configMapData.getKey() + \"=\" + configMapData.getValue());\n        }\n\n        logger.info(\"Undeploying {}...\", deploymentId);\n        timeout = undeploymentTimeout();\n        kubernetesAppDeployer.undeploy(deploymentId);\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> {\n                    assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.unknown);\n                });\n\n        kubernetesClient.configMaps().delete(configMap);\n    }\n\n    @Test\n    public void testConfigMapRefFromDeployerPropertyOverride() throws IOException, InterruptedException {\n        logger.info(\"Testing {}...\", \"ConfigMapRefFromDeployerPropertyOverride\");\n\n        ConfigMap propertyConfigMap = randomConfigMap();\n        ConfigMap deployerPropertyConfigMap = randomConfigMap();\n\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n        kubernetesDeployerProperties.setConfigMapRefs(Collections.singletonList(propertyConfigMap.getMetadata().getName()));\n\n        KubernetesAppDeployer kubernetesAppDeployer = kubernetesAppDeployer(kubernetesDeployerProperties);\n\n        Map<String, String> props = new HashMap<>();\n        props.put(\"spring.cloud.deployer.kubernetes.configMapRefs\", deployerPropertyConfigMap.getMetadata().getName());\n\n        AppDefinition definition = new AppDefinition(randomName(), null);\n        Resource resource = testApplication();\n        AppDeploymentRequest request = new AppDeploymentRequest(definition, resource, props);\n\n        logger.info(\"Deploying {}...\", request.getDefinition().getName());\n        String deploymentId = kubernetesAppDeployer.deploy(request);\n        Timeout timeout = deploymentTimeout();\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> {\n                    assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.deployed);\n                });\n\n        Container container = kubernetesClient.apps().deployments().withName(deploymentId).get().getSpec()\n                .getTemplate().getSpec().getContainers().get(0);\n\n        List<EnvFromSource> envFromSources = container.getEnvFrom();\n\n        assertThat(envFromSources).hasSize(1);\n\n        EnvFromSource envFromSource = envFromSources.get(0);\n        assertThat(deployerPropertyConfigMap.getMetadata().getName()).isEqualTo(envFromSource.getConfigMapRef().getName());\n\n        String podEnvironment = getPodEnvironment(deploymentId);\n\n        for (Map.Entry<String, String> deployerPropertyConfigMapData : deployerPropertyConfigMap.getData().entrySet()) {\n            assertThat(podEnvironment)\n                    .contains(deployerPropertyConfigMapData.getKey() + \"=\" + deployerPropertyConfigMapData.getValue());\n        }\n\n        for (Map.Entry<String, String> propertyConfigMapData : propertyConfigMap.getData().entrySet()) {\n            assertThat(podEnvironment).doesNotContain(propertyConfigMapData.getKey() + \"=\" + propertyConfigMapData.getValue());\n        }\n\n        logger.info(\"Undeploying {}...\", deploymentId);\n        timeout = undeploymentTimeout();\n        kubernetesAppDeployer.undeploy(deploymentId);\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> {\n                    assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.unknown);\n                });\n\n        kubernetesClient.configMaps().delete(propertyConfigMap);\n        kubernetesClient.configMaps().delete(deployerPropertyConfigMap);\n    }\n\n    @Test\n    public void testConfigMapRefFromPropertyMultiple() throws InterruptedException {\n        logger.info(\"Testing {}...\", \"ConfigMapRefFromPropertyMultiple\");\n\n        ConfigMap configMap1 = randomConfigMap();\n        ConfigMap configMap2 = randomConfigMap();\n\n        List<String> configMaps = new ArrayList<>();\n        configMaps.add(configMap1.getMetadata().getName());\n        configMaps.add(configMap2.getMetadata().getName());\n\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n        kubernetesDeployerProperties.setConfigMapRefs(configMaps);\n\n        KubernetesAppDeployer kubernetesAppDeployer = kubernetesAppDeployer(kubernetesDeployerProperties);\n\n        AppDefinition definition = new AppDefinition(randomName(), null);\n        Resource resource = testApplication();\n        AppDeploymentRequest request = new AppDeploymentRequest(definition, resource, null);\n\n        logger.info(\"Deploying {}...\", request.getDefinition().getName());\n        String deploymentId = kubernetesAppDeployer.deploy(request);\n        Timeout timeout = deploymentTimeout();\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> {\n                    assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.deployed);\n                });\n\n        Container container = kubernetesClient.apps().deployments().withName(deploymentId).get().getSpec()\n                .getTemplate().getSpec().getContainers().get(0);\n\n        List<EnvFromSource> envFromSources = container.getEnvFrom();\n\n        assertThat(envFromSources).hasSize(2);\n\n        String podEnvironment = getPodEnvironment(deploymentId);\n\n        EnvFromSource envFromSource1 = envFromSources.get(0);\n        assertThat(configMap1.getMetadata().getName()).isEqualTo(envFromSource1.getConfigMapRef().getName());\n\n        EnvFromSource envFromSource2 = envFromSources.get(1);\n        assertThat(configMap2.getMetadata().getName()).isEqualTo(envFromSource2.getConfigMapRef().getName());\n\n        Map<String, String> mergedConfigMapData = new HashMap<>();\n        mergedConfigMapData.putAll(configMap1.getData());\n        mergedConfigMapData.putAll(configMap2.getData());\n\n        assertThat(mergedConfigMapData).hasSize(4);\n\n        for (Map.Entry<String, String> configMapData : configMap1.getData().entrySet()) {\n            assertThat(podEnvironment).contains(configMapData.getKey() + \"=\" + configMapData.getValue());\n        }\n\n        logger.info(\"Undeploying {}...\", deploymentId);\n        timeout = undeploymentTimeout();\n        kubernetesAppDeployer.undeploy(deploymentId);\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> {\n                    assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.unknown);\n                });\n\n        kubernetesClient.configMaps().delete(configMap1);\n        kubernetesClient.configMaps().delete(configMap2);\n    }\n\n    @Test\n    public void testConfigMapRefFromDeploymentPropertyMultiple() throws InterruptedException {\n        logger.info(\"Testing {}...\", \"ConfigMapRefFromDeploymentPropertyMultiple\");\n\n        ConfigMap configMap1 = randomConfigMap();\n        ConfigMap configMap2 = randomConfigMap();\n\n        Map<String, String> props = new HashMap<>();\n        props.put(\"spring.cloud.deployer.kubernetes.configMapRefs\", \"[\" + configMap1.getMetadata().getName() + \",\" +\n                configMap2.getMetadata().getName() + \"]\");\n\n        KubernetesAppDeployer kubernetesAppDeployer = kubernetesAppDeployer();\n\n        AppDefinition definition = new AppDefinition(randomName(), null);\n        Resource resource = testApplication();\n        AppDeploymentRequest request = new AppDeploymentRequest(definition, resource, props);\n\n        logger.info(\"Deploying {}...\", request.getDefinition().getName());\n        String deploymentId = kubernetesAppDeployer.deploy(request);\n        Timeout timeout = deploymentTimeout();\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> {\n                    assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.deployed);\n                });\n\n        Container container = kubernetesClient.apps().deployments().withName(deploymentId).get().getSpec()\n                .getTemplate().getSpec().getContainers().get(0);\n\n        List<EnvFromSource> envFromSources = container.getEnvFrom();\n\n        assertThat(envFromSources).isNotNull();\n        assertThat(envFromSources).hasSize(2);\n\n        String podEnvironment = getPodEnvironment(deploymentId);\n\n        EnvFromSource envFromSource1 = envFromSources.get(0);\n        assertThat(envFromSource1.getConfigMapRef().getName()).isEqualTo(configMap1.getMetadata().getName());\n\n        EnvFromSource envFromSource2 = envFromSources.get(1);\n        assertThat(envFromSource2.getConfigMapRef().getName()).isEqualTo(configMap2.getMetadata().getName());\n\n        Map<String, String> mergedConfigMapData = new HashMap<>();\n        mergedConfigMapData.putAll(configMap1.getData());\n        mergedConfigMapData.putAll(configMap2.getData());\n\n        assertThat(mergedConfigMapData).hasSize(4);\n\n        for (Map.Entry<String, String> configMapData : configMap1.getData().entrySet()) {\n            assertThat(podEnvironment).contains(configMapData.getKey() + \"=\" + configMapData.getValue());\n        }\n\n        logger.info(\"Undeploying {}...\", deploymentId);\n        timeout = undeploymentTimeout();\n        kubernetesAppDeployer.undeploy(deploymentId);\n        await().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> {\n                    assertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.unknown);\n                });\n\n        kubernetesClient.configMaps().delete(configMap1);\n        kubernetesClient.configMaps().delete(configMap2);\n    }\n\n    @Override\n    protected String randomName() {\n        // Kubernetes service names must start with a letter and can only be 24 characters long\n        return \"app-\" + UUID.randomUUID().toString().substring(0, 18);\n    }\n\n    @Override\n    protected Timeout deploymentTimeout() {\n        return new Timeout(300, 2000);\n    }\n\n    @Override\n    protected Resource testApplication() {\n        return new DockerResource(\"springcloud/spring-cloud-deployer-spi-test-app:latest\");\n    }\n\n    private String getPodEnvironment(String deploymentId) throws InterruptedException {\n        String podName = kubernetesClient.pods().withLabel(\"spring-deployment-id\", deploymentId).list().getItems()\n                .get(0).getMetadata().getName();\n\n        final CountDownLatch countDownLatch = new CountDownLatch(1);\n        ByteArrayOutputStream execOutputStream = new ByteArrayOutputStream();\n\n        ExecWatch watch = kubernetesClient.pods().withName(podName).inContainer(deploymentId)\n                .writingOutput(execOutputStream)\n                .usingListener(new ExecListener() {\n\n                    @Override\n                    public void onFailure(Throwable throwable, Response response) {\n                        countDownLatch.countDown();\n                    }\n\n                    @Override\n                    public void onClose(int code, String reason) {\n                        countDownLatch.countDown();\n                    }\n                }).exec(\"printenv\");\n\n        countDownLatch.await();\n\n        byte[] bytes = execOutputStream.toByteArray();\n\n        watch.close();\n\n        return new String(bytes);\n    }\n\n    // Creates a Secret with a name will be generated prefixed by \"secret-\" followed by random numbers.\n    //\n    // Two data keys are present, both prefixed by \"d-\" followed by random numbers. This allows for cases where\n    // multiple Secrets may be read into environment variables avoiding variable name clashes.\n    //\n    // Data values are Base64 encoded strings of value1 and value2\n    private Secret randomSecret() {\n        Map<String, String> secretData = new HashMap<>();\n        secretData.put(\"d-\" + UUID.randomUUID().toString().substring(0, 5), \"dmFsdWUx\");\n        secretData.put(\"d-\" + UUID.randomUUID().toString().substring(0, 5), \"dmFsdWUy\");\n\n        Secret secret = new Secret();\n        secret.setData(secretData);\n\n        ObjectMeta objectMeta = new ObjectMeta();\n        objectMeta.setName(\"secret-\" + UUID.randomUUID().toString().substring(0, 5));\n\n        secret.setMetadata(objectMeta);\n\n        return kubernetesClient.secrets().create(secret);\n    }\n\n    // Creates a ConfigMap with a name will be generated prefixed by \"cm-\" followed by random numbers.\n    //\n    // Two data keys are present, both prefixed by \"d-\" followed by random numbers. This allows for cases where\n    // multiple ConfigMaps may be read into environment variables avoiding variable name clashes.\n    //\n    // Data values are strings of value1 and value\n    private ConfigMap randomConfigMap() {\n        Map<String, String> configMapData = new HashMap<>();\n        configMapData.put(\"d-\" + UUID.randomUUID().toString().substring(0, 5), \"value1\");\n        configMapData.put(\"d-\" + UUID.randomUUID().toString().substring(0, 5), \"value2\");\n\n        ConfigMap configMap = new ConfigMap();\n        configMap.setData(configMapData);\n\n        ObjectMeta objectMeta = new ObjectMeta();\n        objectMeta.setName(\"cm-\" + UUID.randomUUID().toString().substring(0, 5));\n\n        configMap.setMetadata(objectMeta);\n\n        return kubernetesClient.configMaps().create(configMap);\n    }\n\n    private KubernetesAppDeployer kubernetesAppDeployer() {\n        return kubernetesAppDeployer(new KubernetesDeployerProperties());\n    }\n\n    private KubernetesAppDeployer kubernetesAppDeployer(KubernetesDeployerProperties kubernetesDeployerProperties) {\n        return new KubernetesAppDeployer(kubernetesDeployerProperties, this.kubernetesClient,\n                new DefaultContainerFactory(kubernetesDeployerProperties));\n    }\n}\n"
  },
  {
    "path": "src/test/java/org/springframework/cloud/deployer/spi/kubernetes/KubernetesAppDeployerTests.java",
    "content": "/*\n * Copyright 2015-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.kubernetes;\n\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Properties;\n\nimport io.fabric8.kubernetes.api.model.AffinityBuilder;\nimport io.fabric8.kubernetes.api.model.Capabilities;\nimport io.fabric8.kubernetes.api.model.ConfigMapKeySelector;\nimport io.fabric8.kubernetes.api.model.Container;\nimport io.fabric8.kubernetes.api.model.EnvVar;\nimport io.fabric8.kubernetes.api.model.HostPathVolumeSource;\nimport io.fabric8.kubernetes.api.model.HostPathVolumeSourceBuilder;\nimport io.fabric8.kubernetes.api.model.LabelSelector;\nimport io.fabric8.kubernetes.api.model.LabelSelectorRequirementBuilder;\nimport io.fabric8.kubernetes.api.model.NodeAffinity;\nimport io.fabric8.kubernetes.api.model.NodeSelectorRequirementBuilder;\nimport io.fabric8.kubernetes.api.model.NodeSelectorTerm;\nimport io.fabric8.kubernetes.api.model.PodAffinity;\nimport io.fabric8.kubernetes.api.model.PodAffinityTerm;\nimport io.fabric8.kubernetes.api.model.PodAntiAffinity;\nimport io.fabric8.kubernetes.api.model.PodSecurityContext;\nimport io.fabric8.kubernetes.api.model.PodSecurityContextBuilder;\nimport io.fabric8.kubernetes.api.model.PodSpec;\nimport io.fabric8.kubernetes.api.model.PreferredSchedulingTerm;\nimport io.fabric8.kubernetes.api.model.SELinuxOptions;\nimport io.fabric8.kubernetes.api.model.SeccompProfile;\nimport io.fabric8.kubernetes.api.model.SecretKeySelector;\nimport io.fabric8.kubernetes.api.model.SecurityContext;\nimport io.fabric8.kubernetes.api.model.SecurityContextBuilder;\nimport io.fabric8.kubernetes.api.model.Sysctl;\nimport io.fabric8.kubernetes.api.model.Toleration;\nimport io.fabric8.kubernetes.api.model.VolumeBuilder;\nimport io.fabric8.kubernetes.api.model.WeightedPodAffinityTerm;\nimport io.fabric8.kubernetes.api.model.WindowsSecurityContextOptions;\nimport org.junit.jupiter.api.DisplayName;\nimport org.junit.jupiter.api.Nested;\nimport org.junit.jupiter.api.Test;\n\nimport org.springframework.beans.factory.config.YamlPropertiesFactoryBean;\nimport org.springframework.boot.context.properties.bind.Bindable;\nimport org.springframework.boot.context.properties.bind.Binder;\nimport org.springframework.boot.context.properties.source.MapConfigurationPropertySource;\nimport org.springframework.cloud.deployer.resource.docker.DockerResource;\nimport org.springframework.cloud.deployer.spi.core.AppDefinition;\nimport org.springframework.cloud.deployer.spi.core.AppDeploymentRequest;\nimport org.springframework.core.io.ClassPathResource;\nimport org.springframework.core.io.Resource;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.assertThatThrownBy;\nimport static org.assertj.core.api.Assertions.entry;\n\n/**\n * Unit tests for {@link KubernetesAppDeployer}\n *\n * @author Donovan Muller\n * @author David Turanski\n * @author Ilayaperumal Gopinathan\n * @author Chris Schaefer\n * @author Enrique Medina Montenegro\n * @author Chris Bono\n */\n@DisplayName(\"KubernetesAppDeployer\")\npublic class KubernetesAppDeployerTests {\n\n    private KubernetesAppDeployer deployer;\n\n    private DeploymentPropertiesResolver deploymentPropertiesResolver = new DeploymentPropertiesResolver(\n            KubernetesDeployerProperties.KUBERNETES_DEPLOYER_PROPERTIES_PREFIX, new KubernetesDeployerProperties());\n\n    @Test\n    public void deployWithVolumesOnly() throws Exception {\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(),\n                new HashMap<>());\n\n        deployer = k8sAppDeployer(bindDeployerProperties());\n        PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);\n\n        assertThat(podSpec.getVolumes()).isEmpty();\n    }\n\n    @Test\n    public void deployWithVolumesAndVolumeMounts() throws Exception {\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        Map<String, String> props = new HashMap<>();\n        props.put(\"spring.cloud.deployer.kubernetes.volumeMounts\", \"[\" + \"{name: 'testpvc', mountPath: '/test/pvc'}, \"\n                + \"{name: 'testnfs', mountPath: '/test/nfs', readOnly: 'true'}\" + \"]\");\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), props);\n\n        deployer = k8sAppDeployer();\n        PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);\n\n        assertThat(podSpec.getVolumes()).containsOnly(\n                // volume 'testhostpath' defined in dataflow-server.yml should not be added\n                // as there is no corresponding volume mount\n                new VolumeBuilder().withName(\"testpvc\").withNewPersistentVolumeClaim(\"testClaim\", true).build(),\n                new VolumeBuilder().withName(\"testnfs\").withNewNfs(\"/test/nfs\", null, \"10.0.0.1:111\").build());\n\n        props.clear();\n        props.put(\"spring.cloud.deployer.kubernetes.volumes\",\n                \"[\" + \"{name: testhostpath, hostPath: { path: '/test/override/hostPath' }},\"\n                        + \"{name: 'testnfs', nfs: { server: '192.168.1.1:111', path: '/test/override/nfs' }} \" + \"]\");\n        props.put(\"spring.cloud.deployer.kubernetes.volumeMounts\",\n                \"[\" + \"{name: 'testhostpath', mountPath: '/test/hostPath'}, \"\n                        + \"{name: 'testpvc', mountPath: '/test/pvc'}, \"\n                        + \"{name: 'testnfs', mountPath: '/test/nfs', readOnly: 'true'}\" + \"]\");\n        appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), props);\n\n        deployer = k8sAppDeployer();\n        podSpec = deployer.createPodSpec(appDeploymentRequest);\n\n        HostPathVolumeSource hostPathVolumeSource = new HostPathVolumeSourceBuilder()\n                .withPath(\"/test/override/hostPath\").build();\n\n        assertThat(podSpec.getVolumes()).containsOnly(\n                new VolumeBuilder().withName(\"testhostpath\").withHostPath(hostPathVolumeSource).build(),\n                new VolumeBuilder().withName(\"testpvc\").withNewPersistentVolumeClaim(\"testClaim\", true).build(),\n                new VolumeBuilder().withName(\"testnfs\").withNewNfs(\"/test/override/nfs\", null, \"192.168.1.1:111\").build());\n    }\n\n    @Test\n    public void deployWithNodeSelectorGlobalProperty() throws Exception {\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), null);\n\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n        kubernetesDeployerProperties.setNodeSelector(\"disktype:ssd, os:qnx\");\n\n        deployer = k8sAppDeployer(kubernetesDeployerProperties);\n        PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);\n\n        assertThat(podSpec.getNodeSelector()).containsOnly(entry(\"disktype\", \"ssd\"), entry(\"os\", \"qnx\"));\n    }\n\n    @Test\n    public void deployWithNodeSelectorDeploymentProperty() throws Exception {\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        Map<String, String> props = new HashMap<>();\n        props.put(KubernetesDeployerProperties.KUBERNETES_DEPLOYMENT_NODE_SELECTOR, \"disktype:ssd, os: linux\");\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), props);\n\n        deployer = k8sAppDeployer();\n        PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);\n\n        assertThat(podSpec.getNodeSelector()).containsOnly(entry(\"disktype\", \"ssd\"), entry(\"os\", \"linux\"));\n    }\n\n    @Test\n    public void deployWithNodeSelectorDeploymentPropertyGlobalOverride() throws Exception {\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        Map<String, String> props = new HashMap<>();\n        props.put(KubernetesDeployerProperties.KUBERNETES_DEPLOYMENT_NODE_SELECTOR, \"disktype:ssd, os: openbsd\");\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), props);\n\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n        kubernetesDeployerProperties.setNodeSelector(\"disktype:ssd, os:qnx\");\n\n        deployer = k8sAppDeployer(kubernetesDeployerProperties);\n        PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);\n\n        assertThat(podSpec.getNodeSelector()).containsOnly(entry(\"disktype\", \"ssd\"), entry(\"os\", \"openbsd\"));\n    }\n\n    @Test\n    public void deployWithEnvironmentWithCommaDelimitedValue() throws Exception {\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        Map<String, String> props = new HashMap<>();\n        props.put(\"spring.cloud.deployer.kubernetes.environmentVariables\",\n                \"JAVA_TOOL_OPTIONS='thing1,thing2',foo='bar,baz',car=caz,boo='zoo,gnu',doo=dar,OPTS='thing1'\");\n\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), props);\n\n        deployer = k8sAppDeployer();\n        PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);\n\n        assertThat(podSpec.getContainers().get(0).getEnv())\n                .contains(\n                        new EnvVar(\"foo\", \"bar,baz\", null),\n                        new EnvVar(\"car\", \"caz\", null),\n                        new EnvVar(\"boo\", \"zoo,gnu\", null),\n                        new EnvVar(\"doo\", \"dar\", null),\n                        new EnvVar(\"JAVA_TOOL_OPTIONS\", \"thing1,thing2\", null),\n                        new EnvVar(\"OPTS\", \"thing1\", null));\n    }\n\n    @Test\n    public void deployWithEnvironmentWithSingleCommaDelimitedValue() throws Exception {\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        Map<String, String> props = new HashMap<>();\n        props.put(\"spring.cloud.deployer.kubernetes.environmentVariables\",\n                \"JAVA_TOOL_OPTIONS='thing1,thing2'\");\n\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), props);\n\n        deployer = k8sAppDeployer();\n        PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);\n\n        assertThat(podSpec.getContainers().get(0).getEnv())\n                .contains(new EnvVar(\"JAVA_TOOL_OPTIONS\", \"thing1,thing2\", null));\n    }\n\n    @Test\n    public void deployWithEnvironmentWithMultipleCommaDelimitedValue() throws Exception {\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        Map<String, String> props = new HashMap<>();\n        props.put(\"spring.cloud.deployer.kubernetes.environmentVariables\",\n                \"JAVA_TOOL_OPTIONS='thing1,thing2',OPTS='thing3, thing4'\");\n\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), props);\n\n        deployer = k8sAppDeployer();\n        PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);\n\n        assertThat(podSpec.getContainers().get(0).getEnv())\n                .contains(\n                        new EnvVar(\"JAVA_TOOL_OPTIONS\", \"thing1,thing2\", null),\n                        new EnvVar(\"OPTS\", \"thing3, thing4\", null));\n    }\n\n    @Test\n    public void deployWithImagePullSecretDeploymentProperty() {\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n\n        Map<String, String> props = new HashMap<>();\n        props.put(\"spring.cloud.deployer.kubernetes.imagePullSecret\", \"regcred\");\n\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), props);\n\n        deployer = k8sAppDeployer(new KubernetesDeployerProperties());\n        PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);\n\n        assertThat(podSpec.getImagePullSecrets().size()).isEqualTo(1);\n        assertThat(podSpec.getImagePullSecrets().get(0).getName()).isEqualTo(\"regcred\");\n    }\n\n    @Test\n    public void deployWithImagePullSecretDeployerProperty() {\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), null);\n\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n        kubernetesDeployerProperties.setImagePullSecret(\"regcred\");\n\n        deployer = k8sAppDeployer(kubernetesDeployerProperties);\n        PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);\n\n        assertThat(podSpec.getImagePullSecrets().size()).isEqualTo(1);\n        assertThat(podSpec.getImagePullSecrets().get(0).getName()).isEqualTo(\"regcred\");\n    }\n\n    @Test\n    public void deployWithImagePullSecretsDeploymentProperty() {\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n\n        Map<String, String> props = new HashMap<>();\n        props.put(\"spring.cloud.deployer.kubernetes.imagePullSecrets\", \"['regcredone','regcredtwo']\");\n\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), props);\n\n        deployer = k8sAppDeployer(new KubernetesDeployerProperties());\n        PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);\n\n        assertThat(podSpec.getImagePullSecrets().size()).isEqualTo(2);\n        assertThat(podSpec.getImagePullSecrets().get(0).getName()).isEqualTo(\"regcredone\");\n        assertThat(podSpec.getImagePullSecrets().get(1).getName()).isEqualTo(\"regcredtwo\");\n    }\n\n    @Test\n    public void deployWithImagePullSecretsDeployerProperty() {\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), null);\n\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n        kubernetesDeployerProperties.setImagePullSecrets(Arrays.asList(\"regcredone\", \"regcredtwo\"));\n\n        deployer = k8sAppDeployer(kubernetesDeployerProperties);\n        PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);\n\n        assertThat(podSpec.getImagePullSecrets().size()).isEqualTo(2);\n        assertThat(podSpec.getImagePullSecrets().get(0).getName()).isEqualTo(\"regcredone\");\n        assertThat(podSpec.getImagePullSecrets().get(1).getName()).isEqualTo(\"regcredtwo\");\n    }\n\n    @Test\n    public void deployWithDeploymentServiceAccountNameDeploymentProperties() {\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n\n        Map<String, String> props = new HashMap<>();\n        props.put(\"spring.cloud.deployer.kubernetes.deploymentServiceAccountName\", \"myserviceaccount\");\n\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), props);\n\n        deployer = k8sAppDeployer(new KubernetesDeployerProperties());\n        PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);\n\n        assertThat(podSpec.getServiceAccountName()).isNotNull();\n        assertThat(podSpec.getServiceAccountName().equals(\"myserviceaccount\"));\n    }\n\n    @Test\n    public void deployWithDeploymentServiceAccountNameDeployerProperty() {\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), null);\n\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n        kubernetesDeployerProperties.setDeploymentServiceAccountName(\"myserviceaccount\");\n\n        deployer = k8sAppDeployer(kubernetesDeployerProperties);\n        PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);\n\n        assertThat(podSpec.getServiceAccountName()).isNotNull();\n        assertThat(podSpec.getServiceAccountName().equals(\"myserviceaccount\"));\n    }\n\n    @Test\n    public void deployWithDeploymentServiceAccountNameDeploymentPropertyOverride() {\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n\n        Map<String, String> props = new HashMap<>();\n        props.put(\"spring.cloud.deployer.kubernetes.deploymentServiceAccountName\", \"overridesan\");\n\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), props);\n\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n        kubernetesDeployerProperties.setDeploymentServiceAccountName(\"defaultsan\");\n\n        deployer = k8sAppDeployer(kubernetesDeployerProperties);\n        PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);\n\n        assertThat(podSpec.getServiceAccountName()).isNotNull();\n        assertThat(podSpec.getServiceAccountName().equals(\"overridesan\"));\n    }\n\n    @Test\n    public void deployWithTolerations() throws Exception {\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(),\n                new HashMap<>());\n\n        deployer = k8sAppDeployer();\n        PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);\n\n        assertThat(podSpec.getTolerations()).isNotEmpty();\n    }\n\n    @Test\n    public void deployWithGlobalTolerations() {\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n\n        Map<String, String> props = new HashMap<>();\n        props.put(\"spring.cloud.deployer.kubernetes.tolerations\",\n                \"[{key: 'test', value: 'true', operator: 'Equal', effect: 'NoSchedule', tolerationSeconds: 5}, \"\n                        + \"{key: 'test2', value: 'false', operator: 'Equal', effect: 'NoSchedule', tolerationSeconds: 5}]\");\n\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), props);\n\n        deployer = k8sAppDeployer(new KubernetesDeployerProperties());\n        PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);\n\n        assertThat(podSpec.getTolerations()).isNotNull();\n        assertThat(podSpec.getTolerations().size() == 2);\n        assertThat(podSpec.getTolerations().contains(new Toleration(\"NoSchedule\", \"test\", \"Equal\", 5L, \"true\")));\n        assertThat(podSpec.getTolerations().contains(new Toleration(\"NoSchedule\", \"test2\", \"Equal\", 5L, \"false\")));\n    }\n\n    @Test\n    public void deployWithTolerationPropertyOverride() {\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n\n        Map<String, String> props = new HashMap<>();\n        props.put(\"spring.cloud.deployer.kubernetes.tolerations\",\n                \"[{key: 'test', value: 'true', operator: 'Equal', effect: 'NoSchedule', tolerationSeconds: 5}, \"\n                        + \"{key: 'test2', value: 'false', operator: 'Equal', effect: 'NoSchedule', tolerationSeconds: 5}]\");\n\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), props);\n\n        KubernetesDeployerProperties.Toleration toleration = new KubernetesDeployerProperties.Toleration();\n        toleration.setEffect(\"NoSchedule\");\n        toleration.setKey(\"test\");\n        toleration.setOperator(\"Equal\");\n        toleration.setTolerationSeconds(5L);\n        toleration.setValue(\"false\");\n\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n        kubernetesDeployerProperties.getTolerations().add(toleration);\n\n        deployer = k8sAppDeployer(kubernetesDeployerProperties);\n        PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);\n\n        assertThat(podSpec.getTolerations()).isNotNull();\n        assertThat(podSpec.getTolerations().size() == 2);\n        assertThat(podSpec.getTolerations().contains(new Toleration(\"NoSchedule\", \"test\", \"Equal\", 5L, \"true\")));\n        assertThat(podSpec.getTolerations().contains(new Toleration(\"NoSchedule\", \"test2\", \"Equal\", 5L, \"false\")));\n    }\n\n    @Test\n    public void deployWithDuplicateTolerationKeyPropertyOverride() {\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n\n        Map<String, String> props = new HashMap<>();\n        props.put(\"spring.cloud.deployer.kubernetes.tolerations\",\n                \"[{key: 'test', value: 'true', operator: 'Equal', effect: 'NoSchedule', tolerationSeconds: 5}]\");\n\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), props);\n\n        KubernetesDeployerProperties.Toleration toleration = new KubernetesDeployerProperties.Toleration();\n        toleration.setEffect(\"NoSchedule\");\n        toleration.setKey(\"test\");\n        toleration.setOperator(\"Equal\");\n        toleration.setTolerationSeconds(5L);\n        toleration.setValue(\"false\");\n\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n        kubernetesDeployerProperties.getTolerations().add(toleration);\n        kubernetesDeployerProperties.setStartupHttpProbePort(kubernetesDeployerProperties.getLivenessHttpProbePort());\n        deployer = k8sAppDeployer(kubernetesDeployerProperties);\n        PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);\n\n        assertThat(podSpec.getTolerations()).isNotNull();\n        assertThat(podSpec.getTolerations().size() == 1);\n        assertThat(podSpec.getTolerations().contains(new Toleration(\"NoSchedule\", \"test2\", \"Equal\", 5L, \"false\")));\n    }\n\n    @Test\n    public void deployWithDuplicateGlobalToleration() {\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), null);\n\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n\n        KubernetesDeployerProperties.Toleration toleration1 = new KubernetesDeployerProperties.Toleration();\n        toleration1.setEffect(\"NoSchedule\");\n        toleration1.setKey(\"test\");\n        toleration1.setOperator(\"Equal\");\n        toleration1.setTolerationSeconds(5L);\n        toleration1.setValue(\"false\");\n\n        kubernetesDeployerProperties.getTolerations().add(toleration1);\n\n        KubernetesDeployerProperties.Toleration toleration2 = new KubernetesDeployerProperties.Toleration();\n        toleration2.setEffect(\"NoSchedule\");\n        toleration2.setKey(\"test\");\n        toleration2.setOperator(\"Equal\");\n        toleration2.setTolerationSeconds(5L);\n        toleration2.setValue(\"true\");\n\n        kubernetesDeployerProperties.getTolerations().add(toleration2);\n\n        deployer = k8sAppDeployer(kubernetesDeployerProperties);\n        PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);\n\n        assertThat(podSpec.getTolerations()).isNotNull();\n        assertThat(podSpec.getTolerations().size() == 1);\n        assertThat(podSpec.getTolerations().contains(new Toleration(\"NoSchedule\", \"test2\", \"Equal\", 5L, \"true\")));\n    }\n\n    @Test\n    public void testInvalidDeploymentLabelDelimiter() {\n        Map<String, String> props = Collections.singletonMap(\"spring.cloud.deployer.kubernetes.deploymentLabels\",\n                \"label1|value1\");\n\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), props);\n\n        assertThatThrownBy(() -> {\n            this.deploymentPropertiesResolver.getDeploymentLabels(appDeploymentRequest.getDeploymentProperties());\n        }).isInstanceOf(IllegalArgumentException.class);\n    }\n\n    @Test\n    public void testInvalidMultipleDeploymentLabelDelimiter() {\n        Map<String, String> props = Collections.singletonMap(\"spring.cloud.deployer.kubernetes.deploymentLabels\",\n                \"label1:value1 label2:value2\");\n\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), props);\n\n        assertThatThrownBy(() -> {\n            this.deploymentPropertiesResolver.getDeploymentLabels(appDeploymentRequest.getDeploymentProperties());\n        }).isInstanceOf(IllegalArgumentException.class);\n    }\n\n    @Test\n    public void testDeploymentLabels() {\n        Map<String, String> props = Collections.singletonMap(\"spring.cloud.deployer.kubernetes.deploymentLabels\",\n                \"label1:value1,label2:value2\");\n\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), props);\n\n        Map<String, String> deploymentLabels = this.deploymentPropertiesResolver.getDeploymentLabels(appDeploymentRequest.getDeploymentProperties());\n\n        assertThat(deploymentLabels).isNotEmpty();\n        assertThat(deploymentLabels.size()).as(\"Invalid number of labels\").isEqualTo(2);\n        assertThat(deploymentLabels).containsKey(\"label1\");\n        assertThat(deploymentLabels.get(\"label1\")).as(\"Invalid value for 'label1'\").isEqualTo(\"value1\");\n        assertThat(deploymentLabels).containsKey(\"label2\");\n        assertThat(deploymentLabels.get(\"label2\")).as(\"Invalid value for 'label2'\").isEqualTo(\"value2\");\n    }\n\n    @Test\n    public void testSecretKeyRef() {\n        Map<String, String> props = new HashMap<>();\n        props.put(\"spring.cloud.deployer.kubernetes.secretKeyRefs\",\n                \"[{envVarName: 'SECRET_PASSWORD', secretName: 'mySecret', dataKey: 'password'}]\");\n\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), props);\n\n        deployer = k8sAppDeployer(new KubernetesDeployerProperties());\n        PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);\n\n        List<EnvVar> envVars = podSpec.getContainers().get(0).getEnv();\n\n        assertThat(envVars.size()).as(\"Invalid number of env vars\").isEqualTo(2);\n\n        EnvVar secretKeyRefEnvVar = envVars.get(0);\n        assertThat(secretKeyRefEnvVar.getName()).as(\"Unexpected env var name\").isEqualTo(\"SECRET_PASSWORD\");\n        SecretKeySelector secretKeySelector = secretKeyRefEnvVar.getValueFrom().getSecretKeyRef();\n        assertThat(secretKeySelector.getName()).as(\"Unexpected secret name\").isEqualTo(\"mySecret\");\n        assertThat(secretKeySelector.getKey()).as(\"Unexpected secret data key\").isEqualTo(\"password\");\n    }\n\n    @Test\n    public void testSecretKeyRefMultiple() {\n        Map<String, String> props = new HashMap<>();\n        props.put(\"spring.cloud.deployer.kubernetes.secretKeyRefs\",\n                \"[{envVarName: 'SECRET_PASSWORD', secretName: 'mySecret', dataKey: 'password'},\" +\n                        \"{envVarName: 'SECRET_USERNAME', secretName: 'mySecret2', dataKey: 'username'}]\");\n\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), props);\n\n        deployer = k8sAppDeployer(new KubernetesDeployerProperties());\n        PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);\n\n        List<EnvVar> envVars = podSpec.getContainers().get(0).getEnv();\n\n        assertThat(envVars.size()).as(\"Invalid number of env vars\").isEqualTo(3);\n\n        EnvVar secretKeyRefEnvVar = envVars.get(0);\n        assertThat(secretKeyRefEnvVar.getName()).as(\"Unexpected env var name\").isEqualTo(\"SECRET_PASSWORD\");\n        SecretKeySelector secretKeySelector = secretKeyRefEnvVar.getValueFrom().getSecretKeyRef();\n        assertThat(secretKeySelector.getName()).as(\"Unexpected secret name\").isEqualTo(\"mySecret\");\n        assertThat(secretKeySelector.getKey()).as(\"Unexpected secret data key\").isEqualTo(\"password\");\n\n        secretKeyRefEnvVar = envVars.get(1);\n        assertThat(secretKeyRefEnvVar.getName()).as(\"Unexpected env var name\").isEqualTo(\"SECRET_USERNAME\");\n        secretKeySelector = secretKeyRefEnvVar.getValueFrom().getSecretKeyRef();\n        assertThat(secretKeySelector.getName()).as(\"Unexpected secret name\").isEqualTo(\"mySecret2\");\n        assertThat(secretKeySelector.getKey()).as(\"Unexpected secret data key\").isEqualTo(\"username\");\n    }\n\n    @Test\n    public void testSecretKeyRefGlobal() {\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), null);\n\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n        KubernetesDeployerProperties.SecretKeyRef secretKeyRef = new KubernetesDeployerProperties.SecretKeyRef();\n        secretKeyRef.setEnvVarName(\"SECRET_PASSWORD_GLOBAL\");\n        secretKeyRef.setSecretName(\"mySecretGlobal\");\n        secretKeyRef.setDataKey(\"passwordGlobal\");\n        kubernetesDeployerProperties.setSecretKeyRefs(Collections.singletonList(secretKeyRef));\n\n        deployer = k8sAppDeployer(kubernetesDeployerProperties);\n        PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);\n\n        List<EnvVar> envVars = podSpec.getContainers().get(0).getEnv();\n\n        assertThat(envVars.size()).as(\"Invalid number of env vars\").isEqualTo(2);\n\n        EnvVar secretKeyRefEnvVar = envVars.get(0);\n        assertThat(secretKeyRefEnvVar.getName()).as(\"Unexpected env var name\").isEqualTo(\"SECRET_PASSWORD_GLOBAL\");\n        SecretKeySelector secretKeySelector = secretKeyRefEnvVar.getValueFrom().getSecretKeyRef();\n        assertThat(secretKeySelector.getName()).as(\"Unexpected secret name\").isEqualTo(\"mySecretGlobal\");\n        assertThat(secretKeySelector.getKey()).as(\"Unexpected secret data key\").isEqualTo(\"passwordGlobal\");\n    }\n\n    @Test\n    public void testSecretKeyRefPropertyOverride() {\n        Map<String, String> props = new HashMap<>();\n        props.put(\"spring.cloud.deployer.kubernetes.secretKeyRefs\",\n                \"[{envVarName: 'SECRET_PASSWORD_GLOBAL', secretName: 'mySecret', dataKey: 'password'},\" +\n                        \"{envVarName: 'SECRET_USERNAME', secretName: 'mySecret2', dataKey: 'username'}]\");\n\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), props);\n\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n\n        List<KubernetesDeployerProperties.SecretKeyRef> globalSecretKeyRefs = new ArrayList<>();\n        KubernetesDeployerProperties.SecretKeyRef globalSecretKeyRef1 = new KubernetesDeployerProperties.SecretKeyRef();\n        globalSecretKeyRef1.setEnvVarName(\"SECRET_PASSWORD_GLOBAL\");\n        globalSecretKeyRef1.setSecretName(\"mySecretGlobal\");\n        globalSecretKeyRef1.setDataKey(\"passwordGlobal\");\n\n        KubernetesDeployerProperties.SecretKeyRef globalSecretKeyRef2 = new KubernetesDeployerProperties.SecretKeyRef();\n        globalSecretKeyRef2.setEnvVarName(\"SECRET_USERNAME_GLOBAL\");\n        globalSecretKeyRef2.setSecretName(\"mySecretGlobal\");\n        globalSecretKeyRef2.setDataKey(\"usernameGlobal\");\n\n        globalSecretKeyRefs.add(globalSecretKeyRef1);\n        globalSecretKeyRefs.add(globalSecretKeyRef2);\n\n        kubernetesDeployerProperties.setSecretKeyRefs(globalSecretKeyRefs);\n\n        deployer = k8sAppDeployer(kubernetesDeployerProperties);\n        PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);\n\n        List<EnvVar> envVars = podSpec.getContainers().get(0).getEnv();\n\n        assertThat(envVars.size()).as(\"Invalid number of env vars\").isEqualTo(4);\n\n        // deploy prop overrides global\n        EnvVar secretKeyRefEnvVar = envVars.get(0);\n        assertThat(secretKeyRefEnvVar.getName()).as(\"Unexpected env var name\").isEqualTo(\"SECRET_PASSWORD_GLOBAL\");\n        SecretKeySelector secretKeySelector = secretKeyRefEnvVar.getValueFrom().getSecretKeyRef();\n        assertThat(secretKeySelector.getName()).as(\"Unexpected secret name\").isEqualTo(\"mySecret\");\n        assertThat(secretKeySelector.getKey()).as(\"Unexpected secret data key\").isEqualTo(\"password\");\n\n        // unique deploy prop\n        secretKeyRefEnvVar = envVars.get(1);\n        assertThat(secretKeyRefEnvVar.getName()).as(\"Unexpected env var name\").isEqualTo(\"SECRET_USERNAME\");\n        secretKeySelector = secretKeyRefEnvVar.getValueFrom().getSecretKeyRef();\n        assertThat(secretKeySelector.getName()).as(\"Unexpected secret name\").isEqualTo(\"mySecret2\");\n        assertThat(secretKeySelector.getKey()).as(\"Unexpected secret data key\").isEqualTo(\"username\");\n\n        // unique, non-overridden global prop\n        secretKeyRefEnvVar = envVars.get(2);\n        assertThat(secretKeyRefEnvVar.getName()).as(\"Unexpected env var name\").isEqualTo(\"SECRET_USERNAME_GLOBAL\");\n        secretKeySelector = secretKeyRefEnvVar.getValueFrom().getSecretKeyRef();\n        assertThat(secretKeySelector.getName()).as(\"Unexpected secret name\").isEqualTo(\"mySecretGlobal\");\n        assertThat(secretKeySelector.getKey()).as(\"Unexpected secret data key\").isEqualTo(\"usernameGlobal\");\n    }\n\n    @Test\n    public void testSecretKeyRefGlobalFromYaml() throws Exception {\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), null);\n\n        deployer = k8sAppDeployer();\n        PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);\n\n        List<EnvVar> envVars = podSpec.getContainers().get(0).getEnv();\n\n        assertThat(envVars.size()).as(\"Invalid number of env vars\").isEqualTo(3);\n\n        EnvVar secretKeyRefEnvVar = envVars.get(0);\n        assertThat(secretKeyRefEnvVar.getName()).as(\"Unexpected env var name\").isEqualTo(\"SECRET_PASSWORD\");\n        SecretKeySelector secretKeySelector = secretKeyRefEnvVar.getValueFrom().getSecretKeyRef();\n        assertThat(secretKeySelector.getName()).as(\"Unexpected secret name\").isEqualTo(\"mySecret\");\n        assertThat(secretKeySelector.getKey()).as(\"Unexpected secret data key\").isEqualTo(\"myPassword\");\n    }\n\n    @Test\n    public void testConfigMapKeyRef() {\n        Map<String, String> props = new HashMap<>();\n        props.put(\"spring.cloud.deployer.kubernetes.configMapKeyRefs\",\n                \"[{envVarName: 'MY_ENV', configMapName: 'myConfigMap', dataKey: 'envName'}]\");\n\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), props);\n\n        deployer = k8sAppDeployer(new KubernetesDeployerProperties());\n        PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);\n\n        List<EnvVar> envVars = podSpec.getContainers().get(0).getEnv();\n\n        assertThat(envVars.size()).as(\"Invalid number of env vars\").isEqualTo(2);\n\n        EnvVar configMapKeyRefEnvVar = envVars.get(0);\n        assertThat(configMapKeyRefEnvVar.getName()).as(\"Unexpected env var name\").isEqualTo(\"MY_ENV\");\n        ConfigMapKeySelector configMapKeySelector = configMapKeyRefEnvVar.getValueFrom().getConfigMapKeyRef();\n        assertThat(configMapKeySelector.getName()).as(\"Unexpected config map name\").isEqualTo(\"myConfigMap\");\n        assertThat(configMapKeySelector.getKey()).as(\"Unexpected config map data key\").isEqualTo(\"envName\");\n    }\n\n    @Test\n    public void testConfigMapKeyRefMultiple() {\n        Map<String, String> props = new HashMap<>();\n        props.put(\"spring.cloud.deployer.kubernetes.configMapKeyRefs\",\n                \"[{envVarName: 'MY_ENV', configMapName: 'myConfigMap', dataKey: 'envName'},\" +\n                        \"{envVarName: 'ENV_VALUES', configMapName: 'myOtherConfigMap', dataKey: 'diskType'}]\");\n\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), props);\n\n        deployer = k8sAppDeployer(new KubernetesDeployerProperties());\n        PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);\n\n        List<EnvVar> envVars = podSpec.getContainers().get(0).getEnv();\n\n        assertThat(envVars.size()).as(\"Invalid number of env vars\").isEqualTo(3);\n\n        EnvVar configMapKeyRefEnvVar = envVars.get(0);\n        assertThat(configMapKeyRefEnvVar.getName()).as(\"Unexpected env var name\").isEqualTo(\"MY_ENV\");\n        ConfigMapKeySelector configMapKeySelector = configMapKeyRefEnvVar.getValueFrom().getConfigMapKeyRef();\n        assertThat(configMapKeySelector.getName()).as(\"Unexpected config map name\").isEqualTo(\"myConfigMap\");\n        assertThat(configMapKeySelector.getKey()).as(\"Unexpected config map data key\").isEqualTo(\"envName\");\n\n        configMapKeyRefEnvVar = envVars.get(1);\n        assertThat(configMapKeyRefEnvVar.getName()).as(\"Unexpected env var name\").isEqualTo(\"ENV_VALUES\");\n        configMapKeySelector = configMapKeyRefEnvVar.getValueFrom().getConfigMapKeyRef();\n        assertThat(configMapKeySelector.getName()).as(\"Unexpected config map name\").isEqualTo(\"myOtherConfigMap\");\n        assertThat(configMapKeySelector.getKey()).as(\"Unexpected config map data key\").isEqualTo(\"diskType\");\n    }\n\n    @Test\n    public void testConfigMapKeyRefGlobal() {\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), null);\n\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n        KubernetesDeployerProperties.ConfigMapKeyRef configMapKeyRef = new KubernetesDeployerProperties.ConfigMapKeyRef();\n        configMapKeyRef.setEnvVarName(\"MY_ENV_GLOBAL\");\n        configMapKeyRef.setConfigMapName(\"myConfigMapGlobal\");\n        configMapKeyRef.setDataKey(\"envGlobal\");\n        kubernetesDeployerProperties.setConfigMapKeyRefs(Collections.singletonList(configMapKeyRef));\n\n        deployer = k8sAppDeployer(kubernetesDeployerProperties);\n        PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);\n\n        List<EnvVar> envVars = podSpec.getContainers().get(0).getEnv();\n\n        assertThat(envVars.size()).as(\"Invalid number of env vars\").isEqualTo(2);\n\n        EnvVar configMapKeyRefEnvVar = envVars.get(0);\n        assertThat(configMapKeyRefEnvVar.getName()).as(\"Unexpected env var name\").isEqualTo(\"MY_ENV_GLOBAL\");\n        ConfigMapKeySelector configMapKeySelector = configMapKeyRefEnvVar.getValueFrom().getConfigMapKeyRef();\n        assertThat(configMapKeySelector.getName()).as(\"Unexpected config map name\").isEqualTo(\"myConfigMapGlobal\");\n        assertThat(configMapKeySelector.getKey()).as(\"Unexpected config data key\").isEqualTo(\"envGlobal\");\n    }\n\n    @Test\n    public void testConfigMapKeyRefPropertyOverride() {\n        Map<String, String> props = new HashMap<>();\n        props.put(\"spring.cloud.deployer.kubernetes.configMapKeyRefs\",\n                \"[{envVarName: 'MY_ENV', configMapName: 'myConfigMap', dataKey: 'envName'},\" +\n                        \"{envVarName: 'ENV_VALUES', configMapName: 'myOtherConfigMap', dataKey: 'diskType'}]\");\n\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), props);\n\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n\n        List<KubernetesDeployerProperties.ConfigMapKeyRef> globalConfigMapKeyRefs = new ArrayList<>();\n        KubernetesDeployerProperties.ConfigMapKeyRef globalConfigMapKeyRef1 = new KubernetesDeployerProperties.ConfigMapKeyRef();\n        globalConfigMapKeyRef1.setEnvVarName(\"MY_ENV\");\n        globalConfigMapKeyRef1.setConfigMapName(\"myEnvGlobal\");\n        globalConfigMapKeyRef1.setDataKey(\"envGlobal\");\n\n        KubernetesDeployerProperties.ConfigMapKeyRef globalConfigMapKeyRef2 = new KubernetesDeployerProperties.ConfigMapKeyRef();\n        globalConfigMapKeyRef2.setEnvVarName(\"MY_VALS_GLOBAL\");\n        globalConfigMapKeyRef2.setConfigMapName(\"myValsGlobal\");\n        globalConfigMapKeyRef2.setDataKey(\"valsGlobal\");\n\n        globalConfigMapKeyRefs.add(globalConfigMapKeyRef1);\n        globalConfigMapKeyRefs.add(globalConfigMapKeyRef2);\n\n        kubernetesDeployerProperties.setConfigMapKeyRefs(globalConfigMapKeyRefs);\n\n        deployer = k8sAppDeployer(kubernetesDeployerProperties);\n        PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);\n\n        List<EnvVar> envVars = podSpec.getContainers().get(0).getEnv();\n\n        assertThat(envVars.size()).as(\"Invalid number of env vars\").isEqualTo(4);\n\n        // deploy prop overrides global\n        EnvVar configMapKeyRefEnvVar = envVars.get(0);\n        assertThat(configMapKeyRefEnvVar.getName()).as(\"Unexpected env var name\").isEqualTo(\"MY_ENV\");\n        ConfigMapKeySelector configMapKeySelector = configMapKeyRefEnvVar.getValueFrom().getConfigMapKeyRef();\n        assertThat(configMapKeySelector.getName()).as(\"Unexpected config map name\").isEqualTo(\"myConfigMap\");\n        assertThat(configMapKeySelector.getKey()).as(\"Unexpected config map data key\").isEqualTo(\"envName\");\n\n        // unique deploy prop\n        configMapKeyRefEnvVar = envVars.get(1);\n        assertThat(configMapKeyRefEnvVar.getName()).as(\"Unexpected env var name\").isEqualTo(\"ENV_VALUES\");\n        configMapKeySelector = configMapKeyRefEnvVar.getValueFrom().getConfigMapKeyRef();\n        assertThat(configMapKeySelector.getName()).as(\"Unexpected config map name\").isEqualTo(\"myOtherConfigMap\");\n        assertThat(configMapKeySelector.getKey()).as(\"Unexpected config map data key\").isEqualTo(\"diskType\");\n\n        // unique, non-overridden global prop\n        configMapKeyRefEnvVar = envVars.get(2);\n        assertThat(configMapKeyRefEnvVar.getName()).as(\"Unexpected env var name\").isEqualTo(\"MY_VALS_GLOBAL\");\n        configMapKeySelector = configMapKeyRefEnvVar.getValueFrom().getConfigMapKeyRef();\n        assertThat(configMapKeySelector.getName()).as(\"Unexpected config map name\").isEqualTo(\"myValsGlobal\");\n        assertThat(configMapKeySelector.getKey()).as(\"Unexpected config map data key\").isEqualTo(\"valsGlobal\");\n    }\n\n    @Test\n    public void testConfigMapKeyRefGlobalFromYaml() throws Exception {\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), null);\n\n        deployer = k8sAppDeployer();\n        PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);\n\n        List<EnvVar> envVars = podSpec.getContainers().get(0).getEnv();\n\n        assertThat(envVars.size()).as(\"Invalid number of env vars\").isEqualTo(3);\n\n        EnvVar configMapKeyRefEnvVar = envVars.get(1);\n        assertThat(configMapKeyRefEnvVar.getName()).as(\"Unexpected env var name\").isEqualTo(\"MY_ENV\");\n        ConfigMapKeySelector configMapKeySelector = configMapKeyRefEnvVar.getValueFrom().getConfigMapKeyRef();\n        assertThat(configMapKeySelector.getName()).as(\"Unexpected config map name\").isEqualTo(\"myConfigMap\");\n        assertThat(configMapKeySelector.getKey()).as(\"Unexpected config map data key\").isEqualTo(\"envName\");\n    }\n\n    @Test\n    public void testNodeAffinityProperty() {\n        Map<String, String> props = new HashMap<>();\n        props.put(\"spring.cloud.deployer.kubernetes.affinity.nodeAffinity\",\n                \"{ requiredDuringSchedulingIgnoredDuringExecution:\" +\n                        \"  { nodeSelectorTerms:\" +\n                        \"    [ { matchExpressions:\" +\n                        \"        [ { key: 'kubernetes.io/e2e-az-name', \" +\n                        \"            operator: 'In',\" +\n                        \"            values:\" +\n                        \"            [ 'e2e-az1', 'e2e-az2']}]}]}, \" +\n                        \"  preferredDuringSchedulingIgnoredDuringExecution:\" +\n                        \"  [ { weight: 1,\" +\n                        \"      preference:\" +\n                        \"      { matchExpressions:\" +\n                        \"        [ { key: 'another-node-label-key',\" +\n                        \"            operator: 'In',\" +\n                        \"            values:\" +\n                        \"            [ 'another-node-label-value' ]}]}}]}\");\n\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), props);\n\n        deployer = k8sAppDeployer(new KubernetesDeployerProperties());\n        PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);\n\n        NodeAffinity nodeAffinity = podSpec.getAffinity().getNodeAffinity();\n        assertThat(nodeAffinity).as(\"Node affinity should not be null\").isNotNull();\n        assertThat(nodeAffinity.getRequiredDuringSchedulingIgnoredDuringExecution()).as(\"RequiredDuringSchedulingIgnoredDuringExecution should not be null\").isNotNull();\n        assertThat(nodeAffinity.getPreferredDuringSchedulingIgnoredDuringExecution().size()).as(\"PreferredDuringSchedulingIgnoredDuringExecution should have one element\").isEqualTo(1);\n    }\n\n    @Test\n    public void testPodAffinityProperty() {\n        Map<String, String> props = new HashMap<>();\n        props.put(\"spring.cloud.deployer.kubernetes.affinity.podAffinity\",\n                \"{ requiredDuringSchedulingIgnoredDuringExecution:\" +\n                        \"  { labelSelector:\" +\n                        \"    [ { matchExpressions:\" +\n                        \"        [ { key: 'app', \" +\n                        \"            operator: 'In',\" +\n                        \"            values:\" +\n                        \"            [ 'store']}]}], \" +\n                        \"     topologyKey: 'kubernetes.io/hostname'}, \" +\n                        \"  preferredDuringSchedulingIgnoredDuringExecution:\" +\n                        \"  [ { weight: 1,\" +\n                        \"      podAffinityTerm:\" +\n                        \"      { labelSelector:\" +\n                        \"        { matchExpressions:\" +\n                        \"          [ { key: 'security',\" +\n                        \"              operator: 'In',\" +\n                        \"              values:\" +\n                        \"              [ 'S2' ]}]}, \" +\n                        \"        topologyKey: 'failure-domain.beta.kubernetes.io/zone'}}]}\");\n\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), props);\n\n        deployer = k8sAppDeployer(new KubernetesDeployerProperties());\n        PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);\n\n        PodAffinity podAffinity = podSpec.getAffinity().getPodAffinity();\n        assertThat(podAffinity).as(\"Pod affinity should not be null\").isNotNull();\n        assertThat(podAffinity.getRequiredDuringSchedulingIgnoredDuringExecution()).as(\"RequiredDuringSchedulingIgnoredDuringExecution should not be null\").isNotNull();\n        assertThat(podAffinity.getPreferredDuringSchedulingIgnoredDuringExecution().size()).as(\"PreferredDuringSchedulingIgnoredDuringExecution should have one element\").isEqualTo(1);\n    }\n\n    @Test\n    public void testPodAntiAffinityProperty() {\n        Map<String, String> props = new HashMap<>();\n        props.put(\"spring.cloud.deployer.kubernetes.affinity.podAntiAffinity\",\n                \"{ requiredDuringSchedulingIgnoredDuringExecution:\" +\n                        \"  { labelSelector:\" +\n                        \"    [ { matchExpressions:\" +\n                        \"        [ { key: 'app', \" +\n                        \"            operator: 'In',\" +\n                        \"            values:\" +\n                        \"            [ 'store']}]}], \" +\n                        \"     topologyKey: 'kubernetes.io/hostname'}, \" +\n                        \"  preferredDuringSchedulingIgnoredDuringExecution:\" +\n                        \"  [ { weight: 1,\" +\n                        \"      podAffinityTerm:\" +\n                        \"      { labelSelector:\" +\n                        \"        { matchExpressions:\" +\n                        \"          [ { key: 'security',\" +\n                        \"              operator: 'In',\" +\n                        \"              values:\" +\n                        \"              [ 'S2' ]}]}, \" +\n                        \"        topologyKey: 'failure-domain.beta.kubernetes.io/zone'}}]}\");\n\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), props);\n\n        deployer = k8sAppDeployer(new KubernetesDeployerProperties());\n        PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);\n\n        PodAntiAffinity podAntiAffinity = podSpec.getAffinity().getPodAntiAffinity();\n        assertThat(podAntiAffinity).as(\"Pod anti-affinity should not be null\").isNotNull();\n        assertThat(podAntiAffinity.getRequiredDuringSchedulingIgnoredDuringExecution()).as(\"RequiredDuringSchedulingIgnoredDuringExecution should not be null\").isNotNull();\n        assertThat(podAntiAffinity.getPreferredDuringSchedulingIgnoredDuringExecution().size()).as(\"PreferredDuringSchedulingIgnoredDuringExecution should have one element\").isEqualTo(1);\n    }\n\n    @Test\n    public void testNodeAffinityGlobalProperty() {\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), null);\n\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n\n        NodeSelectorTerm nodeSelectorTerm = new NodeSelectorTerm();\n        nodeSelectorTerm.setMatchExpressions(Arrays.asList(new NodeSelectorRequirementBuilder()\n                .withKey(\"kubernetes.io/e2e-az-name\")\n                .withOperator(\"In\")\n                .withValues(\"e2e-az1\", \"e2e-az2\")\n                .build()));\n        NodeSelectorTerm nodeSelectorTerm2 = new NodeSelectorTerm();\n        nodeSelectorTerm2.setMatchExpressions(Arrays.asList(new NodeSelectorRequirementBuilder()\n                .withKey(\"another-node-label-key\")\n                .withOperator(\"In\")\n                .withValues(\"another-node-label-value2\")\n                .build()));\n        PreferredSchedulingTerm preferredSchedulingTerm = new PreferredSchedulingTerm(nodeSelectorTerm2, 1);\n        NodeAffinity nodeAffinity = new AffinityBuilder()\n                .withNewNodeAffinity()\n                .withNewRequiredDuringSchedulingIgnoredDuringExecution()\n                .withNodeSelectorTerms(nodeSelectorTerm)\n                .endRequiredDuringSchedulingIgnoredDuringExecution()\n                .withPreferredDuringSchedulingIgnoredDuringExecution(preferredSchedulingTerm)\n                .endNodeAffinity()\n                .buildNodeAffinity();\n\n        kubernetesDeployerProperties.setNodeAffinity(nodeAffinity);\n\n        deployer = k8sAppDeployer(kubernetesDeployerProperties);\n        PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);\n\n        NodeAffinity nodeAffinityTest = podSpec.getAffinity().getNodeAffinity();\n        assertThat(nodeAffinityTest).as(\"Node affinity should not be null\").isNotNull();\n        assertThat(nodeAffinityTest.getRequiredDuringSchedulingIgnoredDuringExecution()).as(\"RequiredDuringSchedulingIgnoredDuringExecution should not be null\").isNotNull();\n        assertThat(nodeAffinityTest.getPreferredDuringSchedulingIgnoredDuringExecution().size()).as(\"PreferredDuringSchedulingIgnoredDuringExecution should have one element\").isEqualTo(1);\n    }\n\n    @Test\n    public void testPodAffinityGlobalProperty() {\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), null);\n\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n\n        LabelSelector labelSelector = new LabelSelector();\n        labelSelector.setMatchExpressions(Arrays.asList(new LabelSelectorRequirementBuilder()\n                .withKey(\"security\")\n                .withOperator(\"In\")\n                .withValues(\"S1\")\n                .build()));\n        PodAffinityTerm podAffinityTerm = new PodAffinityTerm(labelSelector, null, null, \"failure-domain.beta.kubernetes.io/zone\");\n        LabelSelector labelSelector2 = new LabelSelector();\n        labelSelector2.setMatchExpressions(Arrays.asList(new LabelSelectorRequirementBuilder()\n                .withKey(\"security\")\n                .withOperator(\"In\")\n                .withValues(\"s2\")\n                .build()));\n        PodAffinityTerm podAffinityTerm2 = new PodAffinityTerm(labelSelector2, null, null, \"failure-domain.beta.kubernetes.io/zone\");\n        WeightedPodAffinityTerm weightedPodAffinityTerm = new WeightedPodAffinityTerm(podAffinityTerm2, 100);\n        PodAffinity podAffinity = new AffinityBuilder()\n                .withNewPodAffinity()\n                .withRequiredDuringSchedulingIgnoredDuringExecution(podAffinityTerm)\n                .withPreferredDuringSchedulingIgnoredDuringExecution(weightedPodAffinityTerm)\n                .endPodAffinity()\n                .buildPodAffinity();\n\n        kubernetesDeployerProperties.setPodAffinity(podAffinity);\n\n        deployer = k8sAppDeployer(kubernetesDeployerProperties);\n        PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);\n\n        PodAffinity podAffinityTest = podSpec.getAffinity().getPodAffinity();\n        assertThat(podAffinityTest).as(\"Pod affinity should not be null\").isNotNull();\n        assertThat(podAffinityTest.getRequiredDuringSchedulingIgnoredDuringExecution()).as(\"RequiredDuringSchedulingIgnoredDuringExecution should not be null\").isNotNull();\n        assertThat(podAffinityTest.getPreferredDuringSchedulingIgnoredDuringExecution().size()).as(\"PreferredDuringSchedulingIgnoredDuringExecution should have one element\").isEqualTo(1);\n    }\n\n    @Test\n    public void testPodAntiAffinityGlobalProperty() {\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), null);\n\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n        kubernetesDeployerProperties.setStartupHttpProbePort(kubernetesDeployerProperties.getLivenessHttpProbePort());\n        LabelSelector labelSelector = new LabelSelector();\n        labelSelector.setMatchExpressions(Arrays.asList(new LabelSelectorRequirementBuilder()\n                .withKey(\"app\")\n                .withOperator(\"In\")\n                .withValues(\"store\")\n                .build()));\n        PodAffinityTerm podAffinityTerm = new PodAffinityTerm(labelSelector, null, null, \"kubernetes.io/hostname\");\n        LabelSelector labelSelector2 = new LabelSelector();\n        labelSelector2.setMatchExpressions(Arrays.asList(new LabelSelectorRequirementBuilder()\n                .withKey(\"security\")\n                .withOperator(\"In\")\n                .withValues(\"s2\")\n                .build()));\n        PodAffinityTerm podAffinityTerm2 = new PodAffinityTerm(labelSelector2, null, null, \"failure-domain.beta.kubernetes.io/zone\");\n        WeightedPodAffinityTerm weightedPodAffinityTerm = new WeightedPodAffinityTerm(podAffinityTerm2, 100);\n        PodAntiAffinity podAntiAffinity = new AffinityBuilder()\n                .withNewPodAntiAffinity()\n                .withRequiredDuringSchedulingIgnoredDuringExecution(podAffinityTerm)\n                .withPreferredDuringSchedulingIgnoredDuringExecution(weightedPodAffinityTerm)\n                .endPodAntiAffinity()\n                .buildPodAntiAffinity();\n\n        kubernetesDeployerProperties.setPodAntiAffinity(podAntiAffinity);\n\n        deployer = k8sAppDeployer(kubernetesDeployerProperties);\n        PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);\n\n        PodAntiAffinity podAntiAffinityTest = podSpec.getAffinity().getPodAntiAffinity();\n        assertThat(podAntiAffinityTest).as(\"Pod anti-affinity should not be null\").isNotNull();\n        assertThat(podAntiAffinityTest.getRequiredDuringSchedulingIgnoredDuringExecution()).as(\"RequiredDuringSchedulingIgnoredDuringExecution should not be null\").isNotNull();\n        assertThat(podAntiAffinityTest.getPreferredDuringSchedulingIgnoredDuringExecution().size()).as(\"PreferredDuringSchedulingIgnoredDuringExecution should have one element\").isEqualTo(1);\n    }\n\n    @Test\n    public void testNodeAffinityFromYaml() throws Exception {\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), null);\n\n        deployer = k8sAppDeployer();\n        PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);\n\n        NodeAffinity nodeAffinity = podSpec.getAffinity().getNodeAffinity();\n        assertThat(nodeAffinity).as(\"Node affinity should not be null\").isNotNull();\n        assertThat(nodeAffinity.getRequiredDuringSchedulingIgnoredDuringExecution()).as(\"RequiredDuringSchedulingIgnoredDuringExecution should not be null\").isNotNull();\n        assertThat(nodeAffinity.getPreferredDuringSchedulingIgnoredDuringExecution().size()).as(\"PreferredDuringSchedulingIgnoredDuringExecution should have one element\").isEqualTo(1);\n    }\n\n    @Test\n    public void testPodAffinityFromYaml() throws Exception {\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), null);\n\n        deployer = k8sAppDeployer();\n        PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);\n\n        PodAffinity podAffinity = podSpec.getAffinity().getPodAffinity();\n        assertThat(podAffinity).as(\"Pod affinity should not be null\").isNotNull();\n        assertThat(podAffinity.getRequiredDuringSchedulingIgnoredDuringExecution()).as(\"RequiredDuringSchedulingIgnoredDuringExecution should not be null\").isNotNull();\n        assertThat(podAffinity.getPreferredDuringSchedulingIgnoredDuringExecution().size()).as(\"PreferredDuringSchedulingIgnoredDuringExecution should have one element\").isEqualTo(1);\n    }\n\n    @Test\n    public void testPodAntiAffinityFromYaml() throws Exception {\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), null);\n\n        deployer = k8sAppDeployer();\n        PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);\n\n        PodAntiAffinity podAntiAffinity = podSpec.getAffinity().getPodAntiAffinity();\n        assertThat(podAntiAffinity).as(\"Pod anti-affinity should not be null\").isNotNull();\n        assertThat(podAntiAffinity.getRequiredDuringSchedulingIgnoredDuringExecution()).as(\"RequiredDuringSchedulingIgnoredDuringExecution should not be null\").isNotNull();\n        assertThat(podAntiAffinity.getPreferredDuringSchedulingIgnoredDuringExecution().size()).as(\"PreferredDuringSchedulingIgnoredDuringExecution should have one element\").isEqualTo(1);\n    }\n\n    @Test\n    public void testNodeAffinityPropertyOverrideGlobal() {\n        Map<String, String> props = new HashMap<>();\n        props.put(\"spring.cloud.deployer.kubernetes.affinity.nodeAffinity\",\n                \"{ requiredDuringSchedulingIgnoredDuringExecution:\" +\n                        \"  { nodeSelectorTerms:\" +\n                        \"    [ { matchExpressions:\" +\n                        \"        [ { key: 'kubernetes.io/e2e-az-name', \" +\n                        \"            operator: 'In',\" +\n                        \"            values:\" +\n                        \"            [ 'e2e-az1', 'e2e-az2']}]}]}, \" +\n                        \"  preferredDuringSchedulingIgnoredDuringExecution:\" +\n                        \"  [ { weight: 1,\" +\n                        \"      preference:\" +\n                        \"      { matchExpressions:\" +\n                        \"        [ { key: 'another-node-label-key',\" +\n                        \"            operator: 'In',\" +\n                        \"            values:\" +\n                        \"            [ 'another-node-label-value' ]}]}}]}\");\n\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), props);\n\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n\n        NodeSelectorTerm nodeSelectorTerm = new NodeSelectorTerm();\n        nodeSelectorTerm.setMatchExpressions(Arrays.asList(new NodeSelectorRequirementBuilder()\n                .withKey(\"kubernetes.io/e2e-az-name\")\n                .withOperator(\"In\")\n                .withValues(\"e2e-az1\", \"e2e-az2\")\n                .build()));\n        NodeAffinity nodeAffinity = new AffinityBuilder()\n                .withNewNodeAffinity()\n                .withNewRequiredDuringSchedulingIgnoredDuringExecution()\n                .withNodeSelectorTerms(nodeSelectorTerm)\n                .endRequiredDuringSchedulingIgnoredDuringExecution()\n                .endNodeAffinity()\n                .buildNodeAffinity();\n\n        kubernetesDeployerProperties.setNodeAffinity(nodeAffinity);\n\n        deployer = k8sAppDeployer(kubernetesDeployerProperties);\n        PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);\n\n        NodeAffinity nodeAffinityTest = podSpec.getAffinity().getNodeAffinity();\n        assertThat(nodeAffinityTest).as(\"Node affinity should not be null\").isNotNull();\n        assertThat(nodeAffinityTest.getRequiredDuringSchedulingIgnoredDuringExecution()).as(\"RequiredDuringSchedulingIgnoredDuringExecution should not be null\").isNotNull();\n        assertThat(nodeAffinityTest.getPreferredDuringSchedulingIgnoredDuringExecution().size()).as(\"PreferredDuringSchedulingIgnoredDuringExecution should have one element\").isEqualTo(1);\n    }\n\n    @Test\n    public void testPodAffinityPropertyOverrideGlobal() {\n        Map<String, String> props = new HashMap<>();\n        props.put(\"spring.cloud.deployer.kubernetes.affinity.podAffinity\",\n                \"{ requiredDuringSchedulingIgnoredDuringExecution:\" +\n                        \"  { labelSelector:\" +\n                        \"    [ { matchExpressions:\" +\n                        \"        [ { key: 'security', \" +\n                        \"            operator: 'In',\" +\n                        \"            values:\" +\n                        \"            [ 'S1']}]}], \" +\n                        \"     topologyKey: 'failure-domain.beta.kubernetes.io/zone'}, \" +\n                        \"  preferredDuringSchedulingIgnoredDuringExecution:\" +\n                        \"  [ { weight: 1,\" +\n                        \"      podAffinityTerm:\" +\n                        \"      { labelSelector:\" +\n                        \"        { matchExpressions:\" +\n                        \"          [ { key: 'security',\" +\n                        \"              operator: 'In',\" +\n                        \"              values:\" +\n                        \"              [ 'S2' ]}]}, \" +\n                        \"        topologyKey: 'failure-domain.beta.kubernetes.io/zone'}}]}\");\n\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), props);\n\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n\n        LabelSelector labelSelector = new LabelSelector();\n        labelSelector.setMatchExpressions(Arrays.asList(new LabelSelectorRequirementBuilder()\n                .withKey(\"tolerance\")\n                .withOperator(\"In\")\n                .withValues(\"Reliable\")\n                .build()));\n        PodAffinityTerm podAffinityTerm = new PodAffinityTerm(labelSelector, null, null, \"failure-domain.beta.kubernetes.io/zone\");\n        PodAffinity podAffinity = new AffinityBuilder()\n                .withNewPodAffinity()\n                .withRequiredDuringSchedulingIgnoredDuringExecution(podAffinityTerm)\n                .endPodAffinity()\n                .buildPodAffinity();\n\n        kubernetesDeployerProperties.setPodAffinity(podAffinity);\n\n        deployer = k8sAppDeployer(kubernetesDeployerProperties);\n        PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);\n\n        PodAffinity podAffinityTest = podSpec.getAffinity().getPodAffinity();\n        assertThat(podAffinityTest).as(\"Pod affinity should not be null\").isNotNull();\n        assertThat(podAffinityTest.getRequiredDuringSchedulingIgnoredDuringExecution()).as(\"RequiredDuringSchedulingIgnoredDuringExecution should not be null\").isNotNull();\n        assertThat(podAffinityTest.getPreferredDuringSchedulingIgnoredDuringExecution().size()).as(\"PreferredDuringSchedulingIgnoredDuringExecution should have one element\").isEqualTo(1);\n    }\n\n    @Test\n    public void testPodAntiAffinityPropertyOverrideGlobal() {\n        Map<String, String> props = new HashMap<>();\n        props.put(\"spring.cloud.deployer.kubernetes.affinity.podAntiAffinity\",\n                \"{ requiredDuringSchedulingIgnoredDuringExecution:\" +\n                        \"  { labelSelector:\" +\n                        \"    [ { matchExpressions:\" +\n                        \"        [ { key: 'app', \" +\n                        \"            operator: 'In',\" +\n                        \"            values:\" +\n                        \"            [ 'store']}]}], \" +\n                        \"     topologyKey: 'kubernetes.io/hostnam'}, \" +\n                        \"  preferredDuringSchedulingIgnoredDuringExecution:\" +\n                        \"  [ { weight: 1,\" +\n                        \"      podAffinityTerm:\" +\n                        \"      { labelSelector:\" +\n                        \"        { matchExpressions:\" +\n                        \"          [ { key: 'security',\" +\n                        \"              operator: 'In',\" +\n                        \"              values:\" +\n                        \"              [ 'S2' ]}]}, \" +\n                        \"        topologyKey: 'failure-domain.beta.kubernetes.io/zone'}}]}\");\n\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), props);\n\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n\n        LabelSelector labelSelector = new LabelSelector();\n        labelSelector.setMatchExpressions(Arrays.asList(new LabelSelectorRequirementBuilder()\n                .withKey(\"version\")\n                .withOperator(\"Equals\")\n                .withValues(\"v1\")\n                .build()));\n        PodAffinityTerm podAffinityTerm = new PodAffinityTerm(labelSelector, null, null, \"kubernetes.io/hostnam\");\n        PodAntiAffinity podAntiAffinity = new AffinityBuilder()\n                .withNewPodAntiAffinity()\n                .withRequiredDuringSchedulingIgnoredDuringExecution(podAffinityTerm)\n                .endPodAntiAffinity()\n                .buildPodAntiAffinity();\n\n        kubernetesDeployerProperties.setPodAntiAffinity(podAntiAffinity);\n\n        deployer = k8sAppDeployer(kubernetesDeployerProperties);\n        PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);\n\n        PodAntiAffinity podAntiAffinityTest = podSpec.getAffinity().getPodAntiAffinity();\n        assertThat(podAntiAffinityTest).as(\"Pod anti-affinity should not be null\").isNotNull();\n        assertThat(podAntiAffinityTest.getRequiredDuringSchedulingIgnoredDuringExecution()).as(\"RequiredDuringSchedulingIgnoredDuringExecution should not be null\").isNotNull();\n        assertThat(podAntiAffinityTest.getPreferredDuringSchedulingIgnoredDuringExecution().size()).as(\"PreferredDuringSchedulingIgnoredDuringExecution should have one element\").isEqualTo(1);\n    }\n\n\t@Nested\n\t@DisplayName(\"creates pod spec with pod security context\")\n    class CreatePodSpecWithPodSecurityContext {\n\n        @Test\n        @DisplayName(\"created from deployment property with all fields\")\n        void createdFromDeploymentPropertyWithAllFields() {\n            KubernetesDeployerProperties globalDeployerProps = new KubernetesDeployerProperties();\n            Map<String, String> deploymentProps = new HashMap<>();\n\t\t\tdeploymentProps.put(\"spring.cloud.deployer.kubernetes.podSecurityContext\", \"{\" +\n\t\t\t\t\t\"  fsGroup: 65534\" +\n\t\t\t\t\t\", fsGroupChangePolicy: Always\" +\n\t\t\t\t\t\", runAsUser: 65534\" +\n\t\t\t\t\t\", runAsGroup: 65534\" +\n\t\t\t\t\t\", runAsNonRoot: true\" +\n\t\t\t\t\t\", seLinuxOptions: { level: \\\"s0:c123,c456\\\" }\" +\n\t\t\t\t\t\", seccompProfile: { type: Localhost, localhostProfile: my-profiles/profile-allow.json }\" +\n\t\t\t\t\t\", supplementalGroups: [65534, 65535]\" +\n\t\t\t\t\t\", sysctls: [{name: \\\"kernel.shm_rmid_forced\\\", value: 0}, {name: \\\"net.core.somaxconn\\\", value: 1024}]\" +\n\t\t\t\t\t\", windowsOptions: { gmsaCredentialSpec: \\\"specA\\\", gmsaCredentialSpecName: \\\"specA-name\\\", hostProcess: true, runAsUserName: \\\"userA\\\" }\" +\n\t\t\t\t\t\"}\");\n            PodSecurityContext expectedPodSecurityContext = new PodSecurityContextBuilder()\n\t\t\t\t\t.withFsGroup(65534L)\n\t\t\t\t\t.withFsGroupChangePolicy(\"Always\")\n                    .withRunAsUser(65534L)\n\t\t\t\t\t.withRunAsGroup(65534L)\n\t\t\t\t\t.withRunAsNonRoot(true)\n\t\t\t\t\t.withSeLinuxOptions(new SELinuxOptions(\"s0:c123,c456\", null, null, null))\n\t\t\t\t\t.withSeccompProfile(new SeccompProfile(\"my-profiles/profile-allow.json\", \"Localhost\"))\n\t\t\t\t\t.withSupplementalGroups(65534L, 65535L)\n\t\t\t\t\t.withSysctls(new Sysctl(\"kernel.shm_rmid_forced\", \"0\"), new Sysctl(\"net.core.somaxconn\", \"1024\"))\n\t\t\t\t\t.withWindowsOptions(new WindowsSecurityContextOptions(\"specA\", \"specA-name\", true, \"userA\"))\n                    .build();\n            assertThatDeployerCreatesPodSpecWithPodSecurityContext(globalDeployerProps, deploymentProps, expectedPodSecurityContext);\n        }\n\n        @Test\n        @DisplayName(\"created from deployment property with runAsUser only\")\n        void createdFromDeploymentPropertyWithRunAsUserOnly() {\n            KubernetesDeployerProperties globalDeployerProps = new KubernetesDeployerProperties();\n            Map<String, String> deploymentProps = new HashMap<>();\n            deploymentProps.put(\"spring.cloud.deployer.kubernetes.podSecurityContext\", \"{runAsUser: 65534}\");\n            PodSecurityContext expectedPodSecurityContext = new PodSecurityContextBuilder()\n                    .withRunAsUser(65534L)\n                    .build();\n            assertThatDeployerCreatesPodSpecWithPodSecurityContext(globalDeployerProps, deploymentProps, expectedPodSecurityContext);\n        }\n\n        @Test\n        @DisplayName(\"created from deployment property with fsGroup only\")\n        void createdFromDeploymentPropertyWithFsGroupOnly() {\n            KubernetesDeployerProperties globalDeployerProps = new KubernetesDeployerProperties();\n            Map<String, String> deploymentProps = new HashMap<>();\n            deploymentProps.put(\"spring.cloud.deployer.kubernetes.podSecurityContext\", \"{fsGroup: 65534}\");\n            PodSecurityContext expectedPodSecurityContext = new PodSecurityContextBuilder()\n                    .withFsGroup(65534L)\n                    .build();\n            assertThatDeployerCreatesPodSpecWithPodSecurityContext(globalDeployerProps, deploymentProps, expectedPodSecurityContext);\n        }\n\n        @Test\n        @DisplayName(\"created from deployment property with supplementalGroups only\")\n        void createdFromDeploymentPropertyWithSupplementalGroupsOnly() {\n            KubernetesDeployerProperties globalDeployerProps = new KubernetesDeployerProperties();\n            Map<String, String> deploymentProps = new HashMap<>();\n            deploymentProps.put(\"spring.cloud.deployer.kubernetes.podSecurityContext\", \"{supplementalGroups: [65534,65535]}\");\n            PodSecurityContext expectedPodSecurityContext = new PodSecurityContextBuilder()\n                    .withSupplementalGroups(65534L, 65535L)\n                    .build();\n            assertThatDeployerCreatesPodSpecWithPodSecurityContext(globalDeployerProps, deploymentProps, expectedPodSecurityContext);\n        }\n\n        @Test\n        @DisplayName(\"created from deployment property with seccompProfile only\")\n        void createdFromDeploymentPropertyWithSeccompProfileOnly() {\n            KubernetesDeployerProperties globalDeployerProps = new KubernetesDeployerProperties();\n            Map<String, String> deploymentProps = new HashMap<>();\n            deploymentProps.put(\"spring.cloud.deployer.kubernetes.podSecurityContext\", \"{seccompProfile: { type: RuntimeDefault}}\");\n            PodSecurityContext expectedPodSecurityContext = new PodSecurityContextBuilder()\n                    .withSeccompProfile(new SeccompProfile(null, \"RuntimeDefault\"))\n                    .build();\n            assertThatDeployerCreatesPodSpecWithPodSecurityContext(globalDeployerProps, deploymentProps, expectedPodSecurityContext);\n        }\n\n        @Test\n        @DisplayName(\"created from global deployer property sourced from yaml\")\n        void createdFromGlobalDeployerPropertySourcedFromYaml() throws Exception {\n            KubernetesDeployerProperties globalDeployerProps = bindDeployerProperties();\n            Map<String, String> deploymentProps = new HashMap<>();\n\t\t\tPodSecurityContext expectedPodSecurityContext = new PodSecurityContextBuilder()\n\t\t\t\t\t.withFsGroup(65534L)\n\t\t\t\t\t.withFsGroupChangePolicy(\"Always\")\n\t\t\t\t\t.withRunAsUser(65534L)\n\t\t\t\t\t.withRunAsGroup(65534L)\n\t\t\t\t\t.withRunAsNonRoot(true)\n\t\t\t\t\t.withSeLinuxOptions(new SELinuxOptions(\"s0:c123,c456\", null, null, null))\n\t\t\t\t\t.withSeccompProfile(new SeccompProfile(\"my-profiles/profile-allow.json\", \"Localhost\"))\n\t\t\t\t\t.withSupplementalGroups(65534L, 65535L)\n\t\t\t\t\t.withSysctls(new Sysctl(\"kernel.shm_rmid_forced\", \"0\"), new Sysctl(\"net.core.somaxconn\", \"1024\"))\n\t\t\t\t\t.withWindowsOptions(new WindowsSecurityContextOptions(\"specA\", \"specA-name\", true, \"userA\"))\n\t\t\t\t\t.build();\n            assertThatDeployerCreatesPodSpecWithPodSecurityContext(globalDeployerProps, deploymentProps, expectedPodSecurityContext);\n        }\n\n        @Test\n        @DisplayName(\"created from global deployer property\")\n        void createdFromGlobalDeployerProperty() {\n            KubernetesDeployerProperties globalDeployerProps = new KubernetesDeployerProperties();\n            KubernetesDeployerProperties.PodSecurityContext securityContext = new KubernetesDeployerProperties.PodSecurityContext();\n            securityContext.setFsGroup(65534L);\n            securityContext.setRunAsUser(65534L);\n            securityContext.setSupplementalGroups(new Long[]{65534L});\n            KubernetesDeployerProperties.SeccompProfile seccompProfile = new KubernetesDeployerProperties.SeccompProfile();\n            seccompProfile.setType(\"Localhost\");\n            seccompProfile.setLocalhostProfile(\"profile.json\");\n            securityContext.setSeccompProfile(seccompProfile);\n            globalDeployerProps.setPodSecurityContext(securityContext);\n            Map<String, String> deploymentProps = Collections.emptyMap();\n            PodSecurityContext expectedPodSecurityContext = new PodSecurityContextBuilder()\n                    .withRunAsUser(65534L)\n                    .withFsGroup(65534L)\n                    .withSupplementalGroups(65534L)\n                    .withSeccompProfile(new SeccompProfile(\"profile.json\", \"Localhost\"))\n                    .build();\n            assertThatDeployerCreatesPodSpecWithPodSecurityContext(globalDeployerProps, deploymentProps, expectedPodSecurityContext);\n        }\n\n        @Test\n        @DisplayName(\"created from deployment property overrriding global deployer property\")\n        void createdFromDeploymentPropertyOverridingGlobalDeployerProperty() {\n            KubernetesDeployerProperties globalDeployerProps = new KubernetesDeployerProperties();\n            KubernetesDeployerProperties.PodSecurityContext securityContext = new KubernetesDeployerProperties.PodSecurityContext();\n            securityContext.setFsGroup(1000L);\n            securityContext.setRunAsUser(1000L);\n            securityContext.setSupplementalGroups(new Long[]{1000L});\n            KubernetesDeployerProperties.SeccompProfile seccompProfile = new KubernetesDeployerProperties.SeccompProfile();\n            seccompProfile.setType(\"Localhost\");\n            seccompProfile.setLocalhostProfile(\"sec/default-allow.json\");\n            securityContext.setSeccompProfile(seccompProfile);\n            globalDeployerProps.setPodSecurityContext(securityContext);\n            Map<String, String> deploymentProps = new HashMap<>();\n            deploymentProps.put(\"spring.cloud.deployer.kubernetes.podSecurityContext\", \"{runAsUser: 65534, fsGroup: 65534, supplementalGroups: [65534,65535], seccompProfile: { type: Localhost, localhostProfile: sec/custom-allow.json } }\");\n            PodSecurityContext expectedPodSecurityContext = new PodSecurityContextBuilder()\n                    .withRunAsUser(65534L)\n                    .withFsGroup(65534L)\n                    .withSupplementalGroups(65534L, 65535L)\n                    .withSeccompProfile(new SeccompProfile(\"sec/custom-allow.json\", \"Localhost\"))\n                    .build();\n            assertThatDeployerCreatesPodSpecWithPodSecurityContext(globalDeployerProps, deploymentProps, expectedPodSecurityContext);\n        }\n\n        private void assertThatDeployerCreatesPodSpecWithPodSecurityContext(\n                KubernetesDeployerProperties globalDeployerProps,\n                Map<String, String> deploymentProps,\n                PodSecurityContext expectedPodSecurityContext\n        ) {\n            PodSpec podSpec = deployerCreatesPodSpec(globalDeployerProps, deploymentProps);\n            PodSecurityContext actualPodSecurityContext = podSpec.getSecurityContext();\n            assertThat(actualPodSecurityContext)\n                    .isNotNull()\n                    .isEqualTo(expectedPodSecurityContext);\n        }\n    }\n\n    @Nested\n    @DisplayName(\"creates pod spec with container security context\")\n    class CreatePodSpecWithContainerSecurityContext {\n\n        @Test\n        @DisplayName(\"created from deployment property with all fields\")\n        void createdFromDeploymentPropertyWithAllFields() {\n            KubernetesDeployerProperties globalDeployerProps = new KubernetesDeployerProperties();\n            Map<String, String> deploymentProps = new HashMap<>();\n\t\t\tdeploymentProps.put(\"spring.cloud.deployer.kubernetes.containerSecurityContext\", \"{\" +\n\t\t\t\t\t\"  allowPrivilegeEscalation: true\" +\n\t\t\t\t\t\", capabilities: { add: [ \\\"a\\\", \\\"b\\\" ], drop: [ \\\"c\\\" ] }\" +\n\t\t\t\t\t\", privileged: true\" +\n\t\t\t\t\t\", procMount: DefaultProcMount\" +\n\t\t\t\t\t\", readOnlyRootFilesystem: true\" +\n\t\t\t\t\t\", runAsUser: 65534\" +\n\t\t\t\t\t\", runAsGroup: 65534\" +\n\t\t\t\t\t\", runAsNonRoot: true\" +\n\t\t\t\t\t\", seLinuxOptions: { level: \\\"s0:c123,c456\\\" }\" +\n\t\t\t\t\t\", seccompProfile: { type: Localhost, localhostProfile: my-profiles/profile-allow.json }\" +\n\t\t\t\t\t\", windowsOptions: { gmsaCredentialSpec: \\\"specA\\\", gmsaCredentialSpecName: \\\"specA-name\\\", hostProcess: true, runAsUserName: \\\"userA\\\" }\" +\n\t\t\t\t\t\"}\");\n\t\t\tSecurityContext expectedContainerSecurityContext = new SecurityContextBuilder()\n                    .withAllowPrivilegeEscalation(true)\n\t\t\t\t\t.withCapabilities(new Capabilities(Arrays.asList(\"a\", \"b\"), Arrays.asList(\"c\")))\n\t\t\t\t\t.withPrivileged(true)\n\t\t\t\t\t.withProcMount(\"DefaultProcMount\")\n\t\t\t\t\t.withReadOnlyRootFilesystem(true)\n\t\t\t\t\t.withRunAsUser(65534L)\n\t\t\t\t\t.withRunAsGroup(65534L)\n\t\t\t\t\t.withRunAsNonRoot(true)\n\t\t\t\t\t.withSeLinuxOptions(new SELinuxOptions(\"s0:c123,c456\", null, null, null))\n\t\t\t\t\t.withSeccompProfile(new SeccompProfile(\"my-profiles/profile-allow.json\", \"Localhost\"))\n\t\t\t\t\t.withWindowsOptions(new WindowsSecurityContextOptions(\"specA\", \"specA-name\", true, \"userA\"))\n                    .build();\n            assertThatDeployerCreatesPodSpecWithContainerSecurityContext(globalDeployerProps, deploymentProps, expectedContainerSecurityContext);\n        }\n\n        @Test\n        @DisplayName(\"created from deployment property with allowPrivilegeEscalation only\")\n        void createdFromDeploymentPropertyWithAllowPrivilegeEscalationOnly() {\n            KubernetesDeployerProperties globalDeployerProps = new KubernetesDeployerProperties();\n            Map<String, String> deploymentProps = Collections.singletonMap(\"spring.cloud.deployer.kubernetes.containerSecurityContext\", \"{allowPrivilegeEscalation: true}\");\n            SecurityContext expectedContainerSecurityContext = new SecurityContextBuilder()\n                    .withAllowPrivilegeEscalation(true)\n                    .build();\n            assertThatDeployerCreatesPodSpecWithContainerSecurityContext(globalDeployerProps, deploymentProps, expectedContainerSecurityContext);\n        }\n\n        @Test\n        @DisplayName(\"created from deployment property with readOnlyRootFilesystem only\")\n        void createdFromDeploymentPropertyWithReadOnlyRootFilesystemOnly() {\n            KubernetesDeployerProperties globalDeployerProps = new KubernetesDeployerProperties();\n            Map<String, String> deploymentProps = Collections.singletonMap(\"spring.cloud.deployer.kubernetes.containerSecurityContext\", \"{readOnlyRootFilesystem: true}\");\n            SecurityContext expectedContainerSecurityContext = new SecurityContextBuilder()\n                    .withReadOnlyRootFilesystem(true)\n                    .build();\n            assertThatDeployerCreatesPodSpecWithContainerSecurityContext(globalDeployerProps, deploymentProps, expectedContainerSecurityContext);\n        }\n\n        @Test\n        @DisplayName(\"created from global deployer property sourced from yaml\")\n        void createdFromGlobalDeployerPropertySourcedFromYaml() throws Exception {\n            KubernetesDeployerProperties globalDeployerProps = bindDeployerProperties();\n            Map<String, String> deploymentProps = Collections.emptyMap();\n\t\t\tSecurityContext expectedContainerSecurityContext = new SecurityContextBuilder()\n\t\t\t\t\t.withAllowPrivilegeEscalation(true)\n\t\t\t\t\t.withCapabilities(new Capabilities(Arrays.asList(\"a\", \"b\"), Arrays.asList(\"c\")))\n\t\t\t\t\t.withPrivileged(true)\n\t\t\t\t\t.withProcMount(\"DefaultProcMount\")\n\t\t\t\t\t.withReadOnlyRootFilesystem(true)\n\t\t\t\t\t.withRunAsUser(65534L)\n\t\t\t\t\t.withRunAsGroup(65534L)\n\t\t\t\t\t.withRunAsNonRoot(true)\n\t\t\t\t\t.withSeLinuxOptions(new SELinuxOptions(\"s0:c123,c456\", null, null, null))\n\t\t\t\t\t.withSeccompProfile(new SeccompProfile(\"my-profiles/profile-allow.json\", \"Localhost\"))\n\t\t\t\t\t.withWindowsOptions(new WindowsSecurityContextOptions(\"specA\", \"specA-name\", true, \"userA\"))\n\t\t\t\t\t.build();\n            assertThatDeployerCreatesPodSpecWithContainerSecurityContext(globalDeployerProps, deploymentProps, expectedContainerSecurityContext);\n        }\n\n        @Test\n        @DisplayName(\"created from global deployer property\")\n        void createdFromGlobalDeployerProperty() {\n            KubernetesDeployerProperties globalDeployerProps = new KubernetesDeployerProperties();\n            KubernetesDeployerProperties.ContainerSecurityContext securityContext = new KubernetesDeployerProperties.ContainerSecurityContext();\n            securityContext.setAllowPrivilegeEscalation(false);\n            securityContext.setReadOnlyRootFilesystem(true);\n            globalDeployerProps.setContainerSecurityContext(securityContext);\n            Map<String, String> deploymentProps = Collections.emptyMap();\n            SecurityContext expectedContainerSecurityContext = new SecurityContextBuilder()\n                    .withAllowPrivilegeEscalation(false)\n                    .withReadOnlyRootFilesystem(true)\n                    .build();\n            assertThatDeployerCreatesPodSpecWithContainerSecurityContext(globalDeployerProps, deploymentProps, expectedContainerSecurityContext);\n        }\n\n        @Test\n        @DisplayName(\"created from deployment property overrriding global deployer property\")\n        void createdFromDeploymentPropertyOverridingGlobalDeployerProperty() {\n            KubernetesDeployerProperties globalDeployerProps = new KubernetesDeployerProperties();\n            KubernetesDeployerProperties.ContainerSecurityContext securityContext = new KubernetesDeployerProperties.ContainerSecurityContext();\n            securityContext.setAllowPrivilegeEscalation(true);\n            securityContext.setReadOnlyRootFilesystem(false);\n            globalDeployerProps.setContainerSecurityContext(securityContext);\n            Map<String, String> deploymentProps = Collections.singletonMap(\"spring.cloud.deployer.kubernetes.containerSecurityContext\", \"{allowPrivilegeEscalation: false, readOnlyRootFilesystem: true}\");\n            SecurityContext expectedContainerSecurityContext = new SecurityContextBuilder()\n                    .withAllowPrivilegeEscalation(false)\n                    .withReadOnlyRootFilesystem(true)\n                    .build();\n            assertThatDeployerCreatesPodSpecWithContainerSecurityContext(globalDeployerProps, deploymentProps, expectedContainerSecurityContext);\n        }\n\n        private void assertThatDeployerCreatesPodSpecWithContainerSecurityContext(\n                KubernetesDeployerProperties globalDeployerProps,\n                Map<String, String> deploymentProps,\n                SecurityContext expectedContainerSecurityContext\n        ) {\n            PodSpec podSpec = deployerCreatesPodSpec(globalDeployerProps, deploymentProps);\n            assertThat(podSpec.getContainers())\n                    .singleElement()\n                    .extracting(Container::getSecurityContext)\n                    .isEqualTo(expectedContainerSecurityContext);\n        }\n    }\n\n    private PodSpec deployerCreatesPodSpec(KubernetesDeployerProperties globalDeployerProperties, Map<String, String> deploymentProperties) {\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), deploymentProperties);\n        KubernetesAppDeployer deployer = k8sAppDeployer(globalDeployerProperties);\n        return deployer.createPodSpec(appDeploymentRequest);\n    }\n\n    @Test\n    public void testWithLifecyclePostStart() {\n        Map<String, String> props = new HashMap<>();\n        props.put(\"spring.cloud.deployer.kubernetes.lifecycle.postStart.exec.command\",\n                \"/bin/sh,-c,echo Hello from the postStart handler > /usr/share/message\");\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), props);\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n        deployer = k8sAppDeployer(kubernetesDeployerProperties);\n        PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);\n        assertThat(podSpec.getContainers().get(0).getLifecycle().getPostStart().getExec().getCommand())\n                .containsExactlyInAnyOrder(\"/bin/sh\", \"-c\", \"echo Hello from the postStart handler > /usr/share/message\");\n    }\n\n    @Test\n    public void testWithLifecyclePreStop() {\n        Map<String, String> props = new HashMap<>();\n        props.put(\"spring.cloud.deployer.kubernetes.lifecycle.preStop.exec.command\",\n                \"/bin/sh,-c,nginx -s quit; while killall -0 nginx; do sleep 1; done\");\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), props);\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n        deployer = k8sAppDeployer(kubernetesDeployerProperties);\n        PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);\n        assertThat(podSpec.getContainers().get(0).getLifecycle().getPreStop().getExec().getCommand())\n                .containsExactlyInAnyOrder(\n                        \"/bin/sh\", \"-c\", \"nginx -s quit; while killall -0 nginx; do sleep 1; done\");\n    }\n\n    @Test\n    public void testLifecyclePostStartOverridesGlobalPostStart() {\n        Map<String, String> props = new HashMap<>();\n        props.put(\"spring.cloud.deployer.kubernetes.lifecycle.postStart.exec.command\",\n                \"/bin/sh,-c,nginx -s quit; while killall -0 nginx; do sleep 1; done\");\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), props);\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n        KubernetesDeployerProperties.Lifecycle lifecycle = new KubernetesDeployerProperties.Lifecycle();\n        lifecycle.setPostStart(new KubernetesDeployerProperties.Lifecycle.Hook() {\n            @Override\n            KubernetesDeployerProperties.Lifecycle.Exec getExec() {\n                return new KubernetesDeployerProperties.Lifecycle.Exec() {\n                    @Override\n                    List<String> getCommand() {\n                        return Arrays.asList(\"echo\", \"postStart\");\n                    }\n                };\n            }\n        });\n        lifecycle.setPreStop(new KubernetesDeployerProperties.Lifecycle.Hook() {\n            @Override\n            KubernetesDeployerProperties.Lifecycle.Exec getExec() {\n                return new KubernetesDeployerProperties.Lifecycle.Exec() {\n                    @Override\n                    List<String> getCommand() {\n                        return Arrays.asList(\"echo\", \"preStop\");\n                    }\n                };\n            }\n        });\n        kubernetesDeployerProperties.setLifecycle(lifecycle);\n        deployer = k8sAppDeployer(kubernetesDeployerProperties);\n        PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);\n        assertThat(podSpec.getContainers().get(0).getLifecycle().getPostStart().getExec().getCommand())\n                .containsExactlyInAnyOrder(\n                        \"/bin/sh\", \"-c\", \"nginx -s quit; while killall -0 nginx; do sleep 1; done\");\n        assertThat(podSpec.getContainers().get(0).getLifecycle().getPreStop().getExec().getCommand())\n                .containsExactlyInAnyOrder(\"echo\", \"preStop\");\n    }\n\n    @Test\n    public void testLifecyclePrestopOverridesGlobalPrestop() {\n        Map<String, String> props = new HashMap<>();\n        props.put(\"spring.cloud.deployer.kubernetes.lifecycle.preStop.exec.command\",\n                \"/bin/sh,-c,nginx -s quit; while killall -0 nginx; do sleep 1; done\");\n        AppDefinition definition = new AppDefinition(\"app-test\", null);\n        AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), props);\n        KubernetesDeployerProperties kubernetesDeployerProperties = new KubernetesDeployerProperties();\n        KubernetesDeployerProperties.Lifecycle lifecycle = new KubernetesDeployerProperties.Lifecycle();\n        lifecycle.setPostStart(new KubernetesDeployerProperties.Lifecycle.Hook() {\n            @Override\n            KubernetesDeployerProperties.Lifecycle.Exec getExec() {\n                return new KubernetesDeployerProperties.Lifecycle.Exec() {\n                    @Override\n                    List<String> getCommand() {\n                        return Arrays.asList(\"echo\", \"postStart\");\n                    }\n                };\n            }\n        });\n        lifecycle.setPreStop(new KubernetesDeployerProperties.Lifecycle.Hook() {\n            @Override\n            KubernetesDeployerProperties.Lifecycle.Exec getExec() {\n                return new KubernetesDeployerProperties.Lifecycle.Exec() {\n                    @Override\n                    List<String> getCommand() {\n                        return Arrays.asList(\"echo\", \"preStop\");\n                    }\n                };\n            }\n        });\n        kubernetesDeployerProperties.setLifecycle(lifecycle);\n        deployer = k8sAppDeployer(kubernetesDeployerProperties);\n        PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);\n        assertThat(podSpec.getContainers().get(0).getLifecycle().getPreStop().getExec().getCommand())\n                .containsExactlyInAnyOrder(\n                        \"/bin/sh\", \"-c\", \"nginx -s quit; while killall -0 nginx; do sleep 1; done\");\n        assertThat(podSpec.getContainers().get(0).getLifecycle().getPostStart().getExec().getCommand())\n                .containsExactlyInAnyOrder(\"echo\", \"postStart\");\n    }\n\n    private Resource getResource() {\n        return new DockerResource(\"springcloud/spring-cloud-deployer-spi-test-app:latest\");\n    }\n\n    private KubernetesDeployerProperties bindDeployerProperties() throws Exception {\n        YamlPropertiesFactoryBean properties = new YamlPropertiesFactoryBean();\n        properties.setResources(new ClassPathResource(\"dataflow-server.yml\"),\n                new ClassPathResource(\"dataflow-server-tolerations.yml\"),\n                new ClassPathResource(\"dataflow-server-secretKeyRef.yml\"),\n                new ClassPathResource(\"dataflow-server-configMapKeyRef.yml\"),\n                new ClassPathResource(\"dataflow-server-podsecuritycontext.yml\"),\n                new ClassPathResource(\"dataflow-server-containerSecurityContext.yml\"),\n                new ClassPathResource(\"dataflow-server-nodeAffinity.yml\"),\n                new ClassPathResource(\"dataflow-server-podAffinity.yml\"),\n                new ClassPathResource(\"dataflow-server-podAntiAffinity.yml\"));\n        Properties yaml = properties.getObject();\n        MapConfigurationPropertySource source = new MapConfigurationPropertySource(yaml);\n        return new Binder(source).bind(\"\", Bindable.of(KubernetesDeployerProperties.class)).get();\n    }\n\n    protected KubernetesAppDeployer k8sAppDeployer() throws Exception {\n        return k8sAppDeployer(bindDeployerProperties());\n    }\n\n    protected KubernetesAppDeployer k8sAppDeployer(KubernetesDeployerProperties kubernetesDeployerProperties) {\n        return new KubernetesAppDeployer(kubernetesDeployerProperties, null);\n    }\n\n}\n"
  },
  {
    "path": "src/test/java/org/springframework/cloud/deployer/spi/kubernetes/KubernetesTaskLauncherIntegrationIT.java",
    "content": "/*\n * Copyright 2016-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.kubernetes;\n\nimport java.time.Duration;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.UUID;\n\nimport io.fabric8.kubernetes.api.model.Container;\nimport io.fabric8.kubernetes.api.model.Pod;\nimport io.fabric8.kubernetes.client.KubernetesClient;\nimport org.junit.jupiter.api.Disabled;\nimport org.junit.jupiter.api.Test;\n\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.boot.test.context.SpringBootTest;\nimport org.springframework.cloud.deployer.resource.docker.DockerResource;\nimport org.springframework.cloud.deployer.spi.core.AppDefinition;\nimport org.springframework.cloud.deployer.spi.core.AppDeploymentRequest;\nimport org.springframework.cloud.deployer.spi.task.LaunchState;\nimport org.springframework.cloud.deployer.spi.task.TaskLauncher;\nimport org.springframework.cloud.deployer.spi.test.AbstractTaskLauncherIntegrationJUnit5Tests;\nimport org.springframework.cloud.deployer.spi.test.Timeout;\nimport org.springframework.core.io.Resource;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.entry;\nimport static org.awaitility.Awaitility.await;\n\n/**\n * Integration tests for {@link KubernetesTaskLauncher}.\n *\n * @author Thomas Risberg\n * @author Chris Schaefer\n */\n@SpringBootTest(classes = {KubernetesAutoConfiguration.class}, properties = {\n\t\t\"spring.cloud.deployer.kubernetes.namespace=default\"\n})\npublic class KubernetesTaskLauncherIntegrationIT extends AbstractTaskLauncherIntegrationJUnit5Tests {\n\n\t@Autowired\n\tprivate TaskLauncher taskLauncher;\n\n\t@Autowired\n\tprivate KubernetesClient kubernetesClient;\n\n\t@Override\n\tprotected TaskLauncher provideTaskLauncher() {\n\t\treturn taskLauncher;\n\t}\n\n\t@Test\n\t@Override\n\tpublic void testSimpleCancel() throws InterruptedException {\n\t\tsuper.testSimpleCancel();\n\t}\n\n\t@Override\n\tprotected String randomName() {\n\t\treturn \"task-\" + UUID.randomUUID().toString().substring(0, 18);\n\t}\n\n\t@Override\n\tprotected Resource testApplication() {\n\t\treturn new DockerResource(\"springcloud/spring-cloud-deployer-spi-test-app:latest\");\n\t}\n\n\t@Override\n\tprotected Timeout deploymentTimeout() {\n\t\treturn new Timeout(20, 5000);\n\t}\n\n\t@Test\n\tpublic void testJobPodAnnotation() {\n\t\tlogger.info(\"Testing {}...\", \"JobPodAnnotation\");\n\n\t\tKubernetesTaskLauncher kubernetesTaskLauncher = new KubernetesTaskLauncher(new KubernetesDeployerProperties(),\n\t\t\t\tnew KubernetesTaskLauncherProperties(), kubernetesClient);\n\n\t\tAppDefinition definition = new AppDefinition(randomName(), null);\n\t\tResource resource = testApplication();\n\t\tAppDeploymentRequest request = new AppDeploymentRequest(definition, resource,\n\t\t\t\tCollections.singletonMap(\"spring.cloud.deployer.kubernetes.jobAnnotations\", \"key1:val1,key2:val2,key3:val31:val32\"));\n\n\t\tlogger.info(\"Launching {}...\", request.getDefinition().getName());\n\n\t\tString launchId = kubernetesTaskLauncher.launch(request);\n\t\tTimeout timeout = deploymentTimeout();\n\n\t\tawait().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> {\n\t\t\tassertThat(taskLauncher().status(launchId).getState()).isEqualTo(LaunchState.running);\n        });\n\n\t\tString taskName = request.getDefinition().getName();\n\n\t\tlogger.info(\"Checking job pod spec annotations of {}...\", taskName);\n\n\t\tList<Pod> pods = kubernetesClient.pods().withLabel(\"task-name\", taskName).list().getItems();\n\n\t\tassertThat(pods).hasSize(1);\n\n\t\tPod pod = pods.get(0);\n\n\t\tassertThat(pod.getSpec().getContainers().get(0).getPorts()).isEmpty();\n\n\t\tMap<String, String> annotations = pod.getMetadata().getAnnotations();\n\n\t\tassertThat(annotations).contains(entry(\"key1\", \"val1\"), entry(\"key2\", \"val2\"), entry(\"key3\", \"val31:val32\"));\n\n\t\tlogger.info(\"Destroying {}...\", taskName);\n\n\t\ttimeout = undeploymentTimeout();\n\t\tkubernetesTaskLauncher.destroy(taskName);\n\n\t\tawait().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> {\n\t\t\tassertThat(taskLauncher().status(launchId).getState()).isEqualTo(LaunchState.unknown);\n        });\n\t}\n\n\t@Test\n\tpublic void testDeploymentLabels() {\n\t\tlogger.info(\"Testing {}...\", \"deploymentLabels\");\n\n\t\tKubernetesTaskLauncher kubernetesTaskLauncher = new KubernetesTaskLauncher(new KubernetesDeployerProperties(),\n\t\t\t\tnew KubernetesTaskLauncherProperties(), kubernetesClient);\n\n\t\tAppDefinition definition = new AppDefinition(randomName(), null);\n\t\tResource resource = testApplication();\n\t\tAppDeploymentRequest request = new AppDeploymentRequest(definition, resource,\n\t\t\t\tCollections.singletonMap(\"spring.cloud.deployer.kubernetes.deploymentLabels\", \"label1:value1,label2:value2\"));\n\n\t\tlogger.info(\"Launching {}...\", request.getDefinition().getName());\n\n\t\tString launchId = kubernetesTaskLauncher.launch(request);\n\t\tTimeout timeout = deploymentTimeout();\n\n\t\tawait().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> {\n\t\t\tassertThat(taskLauncher().status(launchId).getState()).isEqualTo(LaunchState.running);\n        });\n\n\t\tString taskName = request.getDefinition().getName();\n\n\t\tlogger.info(\"Checking job pod spec labels of {}...\", taskName);\n\n\t\tList<Pod> pods = kubernetesClient.pods().withLabel(\"task-name\", taskName).list().getItems();\n\n\t\tassertThat(pods).hasSize(1);\n\n\t\tPod pod = pods.get(0);\n\n\t\tassertThat(pod.getSpec().getContainers().get(0).getPorts()).isEmpty();\n\n\t\tMap<String, String> labels = pod.getMetadata().getLabels();\n\n\t\tassertThat(labels).contains(entry(\"label1\", \"value1\"), entry(\"label2\", \"value2\"));\n\n\t\tlogger.info(\"Destroying {}...\", taskName);\n\n\t\ttimeout = undeploymentTimeout();\n\t\tkubernetesTaskLauncher.destroy(taskName);\n\n\t\tawait().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> {\n\t\t\tassertThat(taskLauncher().status(launchId).getState()).isEqualTo(LaunchState.unknown);\n        });\n\t}\n\n\t@Test\n\tpublic void testTaskAdditionalContainer() {\n\t\tlogger.info(\"Testing {}...\", \"TaskAdditionalContainer\");\n\n\t\tKubernetesTaskLauncher kubernetesTaskLauncher = new KubernetesTaskLauncher(new KubernetesDeployerProperties(),\n\t\t\t\tnew KubernetesTaskLauncherProperties(), kubernetesClient);\n\n\t\tAppDefinition definition = new AppDefinition(randomName(), null);\n\t\tResource resource = testApplication();\n\t\tMap<String, String> props = Collections.singletonMap(\"spring.cloud.deployer.kubernetes.additionalContainers\",\n\t\t\t\t\"[{name: 'test', image: 'busybox:latest', command: ['sh', '-c', 'echo hello']}]\");\n\t\tAppDeploymentRequest request = new AppDeploymentRequest(definition, resource, props);\n\n\t\tlogger.info(\"Launching {}...\", request.getDefinition().getName());\n\n\t\tString launchId = kubernetesTaskLauncher.launch(request);\n\t\tTimeout timeout = deploymentTimeout();\n\n\t\tawait().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> {\n\t\t\tassertThat(taskLauncher().status(launchId).getState()).isEqualTo(LaunchState.running);\n        });\n\n\t\tString taskName = request.getDefinition().getName();\n\n\t\tList<Pod> pods = kubernetesClient.pods().withLabel(\"task-name\", taskName).list().getItems();\n\n\t\tassertThat(pods).hasSize(1);\n\n\t\tPod pod = pods.get(0);\n\n\t\tassertThat(pod.getSpec().getContainers()).hasSize(2);\n\n\t\tOptional<Container> additionalContainer = pod.getSpec().getContainers().stream().filter(i -> i.getName().equals(\"test\")).findFirst();\n\t\tassertThat(additionalContainer.isPresent()).as(\"Additional container not found\").isTrue();\n\n\t\tContainer testAdditionalContainer = additionalContainer.get();\n\n\t\tassertThat(testAdditionalContainer.getName()).as(\"Unexpected additional container name\").isEqualTo(\"test\");\n\t\tassertThat(testAdditionalContainer.getImage()).as(\"Unexpected additional container image\").isEqualTo(\"busybox:latest\");\n\n\t\tList<String> commands = testAdditionalContainer.getCommand();\n\n\t\tassertThat(commands).contains(\"sh\", \"-c\", \"echo hello\");\n\n\t\tlogger.info(\"Destroying {}...\", taskName);\n\n\t\ttimeout = undeploymentTimeout();\n\t\tkubernetesTaskLauncher.destroy(taskName);\n\n\t\tawait().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> {\n\t\t\tassertThat(taskLauncher().status(launchId).getState()).isEqualTo(LaunchState.unknown);\n        });\n\t}\n}\n"
  },
  {
    "path": "src/test/java/org/springframework/cloud/deployer/spi/kubernetes/KubernetesTaskLauncherWithJobIntegrationIT.java",
    "content": "/*\n * Copyright 2016-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.kubernetes;\n\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.function.Consumer;\n\nimport io.fabric8.kubernetes.api.model.Pod;\nimport io.fabric8.kubernetes.api.model.batch.v1.Job;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.TestInfo;\nimport org.junit.jupiter.api.extension.ExtendWith;\n\nimport org.springframework.boot.test.context.SpringBootTest;\nimport org.springframework.boot.test.system.CapturedOutput;\nimport org.springframework.boot.test.system.OutputCaptureExtension;\nimport org.springframework.cloud.deployer.spi.core.AppDefinition;\nimport org.springframework.cloud.deployer.spi.core.AppDeploymentRequest;\nimport org.springframework.cloud.deployer.spi.task.LaunchState;\nimport org.springframework.core.io.Resource;\nimport org.springframework.test.context.TestPropertySource;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.assertThatThrownBy;\nimport static org.assertj.core.api.Assertions.entry;\n\n/**\n * Integration tests for {@link KubernetesTaskLauncher} using jobs instead of bare pods.\n *\n * <p>NOTE: The tests do not call {@code TaskLauncher.destroy/cleanup} in a finally block but instead rely on the\n * {@link AbstractKubernetesTaskLauncherIntegrationTests#cleanupLingeringApps() AfterEach method} to clean any stray apps.\n *\n * @author Leonardo Diniz\n * @author Chris Schaefer\n * @author Ilayaperumal Gopinathan\n * @author Chris Bono\n * @author Glenn Renfro\n */\n@SpringBootTest(classes = {KubernetesAutoConfiguration.class})\n@TestPropertySource(properties = \"spring.cloud.deployer.kubernetes.create-job=true\")\n@ExtendWith(OutputCaptureExtension.class)\npublic class KubernetesTaskLauncherWithJobIntegrationIT extends AbstractKubernetesTaskLauncherIntegrationTests {\n\n\t@BeforeEach\n\tpublic void setup() {\n\t\tif (kubernetesClient.getNamespace() == null) {\n\t\t\tkubernetesClient.getConfiguration().setNamespace(\"default\");\n\t\t}\n\t}\n\n\t@Test\n\tvoid taskLaunchedWithJobAnnotations(TestInfo testInfo) {\n\t\tlogTestInfo(testInfo);\n\t\tlaunchTaskJobAndValidateCreatedJobAndPodWithCleanup(\n\t\t\t\tCollections.singletonMap(\"spring.cloud.deployer.kubernetes.jobAnnotations\", \"key1:val1,key2:val2,key3:val31:val32\"),\n\t\t\t\t(job) -> assertThat(job.getMetadata().getAnnotations()).isNotEmpty()\n\t\t\t\t\t\t.contains(entry(\"key1\", \"val1\"), entry(\"key2\", \"val2\"), entry(\"key3\", \"val31:val32\")),\n\t\t\t\t(pod) -> assertThat(pod.getMetadata().getAnnotations()).isNotEmpty()\n\t\t\t\t\t\t.contains(entry(\"key1\", \"val1\"), entry(\"key2\", \"val2\"), entry(\"key3\", \"val31:val32\")));\n\t}\n\n\t@Test\n\tvoid taskLaunchedWithJobSpecProperties(TestInfo testInfo) {\n\t\tlogTestInfo(testInfo);\n\t\tMap<String, String> deploymentProps = new HashMap<>();\n\t\tdeploymentProps.put(\"spring.cloud.deployer.kubernetes.restartPolicy\", \"OnFailure\");\n\t\tdeploymentProps.put(\"spring.cloud.deployer.kubernetes.backoffLimit\", \"5\");\n\t\tlaunchTaskJobAndValidateCreatedJobAndPodWithCleanup(\n\t\t\t\tdeploymentProps,\n\t\t\t\t(job) -> assertThat(job.getSpec().getBackoffLimit()).isEqualTo(5),\n\t\t\t\t(pod) -> {});\n\t}\n\n\tprivate void launchTaskJobAndValidateCreatedJobAndPodWithCleanup(Map<String, String> deploymentProps,\n\t\t\tConsumer<Job> assertingJobConsumer, Consumer<Pod> assertingPodConsumer) {\n\t\tString taskName = randomName();\n\t\tAppDefinition definition = new AppDefinition(taskName, null);\n\t\tResource resource = testApplication();\n\t\tAppDeploymentRequest request = new AppDeploymentRequest(definition, resource, deploymentProps);\n\n\t\tlogger.info(\"Launching {}...\", taskName);\n\t\tString launchId = taskLauncher().launch(request);\n\t\tawaitWithPollAndTimeout(deploymentTimeout())\n\t\t\t\t.untilAsserted(() -> assertThat(taskLauncher().status(launchId).getState()).isEqualTo(LaunchState.launching));\n\n\t\tlogger.info(\"Checking task Job for {}...\", taskName);\n\t\tList<Job> jobs = getJobsForTask(taskName);\n\t\tassertThat(jobs).hasSize(1);\n\t\tassertThat(jobs).singleElement().satisfies(assertingJobConsumer);\n\n\t\tlogger.info(\"Checking task Pod for {}...\", taskName);\n\t\tList<Pod> pods = getPodsForTask(taskName);\n\t\tassertThat(pods).hasSize(1);\n\t\tassertThat(pods).singleElement().satisfies(assertingPodConsumer);\n\n\t\tlogger.info(\"Destroying {}...\", taskName);\n\t\ttaskLauncher().destroy(taskName);\n\t\tawaitWithPollAndTimeout(undeploymentTimeout())\n\t\t\t\t.untilAsserted(() -> assertThat(taskLauncher().status(launchId).getState()).isEqualTo(LaunchState.unknown));\n\t}\n\n\t@Test\n\tvoid taskLaunchedWithInvalidRestartPolicyThrowsException(TestInfo testInfo) {\n\t\tlogTestInfo(testInfo);\n\t\tAppDefinition definition = new AppDefinition(randomName(), null);\n\t\tResource resource = testApplication();\n\t\tMap<String, String> deploymentProps = new HashMap<>();\n\t\tdeploymentProps.put(\"spring.cloud.deployer.kubernetes.restartPolicy\", \"Always\");\n\t\tdeploymentProps.put(\"spring.cloud.deployer.kubernetes.backoffLimit\", \"5\");\n\t\tAppDeploymentRequest request = new AppDeploymentRequest(definition, resource, deploymentProps);\n\n\t\tlogger.info(\"Launching {}...\", request.getDefinition().getName());\n\t\tassertThatThrownBy(() -> taskLauncher.launch(request))\n\t\t\t\t.isInstanceOf(Exception.class)\n\t\t\t\t.hasMessage(\"RestartPolicy should not be 'Always' when the JobSpec is used.\");\n\t}\n\n\t@Test\n\tvoid cleanupDeletesTaskJob(TestInfo testInfo) {\n\t\tlogTestInfo(testInfo);\n\t\tAppDefinition definition = new AppDefinition(randomName(), null);\n\t\tResource resource = testApplication();\n\t\tAppDeploymentRequest request = new AppDeploymentRequest(definition, resource, null);\n\t\tString taskName = request.getDefinition().getName();\n\n\t\tlogger.info(\"Launching {}...\", taskName);\n\t\tString launchId = taskLauncher().launch(request);\n\t\tawaitWithPollAndTimeout(deploymentTimeout())\n\t\t\t\t.untilAsserted(() -> assertThat(taskLauncher().status(launchId).getState()).isEqualTo(LaunchState.launching));\n\n\t\tList<Job> jobs = getJobsForTask(taskName);\n\t\tassertThat(jobs).hasSize(1);\n\n\t\tlogger.info(\"Cleaning up {}...\", taskName);\n\t\ttaskLauncher().cleanup(launchId);\n\t\tawaitWithPollAndTimeout(undeploymentTimeout())\n\t\t\t\t.untilAsserted(() -> assertThat(taskLauncher().status(launchId).getState()).isEqualTo(LaunchState.unknown));\n\n\t\tjobs = getJobsForTask(taskName);\n\t\tassertThat(jobs).isEmpty();\n\t}\n\n\t@Test\n\tvoid cleanupForNonExistentTaskThrowsException(TestInfo testInfo, CapturedOutput taskOutput) {\n\t\tlogTestInfo(testInfo);\n\t\ttaskLauncher().cleanup(\"foo\");\n\t\tassertThat(taskOutput.getAll()).contains(\"Cannot delete job for task \\\"foo\\\" (reason: job does not exist)\");\n\t}\n\n\t@Test\n\tvoid deleteJobAfterTtlSecondsOnAfterFinishedExpire(TestInfo testInfo) {\n\t\tlogTestInfo(testInfo);\n\n\t\tMap<String, String> applicationProps = new HashMap<>();\n\t\tapplicationProps.put(\"killDelay\", \"1\");\n\t\tAppDefinition definition = new AppDefinition(randomName(), applicationProps);\n\n\t\tResource resource = testApplication();\n\n\t\tMap<String, String> deploymentProps = new HashMap<>();\n\t\tdeploymentProps.put(\"spring.cloud.deployer.kubernetes.restartPolicy\", \"Never\");\n\t\tdeploymentProps.put(\"spring.cloud.deployer.kubernetes.backoffLimit\", \"0\");\n\t\tdeploymentProps.put(\"spring.cloud.deployer.kubernetes.ttlSecondsAfterFinished\", \"3\");\n\t\tAppDeploymentRequest request = new AppDeploymentRequest(definition, resource, deploymentProps);\n\t\tString taskName = request.getDefinition().getName();\n\n\t\tlogger.info(\"Launching {}...\", taskName);\n\t\tString launchId = taskLauncher().launch(request);\n\t\tawaitWithPollAndTimeout(deploymentTimeout())\n\t\t\t\t.untilAsserted(() -> assertThat(taskLauncher().status(launchId).getState()).isEqualTo(LaunchState.launching));\n\n\t\tList<Job> jobs = getJobsForTask(taskName);\n\t\tassertThat(jobs).hasSize(1);\n\n\t\tlogger.info(\"Waiting for deleting the job {}...\", taskName);\n\n\t\tawaitWithPollAndTimeout(undeploymentTimeout())\n\t\t\t\t.untilAsserted(() -> assertThat(taskLauncher().status(launchId).getState()).isEqualTo(LaunchState.unknown));\n\n\t\tjobs = getJobsForTask(taskName);\n\t\tassertThat(jobs).isEmpty();\n\t}\n}\n"
  },
  {
    "path": "src/test/java/org/springframework/cloud/deployer/spi/local/DebugAddressTests.java",
    "content": "/*\n * Copyright 2020-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.local;\n\nimport java.util.Optional;\n\nimport org.junit.jupiter.api.Test;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * @author Christian Tzolov\n */\npublic class DebugAddressTests {\n\n\t@Test\n\tpublic void testDebugEmptyConfiguration() {\n\t\tOptional<DebugAddress> debugAddress =\n\t\t\t\tDebugAddress.from(new LocalDeployerProperties(), 0);\n\t\tassertThat(debugAddress.isPresent()).isFalse();\n\t}\n\n\t@Test\n\tpublic void testDebugPort() {\n\t\tLocalDeployerProperties properties = new LocalDeployerProperties();\n\t\tproperties.setDebugPort(20075);\n\t\tOptional<DebugAddress> debugAddress =\n\t\t\t\tDebugAddress.from(properties, 0);\n\t\tassertThat(debugAddress.isPresent()).isTrue();\n\t\tassertThat(debugAddress.get().getHost()).isNull();\n\t\tassertThat(debugAddress.get().getPort()).isEqualTo(\"20075\");\n\t\tassertThat(debugAddress.get().getAddress()).isEqualTo(\"20075\");\n\t}\n\n\t@Test\n\tpublic void testDebugPortWithInstance() {\n\t\tLocalDeployerProperties properties = new LocalDeployerProperties();\n\t\tproperties.setDebugPort(20075);\n\t\tOptional<DebugAddress> debugAddress =\n\t\t\t\tDebugAddress.from(properties, 100);\n\t\tassertThat(debugAddress.isPresent()).isTrue();\n\t\tassertThat(debugAddress.get().getHost()).isNull();\n\t\tassertThat(debugAddress.get().getPort()).isEqualTo(\"20175\");\n\t\tassertThat(debugAddress.get().getAddress()).isEqualTo(\"20175\");\n\t}\n\n\t@Test\n\tpublic void testDebugPortInvalidValue() {\n\t\tLocalDeployerProperties properties = new LocalDeployerProperties();\n\t\tproperties.setDebugPort(-666);\n\t\tOptional<DebugAddress> debugAddress =\n\t\t\t\tDebugAddress.from(properties, 0);\n\t\tassertThat(debugAddress.isPresent()).isFalse();\n\t}\n\n\t@Test\n\tpublic void testDebugAddressPortOnly() {\n\t\tLocalDeployerProperties properties = new LocalDeployerProperties();\n\t\tproperties.setDebugAddress(\"20075\");\n\t\tOptional<DebugAddress> debugAddress =\n\t\t\t\tDebugAddress.from(properties, 100);\n\t\tassertThat(debugAddress.isPresent()).isTrue();\n\t\tassertThat(debugAddress.get().getHost()).isNull();\n\t\tassertThat(debugAddress.get().getPort()).isEqualTo(\"20175\");\n\t\tassertThat(debugAddress.get().getAddress()).isEqualTo(\"20175\");\n\t}\n\n\t@Test\n\tpublic void testDebugAddressWildcardHost() {\n\t\tLocalDeployerProperties properties = new LocalDeployerProperties();\n\t\tproperties.setDebugAddress(\"*:20075\");\n\t\tOptional<DebugAddress> debugAddress =\n\t\t\t\tDebugAddress.from(properties, 100);\n\t\tassertThat(debugAddress.isPresent()).isTrue();\n\t\tassertThat(debugAddress.get().getHost()).isEqualTo(\"*\");\n\t\tassertThat(debugAddress.get().getPort()).isEqualTo(\"20175\");\n\t\tassertThat(debugAddress.get().getAddress()).isEqualTo(\"*:20175\");\n\t}\n\n\n\t@Test\n\tpublic void testDebugAddressWithIP() {\n\t\tLocalDeployerProperties properties = new LocalDeployerProperties();\n\t\tproperties.setDebugAddress(\"127.0.0.1:20075\");\n\t\tOptional<DebugAddress> debugAddress =\n\t\t\t\tDebugAddress.from(properties, 100);\n\t\tassertThat(debugAddress.isPresent()).isTrue();\n\t\tassertThat(debugAddress.get().getHost()).isEqualTo(\"127.0.0.1\");\n\t\tassertThat(debugAddress.get().getPort()).isEqualTo(\"20175\");\n\t\tassertThat(debugAddress.get().getAddress()).isEqualTo(\"127.0.0.1:20175\");\n\t}\n\n\t@Test\n\tpublic void testDebugAddressWithHostname() {\n\t\tLocalDeployerProperties properties = new LocalDeployerProperties();\n\t\tproperties.setDebugAddress(\"localhost:20075\");\n\t\tOptional<DebugAddress> debugAddress =\n\t\t\t\tDebugAddress.from(properties, 100);\n\t\tassertThat(debugAddress.isPresent()).isTrue();\n\t\tassertThat(debugAddress.get().getHost()).isEqualTo(\"localhost\");\n\t\tassertThat(debugAddress.get().getPort()).isEqualTo(\"20175\");\n\t\tassertThat(debugAddress.get().getAddress()).isEqualTo(\"localhost:20175\");\n\t}\n\n\t@Test\n\tpublic void testDebugAddressWithInvalidIP() {\n\t\tLocalDeployerProperties properties = new LocalDeployerProperties();\n\t\tproperties.setDebugAddress(\"127.0.:20075\");\n\t\tOptional<DebugAddress> debugAddress =\n\t\t\t\tDebugAddress.from(properties, 100);\n\t\tassertThat(debugAddress.isPresent()).isFalse();\n\t}\n\n}\n"
  },
  {
    "path": "src/test/java/org/springframework/cloud/deployer/spi/local/DeployerSocketUtilsTests.java",
    "content": "/*\n * Copyright 2016-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.local;\n\nimport java.util.SortedSet;\n\nimport org.junit.jupiter.api.Test;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;\n\n/**\n * Unit tests for {@link DeployerSocketUtils}.\n *\n * @author Sam Brannen\n * @author Gary Russell\n * @author Glenn Renfro\n */\nclass DeployerSocketUtilsTests {\n\n\t@Test\n\tvoid findAvailableTcpPortWithZeroMinPort() {\n\t\tassertThatIllegalArgumentException().isThrownBy(() -> DeployerSocketUtils.findAvailableTcpPort(0));\n\t}\n\n\t@Test\n\tvoid findAvailableTcpPortWithNegativeMinPort() {\n\t\tassertThatIllegalArgumentException().isThrownBy(() -> DeployerSocketUtils.findAvailableTcpPort(-500));\n\t}\n\n\t@Test\n\tvoid findAvailableTcpPortWithMin() {\n\t\tint port = DeployerSocketUtils.findAvailableTcpPort(50000);\n\t\tassertPortInRange(port, 50000, DeployerSocketUtils.PORT_RANGE_MAX);\n\t}\n\n\t@Test\n\tvoid find4AvailableTcpPortsInRange() {\n\t\tfindAvailableTcpPorts(4, 30000, 35000);\n\t}\n\n\t@Test\n\tvoid find50AvailableTcpPortsInRange() {\n\t\tfindAvailableTcpPorts(50, 40000, 45000);\n\t}\n\n\t@Test\n\tvoid findAvailableTcpPortsWithRequestedNumberGreaterThanSizeOfRange() {\n\t\tassertThatIllegalArgumentException().isThrownBy(() -> findAvailableTcpPorts(50, 45000, 45010));\n\t}\n\n\t// Helpers\n\n\tprivate void findAvailableTcpPorts(int numRequested, int minPort, int maxPort) {\n\t\tSortedSet<Integer> ports = DeployerSocketUtils.findAvailableTcpPorts(numRequested, minPort, maxPort);\n\t\tassertAvailablePorts(ports, numRequested, minPort, maxPort);\n\t}\n\n\tprivate void assertPortInRange(int port, int minPort, int maxPort) {\n\t\tassertThat(port >= minPort).as(\"port [\" + port + \"] >= \" + minPort).isTrue();\n\t\tassertThat(port <= maxPort).as(\"port [\" + port + \"] <= \" + maxPort).isTrue();\n\t}\n\n\tprivate void assertAvailablePorts(SortedSet<Integer> ports, int numRequested, int minPort, int maxPort) {\n\t\tassertThat(ports.size()).as(\"number of ports requested\").isEqualTo(numRequested);\n\t\tfor (int port : ports) {\n\t\t\tassertPortInRange(port, minPort, maxPort);\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "src/test/java/org/springframework/cloud/deployer/spi/local/DockerCommandBuilderTests.java",
    "content": "/*\n * Copyright 2017-2020 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.local;\n\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.Optional;\n\nimport org.junit.jupiter.api.Test;\n\nimport org.springframework.cloud.deployer.resource.docker.DockerResource;\nimport org.springframework.cloud.deployer.spi.core.AppDefinition;\nimport org.springframework.cloud.deployer.spi.core.AppDeploymentRequest;\nimport org.springframework.core.io.Resource;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * Unit tests for {@link DockerCommandBuilder}.\n *\n * @author Eric Bottard\n * @author Christian Tzolov\n */\npublic class DockerCommandBuilderTests {\n\n\t@Test\n\tpublic void testContainerName() {\n\t\tAppDefinition appDefinition = new AppDefinition(\"foo\", null);\n\t\tResource resource = new DockerResource(\"foo/bar\");\n\t\tMap<String, String> deploymentProperties = Collections.singletonMap(DockerCommandBuilder.DOCKER_CONTAINER_NAME_KEY, \"gogo\");\n\t\tAppDeploymentRequest request = new AppDeploymentRequest(appDefinition, resource, deploymentProperties);\n\n\t\tProcessBuilder builder = new DockerCommandBuilder(null)\n\t\t\t\t.buildExecutionCommand(request, new HashMap<>(), \"deployerId\", Optional.of(1),\n\t\t\t\t\t\tnew LocalDeployerProperties(), Optional.empty());\n\t\tassertThat(builder.command()).containsAnyElementsOf(Arrays.asList(\"docker\", \"run\", \"--rm\", \"--name=gogo-1\",\n\t\t\t\t\"foo/bar\", \"deployerId=deployerId\"));\n\t}\n\n\t@Test\n\tpublic void testContainerNameWithDockerNetwork() {\n\t\tAppDefinition appDefinition = new AppDefinition(\"foo\", null);\n\t\tResource resource = new DockerResource(\"foo/bar\");\n\t\tMap<String, String> deploymentProperties = Collections.singletonMap(DockerCommandBuilder.DOCKER_CONTAINER_NAME_KEY, \"gogo\");\n\t\tAppDeploymentRequest request = new AppDeploymentRequest(appDefinition, resource, deploymentProperties);\n\t\tProcessBuilder builder = new DockerCommandBuilder(\"scdf_default\")\n\t\t\t\t.buildExecutionCommand(request, new HashMap<>(), \"deployerId\", Optional.of(1),\n\t\t\t\t\t\tnew LocalDeployerProperties(), Optional.empty());\n\t\tassertThat(builder.command()).containsAnyElementsOf(Arrays.asList(\"docker\", \"run\", \"--network\", \"scdf_default\",\n\t\t\t\t\"--rm\", \"--name=gogo-1\", \"foo/bar\"));\n\t}\n\n\t@Test\n\tpublic void testContainerNameWithDockerNetworkAndKeepContainers() {\n\t\tAppDefinition appDefinition = new AppDefinition(\"foo\", null);\n\t\tResource resource = new DockerResource(\"foo/bar\");\n\t\tMap<String, String> deploymentProperties = Collections.singletonMap(DockerCommandBuilder.DOCKER_CONTAINER_NAME_KEY, \"gogo\");\n\t\tAppDeploymentRequest request = new AppDeploymentRequest(appDefinition, resource, deploymentProperties);\n\n\t\tLocalDeployerProperties localDeployerProperties = new LocalDeployerProperties();\n\t\tlocalDeployerProperties.getDocker().setDeleteContainerOnExit(false);\n\n\t\tProcessBuilder builder = new DockerCommandBuilder(\"scdf_default\")\n\t\t\t\t.buildExecutionCommand(request, new HashMap<>(), \"deployerId\", Optional.of(1),\n\t\t\t\t\t\tlocalDeployerProperties, Optional.empty());\n\t\tassertThat(builder.command()).containsAnyElementsOf(Arrays.asList(\"docker\", \"run\", \"--network\", \"scdf_default\",\n\t\t\t\t\"--name=gogo-1\", \"foo/bar\"));\n\t\tassertThat(builder.command()).doesNotContain(\"--rm\");\n\t}\n\n\t@Test\n\tpublic void testUseLocalDeployerPropertiesToKeepStoppedContainer() {\n\t\tAppDefinition appDefinition = new AppDefinition(\"foo\", null);\n\t\tResource resource = new DockerResource(\"foo/bar\");\n\t\tMap<String, String> deploymentProperties = Collections.singletonMap(DockerCommandBuilder.DOCKER_CONTAINER_NAME_KEY, \"gogo\");\n\t\tAppDeploymentRequest request = new AppDeploymentRequest(appDefinition, resource, deploymentProperties);\n\n\t\tLocalDeployerProperties localDeployerProperties = new LocalDeployerProperties();\n\t\tlocalDeployerProperties.getDocker().setDeleteContainerOnExit(false);\n\n\t\tProcessBuilder builder = new DockerCommandBuilder(\"scdf_default\")\n\t\t\t\t.buildExecutionCommand(request, new HashMap<>(), \"deployerId\", Optional.of(1),\n\t\t\t\t\t\tlocalDeployerProperties, Optional.empty());\n\t\tassertThat(builder.command()).containsAnyElementsOf(Arrays.asList(\"docker\", \"run\", \"--network\", \"scdf_default\",\n\t\t\t\t\"--name=gogo-1\", \"foo/bar\"));\n\t\tassertThat(builder.command()).doesNotContain(\"--rm\");\n\t}\n\n\t@Test\n\tpublic void testSpringApplicationJSON() {\n\t\tLocalDeployerProperties properties = new LocalDeployerProperties();\n\t\tLocalAppDeployer deployer = new LocalAppDeployer(properties);\n\t\tAppDefinition definition = new AppDefinition(\"foo\", Collections.singletonMap(\"foo\", \"bar\"));\n\t\tResource resource = new DockerResource(\"foo/bar\");\n\t\tMap<String, String> deploymentProperties = new HashMap<>();\n\t\tdeploymentProperties.put(LocalDeployerProperties.DEBUG_ADDRESS, \"*:9999\");\n\t\tdeploymentProperties.put(LocalDeployerProperties.DEBUG_SUSPEND, \"y\");\n\t\tdeploymentProperties.put(LocalDeployerProperties.INHERIT_LOGGING, \"true\");\n\t\tAppDeploymentRequest request = new AppDeploymentRequest(definition, resource, deploymentProperties);\n\t\tProcessBuilder builder = deployer.buildProcessBuilder(request, request.getDefinition().getProperties(), Optional.of(1), \"foo\");\n\n\t\tString SAJ = LocalDeployerUtils.isWindows() ? \"SPRING_APPLICATION_JSON={\\\\\\\"foo\\\\\\\":\\\\\\\"bar\\\\\\\"}\" : \"SPRING_APPLICATION_JSON={\\\"foo\\\":\\\"bar\\\"}\";\n\t\tassertThat(builder.command()).contains(\"-e\", SAJ);\n\t}\n\n\t@Test\n\tpublic void  testContainerPortMappings(){\n\t\tAppDefinition appDefinition = new AppDefinition(\"foo\", null);\n\t\tResource resource = new DockerResource(\"foo/bar\");\n\t\tMap<String, String> deploymentProperties = Collections.emptyMap();\n\t\tAppDeploymentRequest request = new AppDeploymentRequest(appDefinition, resource, deploymentProperties);\n\n\t\tString goodMapping1 = \"9090:9090\";\n\t\tString goodMapping2 = \"6090:7090\";\n\t\tString incompleteMapping = \"8888\";\n\t\tLocalDeployerProperties localDeployerProperties = new LocalDeployerProperties();\n\t\tlocalDeployerProperties.getDocker().setPortMappings(goodMapping1 + \",\" + goodMapping2 + \",\" + incompleteMapping);\n\n\t\tProcessBuilder builder = new DockerCommandBuilder(\"scdf_default\")\n\t\t\t\t.buildExecutionCommand(request, new HashMap<>(), \"deployerId\", Optional.of(1),\n\t\t\t\t\t\tlocalDeployerProperties, Optional.empty());\n\n\t\tassertThat(builder.command()).contains(goodMapping1, goodMapping2);\n\t\tassertThat(builder.command()).doesNotContain(incompleteMapping);\n\t}\n\n\t@Test\n\tpublic void  testContainerVolumeMount(){\n\t\tAppDefinition appDefinition = new AppDefinition(\"foo\", null);\n\t\tResource resource = new DockerResource(\"foo/bar\");\n\t\tMap<String, String> deploymentProperties = Collections.emptyMap();\n\t\tAppDeploymentRequest request = new AppDeploymentRequest(appDefinition, resource, deploymentProperties);\n\n\t\tString goodMapping1 = \"/tmp:/tmp\";\n\t\tString goodMapping2 = \"/opt:/opt\";\n\t\tString incompleteMapping = \"/dev/null\";\n\t\tLocalDeployerProperties localDeployerProperties = new LocalDeployerProperties();\n\t\tlocalDeployerProperties.getDocker().setVolumeMounts(goodMapping1 + \",\" + goodMapping2 + \",\" + incompleteMapping);\n\n\t\tProcessBuilder builder = new DockerCommandBuilder(\"scdf_default\")\n\t\t\t\t.buildExecutionCommand(request, new HashMap<>(), \"deployerId\", Optional.of(1),\n\t\t\t\t\t\tlocalDeployerProperties, Optional.empty());\n\n\t\tassertThat(builder.command()).contains(goodMapping1, goodMapping2);\n\t\tassertThat(builder.command()).doesNotContain(incompleteMapping);\n\t}\n}\n"
  },
  {
    "path": "src/test/java/org/springframework/cloud/deployer/spi/local/JavaExecutionCommandBuilderTests.java",
    "content": "/*\n * Copyright 2015-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.local;\n\nimport java.io.File;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.stream.Collectors;\n\nimport org.junit.jupiter.api.Assertions;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\n\nimport org.springframework.cloud.deployer.spi.app.AppDeployer;\nimport org.springframework.cloud.deployer.spi.core.AppDefinition;\nimport org.springframework.cloud.deployer.spi.core.AppDeploymentRequest;\nimport org.springframework.core.io.ClassPathResource;\nimport org.springframework.core.io.Resource;\nimport org.springframework.core.io.UrlResource;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\npublic class JavaExecutionCommandBuilderTests {\n\n\tprivate JavaCommandBuilder commandBuilder;\n\tprivate List<String> args;\n\tprivate Map<String, String> deploymentProperties;\n\tprivate LocalDeployerProperties localDeployerProperties;\n\n\t@BeforeEach\n\tpublic void setUp() {\n\t\targs = new ArrayList<>();\n\t\tdeploymentProperties = new HashMap<>();\n\t\tlocalDeployerProperties = new LocalDeployerProperties();\n\t\tcommandBuilder = new JavaCommandBuilder(localDeployerProperties);\n\t}\n\n\t@Test\n\tpublic void testDirectJavaMemoryOption() {\n\t\tdeploymentProperties.put(AppDeployer.MEMORY_PROPERTY_KEY, \"1024m\");\n\t\tcommandBuilder.addJavaOptions(args, deploymentProperties, localDeployerProperties);\n\t\tassertThat(args).hasSize(1);\n\t\tassertThat(args.get(0)).isEqualTo(\"-Xmx1024m\");\n\t}\n\n\t@Test\n\tpublic void testDirectJavaMemoryOptionWithG() {\n\t\tdeploymentProperties.put(AppDeployer.MEMORY_PROPERTY_KEY, \"1g\");\n\t\tcommandBuilder.addJavaOptions(args, deploymentProperties, localDeployerProperties);\n\t\tassertThat(args).hasSize(1);\n\t\tassertThat(args.get(0)).isEqualTo(\"-Xmx1024m\");\n\t}\n\n\t@Test\n\tpublic void testJavaMemoryOption() {\n\t\tdeploymentProperties.put(LocalDeployerProperties.PREFIX + \".javaOpts\", \"-Xmx1024m\");\n\t\tcommandBuilder.addJavaOptions(args, deploymentProperties, localDeployerProperties);\n\t\tassertThat(args).hasSize(1);\n\t\tassertThat(args.get(0)).isEqualTo(\"-Xmx1024m\");\n\t}\n\n\t@Test\n\tpublic void testJavaMemoryOptionWithKebabCase() {\n\t\tdeploymentProperties.put(LocalDeployerProperties.PREFIX + \".java-opts\", \"-Xmx1024m\");\n\t\tcommandBuilder.addJavaOptions(args, deploymentProperties, localDeployerProperties);\n\t\tassertThat(args).hasSize(1);\n\t\tassertThat(args.get(0)).isEqualTo(\"-Xmx1024m\");\n\t}\n\n\t@Test\n\tpublic void testJavaCmdOption() throws Exception {\n\t\tMap<String, String> properties = new HashMap<>();\n\t\tproperties.put(LocalDeployerProperties.PREFIX + \".javaCmd\", \"/test/java\");\n\t\tResource resource = mock(Resource.class);\n\t\twhen(resource.getFile()).thenReturn(new File(\"/\"));\n\t\tAppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(mock(AppDefinition.class), resource, properties);\n\t\tProcessBuilder builder = commandBuilder.buildExecutionCommand(appDeploymentRequest, new HashMap<>(),\n\t\t\t\t\"deployerId\", Optional.of(1), new LocalDeployerProperties(), Optional.empty());\n\t\tassertThat(builder.command().get(0)).isEqualTo(\"/test/java\");\n\t}\n\n\t@Test\n\tpublic void testJavaCmdOptionWithKebabCase() throws Exception {\n\t\tMap<String, String> properties = new HashMap<>();\n\t\tproperties.put(LocalDeployerProperties.PREFIX + \".java-cmd\", \"/test/java\");\n\t\tResource resource = mock(Resource.class);\n\t\twhen(resource.getFile()).thenReturn(new File(\"/\"));\n\t\tAppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(mock(AppDefinition.class), resource, properties);\n\t\tProcessBuilder builder = commandBuilder.buildExecutionCommand(appDeploymentRequest, new HashMap<>(),\n\t\t\t\t\"deployerId\", Optional.of(1), new LocalDeployerProperties(), Optional.empty());\n\t\tassertThat(builder.command().get(0)).isEqualTo(\"/test/java\");\n\t}\n\n\t@Test\n\tpublic void testOverrideMemoryOptions() {\n\t\tdeploymentProperties.put(AppDeployer.MEMORY_PROPERTY_KEY, \"1024m\");\n\t\tdeploymentProperties.put(LocalDeployerProperties.PREFIX + \".javaOpts\", \"-Xmx2048m\");\n\t\tcommandBuilder.addJavaOptions(args, deploymentProperties, localDeployerProperties);\n\t\tassertThat(args).hasSize(1);\n\t\tassertThat(args.get(0)).isEqualTo(\"-Xmx2048m\");\n\t}\n\n\t@Test\n\tpublic void testDirectMemoryOptionsWithOtherOptions() {\n\t\tdeploymentProperties.put(AppDeployer.MEMORY_PROPERTY_KEY, \"1024m\");\n\t\tdeploymentProperties.put(LocalDeployerProperties.PREFIX + \".javaOpts\", \"-Dtest=foo\");\n\t\tcommandBuilder.addJavaOptions(args, deploymentProperties, localDeployerProperties);\n\t\tassertThat(args).hasSize(2);\n\t\tassertThat(args.get(0)).isEqualTo(\"-Xmx1024m\");\n\t\tassertThat(args.get(1)).isEqualTo(\"-Dtest=foo\");\n\t}\n\n\t@Test\n\tpublic void testMultipleOptions() {\n\t\tdeploymentProperties.put(LocalDeployerProperties.PREFIX + \".javaOpts\", \"-Dtest=foo -Dbar=baz\");\n\t\tcommandBuilder.addJavaOptions(args, deploymentProperties, localDeployerProperties);\n\t\tassertThat(args).hasSize(2);\n\t\tassertThat(args.get(0)).isEqualTo(\"-Dtest=foo\");\n\t\tassertThat(args.get(1)).isEqualTo(\"-Dbar=baz\");\n\t}\n\n\t@Test\n\tpublic void testConfigurationPropertiesOverride() {\n\t\tlocalDeployerProperties.setJavaOpts(\"-Dfoo=test -Dbaz=bar\");\n\t\tcommandBuilder.addJavaOptions(args, deploymentProperties, localDeployerProperties);\n\t\tassertThat(args).hasSize(2);\n\t\tassertThat(args.get(0)).isEqualTo(\"-Dfoo=test\");\n\t\tassertThat(args.get(1)).isEqualTo(\"-Dbaz=bar\");\n\t}\n\n\t@Test\n\tpublic void testJarExecution() {\n\t\tAppDefinition definition = new AppDefinition(\"randomApp\", new HashMap<>());\n\t\tdeploymentProperties.put(LocalDeployerProperties.PREFIX + \".javaOpts\", \"-Dtest=foo -Dbar=baz\");\n\t\tAppDeploymentRequest appDeploymentRequest =\n\t\t\t\tnew AppDeploymentRequest(definition, testResource(), deploymentProperties);\n\t\tcommandBuilder.addJavaExecutionOptions(args, appDeploymentRequest);\n\t\tassertThat(args).hasSize(2);\n\t\tassertThat(args.get(0)).isEqualTo(\"-jar\");\n\t\tassertThat(args.get(1)).contains(\"testResource.txt\");\n\t}\n\n\t@Test\n\tpublic void testBadResourceExecution() {\n\t\tAssertions.assertThrows(IllegalStateException.class, () -> {\n\t\t\tAppDefinition definition = new AppDefinition(\"randomApp\", new HashMap<>());\n\t\t\tdeploymentProperties.put(LocalDeployerProperties.PREFIX + \".javaOpts\", \"-Dtest=foo -Dbar=baz\");\n\t\t\tAppDeploymentRequest appDeploymentRequest =\n\t\t\t\t\tnew AppDeploymentRequest(definition, new UrlResource(\"https://spring.io\"), deploymentProperties);\n\t\t\tcommandBuilder.addJavaExecutionOptions(args, appDeploymentRequest);\n\t\t});\n\t}\n\n\t@Test\n\tpublic void testCommandBuilderSpringApplicationJson() {\n\t\tLocalDeployerProperties properties = new LocalDeployerProperties();\n\t\tLocalAppDeployer deployer = new LocalAppDeployer(properties);\n\t\tAppDefinition definition = new AppDefinition(\"foo\", Collections.singletonMap(\"foo\", \"bar\"));\n\n\t\tdeploymentProperties.put(LocalDeployerProperties.DEBUG_PORT, \"9999\");\n\t\tdeploymentProperties.put(LocalDeployerProperties.DEBUG_SUSPEND, \"y\");\n\t\tdeploymentProperties.put(LocalDeployerProperties.INHERIT_LOGGING, \"true\");\n\t\tAppDeploymentRequest request = new AppDeploymentRequest(definition, testResource(), deploymentProperties);\n\n\t\tProcessBuilder builder = deployer.buildProcessBuilder(request, definition.getProperties(), Optional.of(1), \"foo\");\n\t\tassertThat(builder.environment().keySet()).contains(AbstractLocalDeployerSupport.SPRING_APPLICATION_JSON);\n\t\tassertThat(builder.environment().get(AbstractLocalDeployerSupport.SPRING_APPLICATION_JSON)).isEqualTo(\"{\\\"foo\\\":\\\"bar\\\"}\");\n\t}\n\n\t@Test\n\tpublic void testCommandBuilderWithSpringApplicationJson() {\n\t\tLocalDeployerProperties properties = new LocalDeployerProperties();\n\t\tLocalAppDeployer deployer = new LocalAppDeployer(properties);\n\t\tMap<String, String> applicationProperties = new HashMap<>();\n\t\tapplicationProperties.put(\"foo\", \"bar\");\n\t\tString SAJ = \"{\\\"debug\\\":\\\"true\\\"}\";\n\t\tapplicationProperties.put(AbstractLocalDeployerSupport.SPRING_APPLICATION_JSON, SAJ);\n\t\tAppDefinition definition = new AppDefinition(\"foo\", applicationProperties);\n\n\t\tdeploymentProperties.put(LocalDeployerProperties.DEBUG_PORT, \"9999\");\n\t\tdeploymentProperties.put(LocalDeployerProperties.DEBUG_SUSPEND, \"y\");\n\t\tdeploymentProperties.put(LocalDeployerProperties.INHERIT_LOGGING, \"true\");\n\t\tAppDeploymentRequest request = new AppDeploymentRequest(definition, testResource(), deploymentProperties);\n\n\n\t\tProcessBuilder builder = deployer.buildProcessBuilder(request, definition.getProperties(), Optional.of(1), \"foo\");\n\t\tassertThat(builder.environment().keySet()).contains(AbstractLocalDeployerSupport.SPRING_APPLICATION_JSON);\n\t\tassertThat(builder.environment().get(AbstractLocalDeployerSupport.SPRING_APPLICATION_JSON)).isEqualTo(\"{\\\"foo\\\":\\\"bar\\\",\\\"debug\\\":\\\"true\\\"}\");\n\n\t}\n\n\t@Test\n\tpublic void testRetainEnv() {\n\t\tLocalDeployerProperties properties1 = new LocalDeployerProperties();\n\t\tLocalAppDeployer deployer1 = new LocalAppDeployer(properties1);\n\t\tAppDefinition definition1 = new AppDefinition(\"foo\", null);\n\t\tAppDeploymentRequest request1 = new AppDeploymentRequest(definition1, testResource(), deploymentProperties);\n\t\tProcessBuilder builder1 = deployer1.buildProcessBuilder(request1, definition1.getProperties(), Optional.of(1), \"foo\");\n\t\tList<String> env1 = builder1.environment().keySet().stream().map(String::toLowerCase).collect(Collectors.toList());\n\n\t\tLocalDeployerProperties properties2 = new LocalDeployerProperties();\n\t\tproperties2.setEnvVarsToInherit(new String[0]);\n\t\tLocalAppDeployer deployer2 = new LocalAppDeployer(properties2);\n\t\tAppDefinition definition2 = new AppDefinition(\"foo\", null);\n\t\tAppDeploymentRequest request2 = new AppDeploymentRequest(definition2, testResource(), deploymentProperties);\n\t\tProcessBuilder builder2 = deployer2.buildProcessBuilder(request2, definition2.getProperties(), Optional.of(1), \"foo\");\n\t\tList<String> env2 = builder2.environment().keySet().stream().map(String::toLowerCase).collect(Collectors.toList());\n\n\t\tif (env1.contains(\"path\")) {\n\t\t\t// path should be there, and it was check that something were removed\n\t\t\tassertThat(builder1.environment().keySet().size()).isGreaterThan(builder2.environment().keySet().size());\n\t\t}\n\t\tassertThat(env2).doesNotContain(\"path\");\n\t}\n\n\tprotected Resource testResource() {\n\t\treturn new ClassPathResource(\"testResource.txt\");\n\t}\n\n}\n"
  },
  {
    "path": "src/test/java/org/springframework/cloud/deployer/spi/local/LocalAppDeployerEnvironmentIntegrationTests.java",
    "content": "/*\n * Copyright 2018-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.local;\n\nimport java.io.BufferedReader;\nimport java.io.IOException;\nimport java.io.InputStreamReader;\nimport java.nio.ByteBuffer;\nimport java.time.Duration;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.Map.Entry;\nimport java.util.UUID;\n\nimport org.junit.jupiter.api.Test;\n\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.boot.context.properties.EnableConfigurationProperties;\nimport org.springframework.boot.test.context.SpringBootTest;\nimport org.springframework.cloud.deployer.resource.docker.DockerResource;\nimport org.springframework.cloud.deployer.spi.app.ActuatorOperations;\nimport org.springframework.cloud.deployer.spi.app.AppDeployer;\nimport org.springframework.cloud.deployer.spi.app.AppInstanceStatus;\nimport org.springframework.cloud.deployer.spi.app.AppStatus;\nimport org.springframework.cloud.deployer.spi.app.DeploymentState;\nimport org.springframework.cloud.deployer.spi.core.AppDefinition;\nimport org.springframework.cloud.deployer.spi.core.AppDeploymentRequest;\nimport org.springframework.cloud.deployer.spi.local.LocalAppDeployerEnvironmentIntegrationTests.Config;\nimport org.springframework.cloud.deployer.spi.test.AbstractAppDeployerIntegrationJUnit5Tests;\nimport org.springframework.cloud.deployer.spi.test.AbstractIntegrationTests;\nimport org.springframework.cloud.deployer.spi.test.Timeout;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.core.io.Resource;\nimport org.springframework.web.client.RestTemplate;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.awaitility.Awaitility.await;\n\n/**\n * Integration tests for {@link LocalAppDeployer} not using SAJ.\n *\n * Now supports running with Docker images for tests, just set this env var:\n *\n *   SPRING_CLOUD_DEPLOYER_SPI_TEST_USE_DOCKER=true\n *\n * @author Eric Bottard\n * @author Mark Fisher\n * @author Oleg Zhurakousky\n * @author Janne Valkealahti\n * @author Ilayaperumal Gopinathan\n */\n@SpringBootTest(classes = { Config.class, AbstractIntegrationTests.Config.class }, value = {\n\t\t\"maven.remoteRepositories.springRepo.url=https://repo.spring.io/snapshot\",\n\t\t\"spring.cloud.deployer.local.use-spring-application-json=false\"\n})\npublic class LocalAppDeployerEnvironmentIntegrationTests extends AbstractAppDeployerIntegrationJUnit5Tests {\n\n\tprivate static final String TESTAPP_DOCKER_IMAGE_NAME = \"springcloud/spring-cloud-deployer-spi-test-app:latest\";\n\n\t@Autowired\n\tprivate AppDeployer appDeployer;\n\n\t@Autowired\n\tprivate ActuatorOperations actuatorOperations;\n\n\t@Value(\"${spring-cloud-deployer-spi-test-use-docker:false}\")\n\tprivate boolean useDocker;\n\n\t@Override\n\tprotected AppDeployer provideAppDeployer() {\n\t\treturn appDeployer;\n\t}\n\n\t@Override\n\tprotected Resource testApplication() {\n\t\tif (useDocker) {\n\t\t\tlogger.info(\"Using Docker image for tests\");\n\t\t\treturn new DockerResource(TESTAPP_DOCKER_IMAGE_NAME);\n\t\t}\n\t\treturn super.testApplication();\n\t}\n\n\t@Override\n\tprotected String randomName() {\n\t\tif (LocalDeployerUtils.isWindows()) {\n\t\t\t// tweak random dir name on win to be shorter\n\t\t\tString uuid = UUID.randomUUID().toString();\n\t\t\tlong l = ByteBuffer.wrap(uuid.toString().getBytes()).getLong();\n\t\t\treturn testName + Long.toString(l, Character.MAX_RADIX);\n\t\t}\n\t\telse {\n\t\t\treturn super.randomName();\n\t\t}\n\t}\n\n\t@Test\n\tpublic void testEnvVariablesInheritedViaEnvEndpointNoSaj() {\n\t\tif (useDocker) {\n\t\t\t// would not expect to be able to check anything on docker\n\t\t\treturn;\n\t\t}\n\t\tMap<String, String> properties = new HashMap<>();\n\t\tAppDefinition definition = new AppDefinition(randomName(), properties);\n\t\tResource resource = testApplication();\n\t\tAppDeploymentRequest request = new AppDeploymentRequest(definition, resource);\n\n\t\tlogger.info(\"Deploying {}...\", request.getDefinition().getName());\n\n\t\tString deploymentId = appDeployer().deploy(request);\n\t\tTimeout timeout = deploymentTimeout();\n\t\tawait().pollInterval(Duration.ofMillis(timeout.pause))\n\t\t\t\t.atMost(Duration.ofMillis(timeout.totalTime))\n\t\t\t\t.untilAsserted(() -> {\n\t\t\tassertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.deployed);\n\t\t});\n\n\t\tMap<String, AppInstanceStatus> instances = appDeployer().status(deploymentId).getInstances();\n\t\tString url = null;\n\t\tif (instances.size() == 1) {\n\t\t\turl = instances.entrySet().iterator().next().getValue().getAttributes().get(\"url\");\n\t\t}\n\t\tString env = null;\n\t\tif (url != null) {\n\t\t\tRestTemplate template = new RestTemplate();\n\t\t\tenv = template.getForObject(url + \"/actuator/env\", String.class);\n\t\t}\n\n\t\tlogger.info(\"Undeploying {}...\", deploymentId);\n\n\t\ttimeout = undeploymentTimeout();\n\t\tappDeployer().undeploy(deploymentId);\n\t\tawait().pollInterval(Duration.ofMillis(timeout.pause))\n\t\t\t\t.atMost(Duration.ofMillis(timeout.totalTime))\n\t\t\t\t.untilAsserted(() -> {\n\t\t\tassertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.unknown);\n\t\t});\n\n\t\tassertThat(url).isNotNull();\n\t\tif (LocalDeployerUtils.isWindows()) {\n\t\t\t// windows is weird, we may still get Path or PATH\n\t\t\tassertThat(env).containsIgnoringCase(\"path\");\n\t\t}\n\t\telse {\n\t\t\tassertThat(env).contains(\"\\\"PATH\\\"\");\n\t\t\t// we're not using SAJ so it's i.e.\n\t\t\t// INSTANCE_INDEX not instance.index\n\t\t\tassertThat(env).contains(\"\\\"INSTANCE_INDEX\\\"\");\n\t\t\tassertThat(env).contains(\"\\\"SPRING_APPLICATION_INDEX\\\"\");\n\t\t\tassertThat(env).contains(\"\\\"SPRING_CLOUD_APPLICATION_GUID\\\"\");\n\t\t}\n\t}\n\n\t@Test\n\tpublic void testFailureToCallShutdownOnUndeploy() {\n\t\tMap<String, String> properties = new HashMap<>();\n\t\t// disable shutdown endpoint\n\t\tproperties.put(\"endpoints.shutdown.enabled\", \"false\");\n\t\tAppDefinition definition = new AppDefinition(randomName(), properties);\n\t\tResource resource = testApplication();\n\t\tAppDeploymentRequest request = new AppDeploymentRequest(definition, resource);\n\n\t\tlogger.info(\"Deploying {}...\", request.getDefinition().getName());\n\n\t\tString deploymentId = appDeployer().deploy(request);\n\t\tTimeout timeout = deploymentTimeout();\n\t\tawait().pollInterval(Duration.ofMillis(timeout.pause))\n\t\t\t\t.atMost(Duration.ofMillis(timeout.totalTime))\n\t\t\t\t.untilAsserted(() -> {\n\t\t\tassertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.deployed);\n\t\t});\n\n\t\tlogger.info(\"Undeploying {}...\", deploymentId);\n\n\t\ttimeout = undeploymentTimeout();\n\t\tappDeployer().undeploy(deploymentId);\n\t\tawait().pollInterval(Duration.ofMillis(timeout.pause))\n\t\t\t\t.atMost(Duration.ofMillis(timeout.totalTime))\n\t\t\t\t.untilAsserted(() -> {\n\t\t\tassertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.unknown);\n\t\t});\n\t}\n\n\t@Test\n\t// was triggered by GH-50 and subsequently GH-55\n\tpublic void testNoStdoutStderrOnInheritLoggingAndNoNPEOnGetAttributes() {\n\t\tMap<String, String> properties = new HashMap<>();\n\t\tAppDefinition definition = new AppDefinition(randomName(), properties);\n\t\tResource resource = testApplication();\n\t\tAppDeploymentRequest request = new AppDeploymentRequest(definition, resource, Collections.singletonMap(LocalDeployerProperties.INHERIT_LOGGING, \"true\"));\n\n\t\tAppDeployer deployer = appDeployer();\n\t\tString deploymentId = deployer.deploy(request);\n\t\tAppStatus appStatus = deployer.status(deploymentId);\n\t\tassertThat(appStatus.getInstances()).hasSizeGreaterThan(0);\n\t\tfor (Entry<String, AppInstanceStatus> instanceStatusEntry : appStatus.getInstances().entrySet()) {\n\t\t\tMap<String, String> attributes = instanceStatusEntry.getValue().getAttributes();\n\t\t\tassertThat(attributes).doesNotContainKey(\"stdout\");\n\t\t\tassertThat(attributes).doesNotContainKey(\"stderr\");\n\t\t}\n\t\tdeployer.undeploy(deploymentId);\n\t}\n\n\t@Test\n\tpublic void testInDebugModeWithSuspended() throws Exception {\n\t\tMap<String, String> properties = new HashMap<>();\n\t\tAppDefinition definition = new AppDefinition(randomName(), properties);\n\t\tResource resource = testApplication();\n\t\tMap<String, String> deploymentProperties = new HashMap<>();\n\t\tdeploymentProperties.put(LocalDeployerProperties.DEBUG_PORT, \"9999\");\n\t\tdeploymentProperties.put(LocalDeployerProperties.DEBUG_SUSPEND, \"y\");\n\t\tdeploymentProperties.put(LocalDeployerProperties.INHERIT_LOGGING, \"true\");\n\t\tAppDeploymentRequest request = new AppDeploymentRequest(definition, resource, deploymentProperties);\n\n\t\tAppDeployer deployer = appDeployer();\n\t\tString deploymentId = deployer.deploy(request);\n\t\tThread.sleep(5000);\n\t\tAppStatus appStatus = deployer.status(deploymentId);\n\t\tif (resource instanceof DockerResource) {\n\t\t\ttry {\n\t\t\t\tString containerId = getCommandOutput(\"docker ps -q --filter ancestor=\"+ TESTAPP_DOCKER_IMAGE_NAME);\n\t\t\t\tString logOutput = getCommandOutput(\"docker logs \"+ containerId);\n\t\t\t\tassertThat(logOutput).contains(\"Listening for transport dt_socket at address: 9999\");\n\t\t\t} catch (IOException e) {\n\t\t\t}\n\t\t}\n\t\telse {\n\t\t\tassertThat(appStatus.toString()).contains(\"deploying\");\n\t\t}\n\n\t\tdeployer.undeploy(deploymentId);\n\t}\n\n\t@Test\n\tpublic void testActuatorOperations() {\n\t\tif (useDocker) {\n\t\t\t// would not expect to be able to check anything on docker\n\t\t\treturn;\n\t\t}\n\t\tMap<String, String> properties = new HashMap<>();\n\t\tAppDefinition definition = new AppDefinition(randomName(), properties);\n\t\tResource resource = testApplication();\n\t\tAppDeploymentRequest request = new AppDeploymentRequest(definition, resource);\n\n\t\tlogger.info(\"Deploying {}...\", request.getDefinition().getName());\n\n\t\tString deploymentId = appDeployer().deploy(request);\n\t\tTimeout timeout = deploymentTimeout();\n\t\tawait().pollInterval(Duration.ofMillis(timeout.pause))\n\t\t\t\t.atMost(Duration.ofMillis(timeout.totalTime))\n\t\t\t\t.untilAsserted(() -> {\n\t\t\t\t\tassertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.deployed);\n\t\t\t\t});\n\t\tString id = deploymentId + \"-0\";\n\t\tMap<String, Object> env = actuatorOperations\n\t\t\t\t.getFromActuator(deploymentId, id, \"/env\", Map.class);\n\t\tassertThat(env).containsKeys(\"activeProfiles\", \"propertySources\");\n\t\tMap<String, Object> status = actuatorOperations\n\t\t\t\t.getFromActuator(deploymentId, id, \"/health\", Map.class);\n\t\tassertThat(status.get(\"status\")).isEqualTo(\"UP\");\n\n\t\tMap<String, Object> loggers = actuatorOperations\n\t\t\t\t.getFromActuator(deploymentId, id, \"/loggers/org.springframework\", Map.class);\n\t\tassertThat(loggers).isNotNull();\n\t\tassertThat(loggers.get(\"configuredLevel\")).isNull();\n\t\tactuatorOperations.postToActuator(deploymentId, id,\"/loggers/org.springframework\",\n\t\t\t\tCollections.singletonMap(\"configuredLevel\", \"debug\"),  Object.class);\n\t\tloggers = actuatorOperations\n\t\t\t\t.getFromActuator(deploymentId, id, \"/loggers/org.springframework\", Map.class);\n\t\tassertThat(((String)loggers.get(\"configuredLevel\")).toLowerCase(Locale.ROOT)).isEqualTo(\"debug\");\n\n\t}\n\n\tprivate String getCommandOutput(String cmd) throws IOException {\n\t\tProcess process = Runtime.getRuntime().exec(cmd);\n\t\tBufferedReader stdInput = new BufferedReader(new InputStreamReader(process.getInputStream()));\n\t\treturn stdInput.lines().findFirst().get();\n\t}\n\n\t@Configuration\n\t@EnableConfigurationProperties(LocalDeployerProperties.class)\n\tpublic static class Config {\n\n\t\t@Bean\n\t\tpublic AppDeployer appDeployer(LocalDeployerProperties properties) {\n\t\t\treturn new LocalAppDeployer(properties);\n\t\t}\n\n\t\t@Bean\n\t\tActuatorOperations actuatorOperations(AppDeployer appDeployer, LocalDeployerProperties properties) {\n\t\t\treturn new LocalActuatorTemplate(new RestTemplate(), appDeployer, properties.getAppAdmin());\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "src/test/java/org/springframework/cloud/deployer/spi/local/LocalAppDeployerIntegrationTests.java",
    "content": "/*\n * Copyright 2016-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.local;\n\nimport java.io.BufferedReader;\nimport java.io.File;\nimport java.io.IOException;\nimport java.io.InputStreamReader;\nimport java.nio.ByteBuffer;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.time.Duration;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Map.Entry;\nimport java.util.UUID;\nimport java.util.stream.Collectors;\n\nimport org.junit.jupiter.api.Test;\n\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.boot.context.properties.EnableConfigurationProperties;\nimport org.springframework.boot.test.context.SpringBootTest;\nimport org.springframework.cloud.deployer.resource.docker.DockerResource;\nimport org.springframework.cloud.deployer.spi.app.AppDeployer;\nimport org.springframework.cloud.deployer.spi.app.AppInstanceStatus;\nimport org.springframework.cloud.deployer.spi.app.AppStatus;\nimport org.springframework.cloud.deployer.spi.app.DeploymentState;\nimport org.springframework.cloud.deployer.spi.core.AppDefinition;\nimport org.springframework.cloud.deployer.spi.core.AppDeploymentRequest;\nimport org.springframework.cloud.deployer.spi.local.LocalAppDeployerIntegrationTests.Config;\nimport org.springframework.cloud.deployer.spi.test.AbstractAppDeployerIntegrationJUnit5Tests;\nimport org.springframework.cloud.deployer.spi.test.AbstractIntegrationTests;\nimport org.springframework.cloud.deployer.spi.test.Timeout;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.core.io.Resource;\nimport org.springframework.web.client.RestTemplate;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.awaitility.Awaitility.await;\n\n/**\n * Integration tests for {@link LocalAppDeployer}.\n *\n * Now supports running with Docker images for tests, just set this env var:\n *\n *   SPRING_CLOUD_DEPLOYER_SPI_TEST_USE_DOCKER=true\n *\n * @author Eric Bottard\n * @author Mark Fisher\n * @author Oleg Zhurakousky\n * @author Janne Valkealahti\n * @author Ilayaperumal Gopinathan\n */\n@SpringBootTest(classes = {Config.class, AbstractIntegrationTests.Config.class}, value = {\n\t\t\"maven.remoteRepositories.springRepo.url=https://repo.spring.io/snapshot\"})\npublic class LocalAppDeployerIntegrationTests extends AbstractAppDeployerIntegrationJUnit5Tests {\n\n\tprivate static final String TESTAPP_DOCKER_IMAGE_NAME = \"springcloud/spring-cloud-deployer-spi-test-app:latest\";\n\n\t@Autowired\n\tprivate AppDeployer appDeployer;\n\n\t@Value(\"${spring-cloud-deployer-spi-test-use-docker:false}\")\n\tprivate boolean useDocker;\n\n\t@Override\n\tprotected AppDeployer provideAppDeployer() {\n\t\treturn appDeployer;\n\t}\n\n\t@Override\n\tprotected Resource testApplication() {\n\t\tif (useDocker) {\n\t\t\tlogger.info(\"Using Docker image for tests\");\n\t\t\treturn new DockerResource(TESTAPP_DOCKER_IMAGE_NAME);\n\t\t}\n\t\treturn super.testApplication();\n\t}\n\n\t@Override\n\tprotected String randomName() {\n\t\tif (LocalDeployerUtils.isWindows()) {\n\t\t\t// tweak random dir name on win to be shorter\n\t\t\tString uuid = UUID.randomUUID().toString();\n\t\t\tlong l = ByteBuffer.wrap(uuid.toString().getBytes()).getLong();\n\t\t\treturn testName + Long.toString(l, Character.MAX_RADIX);\n\t\t}\n\t\telse {\n\t\t\treturn super.randomName();\n\t\t}\n\t}\n\n\t@Test\n\tpublic void testEnvVariablesInheritedViaEnvEndpoint() {\n\t\tif (useDocker) {\n\t\t\t// would not expect to be able to check anything on docker\n\t\t\treturn;\n\t\t}\n\t\tMap<String, String> properties = new HashMap<>();\n\t\tAppDefinition definition = new AppDefinition(randomName(), properties);\n\t\tResource resource = testApplication();\n\t\tAppDeploymentRequest request = new AppDeploymentRequest(definition, resource);\n\n\t\tlogger.info(\"Deploying {}...\", request.getDefinition().getName());\n\n\t\tString deploymentId = appDeployer().deploy(request);\n\t\tTimeout timeout = deploymentTimeout();\n\t\tawait().pollInterval(Duration.ofMillis(timeout.pause))\n\t\t\t\t.atMost(Duration.ofMillis(timeout.totalTime))\n\t\t\t\t.untilAsserted(() -> {\n\t\t\tassertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.deployed);\n\t\t});\n\n\t\tMap<String, AppInstanceStatus> instances = appDeployer().status(deploymentId).getInstances();\n\t\tString url = null;\n\t\tif (instances.size() == 1) {\n\t\t\turl = instances.entrySet().iterator().next().getValue().getAttributes().get(\"url\");\n\t\t}\n\t\tString env = null;\n\t\tif (url != null) {\n\t\t\tRestTemplate template = new RestTemplate();\n\t\t\tenv = template.getForObject(url + \"/actuator/env\", String.class);\n\t\t}\n\n\t\tlogger.info(\"Undeploying {}...\", deploymentId);\n\n\t\ttimeout = undeploymentTimeout();\n\t\tappDeployer().undeploy(deploymentId);\n\t\tawait().pollInterval(Duration.ofMillis(timeout.pause))\n\t\t\t\t.atMost(Duration.ofMillis(timeout.totalTime))\n\t\t\t\t.untilAsserted(() -> {\n\t\t\tassertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.unknown);\n\t\t});\n\n\t\tassertThat(url).isNotNull();\n\t\tif (LocalDeployerUtils.isWindows()) {\n\t\t\t// windows is weird, we may still get Path or PATH\n\t\t\tassertThat(env).containsIgnoringCase(\"path\");\n\t\t}\n\t\telse {\n\t\t\tassertThat(env).contains(\"\\\"PATH\\\"\");\n\t\t\t// we're defaulting to SAJ so it's i.e.\n\t\t\t// instance.index not INSTANCE_INDEX\n\t\t\tassertThat(env).contains(\"\\\"instance.index\\\"\");\n\t\t\tassertThat(env).contains(\"\\\"spring.application.index\\\"\");\n\t\t\tassertThat(env).contains(\"\\\"spring.cloud.application.guid\\\"\");\n\t\t\tassertThat(env).contains(\"\\\"spring.cloud.stream.instanceIndex\\\"\");\n\t\t}\n\t}\n\n\t@Test\n\tpublic void testAppLogRetrieval() {\n\t\tMap<String, String> properties = new HashMap<>();\n\t\tAppDefinition definition = new AppDefinition(randomName(), properties);\n\t\tResource resource = testApplication();\n\t\tAppDeploymentRequest request = new AppDeploymentRequest(definition, resource);\n\n\t\tlogger.info(\"Deploying {}...\", request.getDefinition().getName());\n\t\tString deploymentId = appDeployer().deploy(request);\n\t\tTimeout timeout = deploymentTimeout();\n\t\tawait().pollInterval(Duration.ofMillis(timeout.pause))\n\t\t\t\t.atMost(Duration.ofMillis(timeout.totalTime))\n\t\t\t\t.untilAsserted(() -> {\n\t\t\tassertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.deployed);\n\t\t});\n\t\tString logContent = appDeployer().getLog(deploymentId);\n\t\tassertThat(logContent).contains(\"Starting DeployerIntegrationTestApplication\");\n\t}\n\n\t// TODO: remove when these two are forced in tck tests\n\t@Test\n\tpublic void testScale() {\n\t\tdoTestScale(false);\n\t}\n\n\t@Test\n\tpublic void testScaleWithIndex() {\n\t\tdoTestScale(true);\n\t}\n\n\t@Test\n\tpublic void testFailureToCallShutdownOnUndeploy() {\n\t\tMap<String, String> properties = new HashMap<>();\n\t\t// disable shutdown endpoint\n\t\tproperties.put(\"endpoints.shutdown.enabled\", \"false\");\n\t\tAppDefinition definition = new AppDefinition(randomName(), properties);\n\t\tResource resource = testApplication();\n\t\tAppDeploymentRequest request = new AppDeploymentRequest(definition, resource);\n\n\t\tlogger.info(\"Deploying {}...\", request.getDefinition().getName());\n\n\t\tString deploymentId = appDeployer().deploy(request);\n\t\tTimeout timeout = deploymentTimeout();\n\t\tawait().pollInterval(Duration.ofMillis(timeout.pause))\n\t\t\t\t.atMost(Duration.ofMillis(timeout.totalTime))\n\t\t\t\t.untilAsserted(() -> {\n\t\t\tassertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.deployed);\n\t\t});\n\n\t\tlogger.info(\"Undeploying {}...\", deploymentId);\n\n\t\ttimeout = undeploymentTimeout();\n\t\tappDeployer().undeploy(deploymentId);\n\t\tawait().pollInterval(Duration.ofMillis(timeout.pause))\n\t\t\t\t.atMost(Duration.ofMillis(timeout.totalTime))\n\t\t\t\t.untilAsserted(() -> {\n\t\t\tassertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.unknown);\n\t\t});\n\t}\n\n\t@Test\n\t// was triggered by GH-50 and subsequently GH-55\n\tpublic void testNoStdoutStderrOnInheritLoggingAndNoNPEOnGetAttributes() {\n\t\tMap<String, String> properties = new HashMap<>();\n\t\tAppDefinition definition = new AppDefinition(randomName(), properties);\n\t\tResource resource = testApplication();\n\t\tAppDeploymentRequest request = new AppDeploymentRequest(definition, resource, Collections.singletonMap(LocalDeployerProperties.INHERIT_LOGGING, \"true\"));\n\n\t\tAppDeployer deployer = appDeployer();\n\t\tString deploymentId = deployer.deploy(request);\n\t\tAppStatus appStatus = deployer.status(deploymentId);\n\t\tassertThat(appStatus.getInstances()).hasSizeGreaterThan(0);\n\t\tfor (Entry<String, AppInstanceStatus> instanceStatusEntry : appStatus.getInstances().entrySet()) {\n\t\t\tMap<String, String> attributes = instanceStatusEntry.getValue().getAttributes();\n\t\t\tassertThat(attributes).doesNotContainKey(\"stdout\");\n\t\t\tassertThat(attributes).doesNotContainKey(\"stderr\");\n\t\t}\n\t\tdeployer.undeploy(deploymentId);\n\t}\n\n\t@Test\n\tpublic void testInDebugModeWithSuspended() throws Exception {\n\t\tMap<String, String> properties = new HashMap<>();\n\t\tAppDefinition definition = new AppDefinition(randomName(), properties);\n\t\tResource resource = testApplication();\n\t\tMap<String, String> deploymentProperties = new HashMap<>();\n\t\tdeploymentProperties.put(LocalDeployerProperties.DEBUG_PORT, \"9999\");\n\t\tdeploymentProperties.put(LocalDeployerProperties.DEBUG_SUSPEND, \"y\");\n\t\tdeploymentProperties.put(LocalDeployerProperties.INHERIT_LOGGING, \"true\");\n\t\tAppDeploymentRequest request = new AppDeploymentRequest(definition, resource, deploymentProperties);\n\n\t\tAppDeployer deployer = appDeployer();\n\t\tString deploymentId = deployer.deploy(request);\n\t\tThread.sleep(5000);\n\t\tAppStatus appStatus = deployer.status(deploymentId);\n\t\tif (resource instanceof DockerResource) {\n\t\t\ttry {\n\t\t\t\tString containerId = getCommandOutput(\"docker ps -q --filter ancestor=\"+ TESTAPP_DOCKER_IMAGE_NAME);\n\t\t\t\tString logOutput = getCommandOutput(\"docker logs \"+ containerId);\n\t\t\t\tassertThat(logOutput).contains(\"Listening for transport dt_socket at address: 9999\");\n\t\t\t} catch (IOException e) {\n\t\t\t}\n\t\t}\n\t\telse {\n\t\t\tassertThat(appStatus.toString()).contains(\"deploying\");\n\t\t}\n\n\t\tdeployer.undeploy(deploymentId);\n\t}\n\n\t@Test\n\tpublic void testInDebugModeWithSuspendedUseCamelCase() throws Exception {\n\t\tMap<String, String> properties = new HashMap<>();\n\t\tAppDefinition definition = new AppDefinition(randomName(), properties);\n\t\tResource resource = testApplication();\n\t\tMap<String, String> deploymentProperties = new HashMap<>();\n\t\tdeploymentProperties.put(LocalDeployerProperties.PREFIX + \".debugPort\", \"8888\");\n\t\tdeploymentProperties.put(LocalDeployerProperties.PREFIX + \".debugSuspend\", \"y\");\n\t\tdeploymentProperties.put(LocalDeployerProperties.PREFIX + \".inheritLogging\", \"true\");\n\t\tAppDeploymentRequest request = new AppDeploymentRequest(definition, resource, deploymentProperties);\n\n\t\tAppDeployer deployer = appDeployer();\n\t\tString deploymentId = deployer.deploy(request);\n\t\tThread.sleep(5000);\n\t\tAppStatus appStatus = deployer.status(deploymentId);\n\t\tif (resource instanceof DockerResource) {\n\t\t\ttry {\n\t\t\t\tString containerId = getCommandOutput(\"docker ps -q --filter ancestor=\"+ TESTAPP_DOCKER_IMAGE_NAME);\n\t\t\t\tString logOutput = getCommandOutput(\"docker logs \"+ containerId);\n\t\t\t\tassertThat(logOutput).contains(\"Listening for transport dt_socket at address: 8888\");\n\t\t\t\tString containerPorts = getCommandOutputAll(\"docker port \"+ containerId);\n\t\t\t\tassertThat(containerPorts).contains(\"8888/tcp -> 0.0.0.0:8888\");\n\t\t\t} catch (IOException e) {\n\t\t\t}\n\t\t}\n\t\telse {\n\t\t\tassertThat(appStatus.toString()).contains(\"deploying\");\n\t\t}\n\n\t\tdeployer.undeploy(deploymentId);\n\t}\n\n\t@Test\n\tpublic void testUseDefaultDeployerProperties()  throws IOException {\n\n\t\tLocalDeployerProperties localDeployerProperties = new LocalDeployerProperties();\n\t\tPath tmpPath = new File(System.getProperty(\"java.io.tmpdir\")).toPath();\n\t\tPath customWorkDirRoot = tmpPath.resolve(\"test-default-directory\");\n\t\tlocalDeployerProperties.setWorkingDirectoriesRoot(customWorkDirRoot.toFile().getAbsolutePath());\n\n\t\t// Create a new LocalAppDeployer using a working directory that is different from the default value.\n\t\tAppDeployer appDeployer = new LocalAppDeployer(localDeployerProperties);\n\n\t\tList<Path> beforeDirs = getBeforePaths(customWorkDirRoot);\n\n\t\tMap<String, String> properties = new HashMap<>();\n\t\tproperties.put(\"server.port\", \"0\");\n\t\tAppDefinition definition = new AppDefinition(randomName(), properties);\n\t\tResource resource = testApplication();\n\t\tAppDeploymentRequest request = new AppDeploymentRequest(definition, resource);\n\n\n\t\t// Deploy\n\t\tString deploymentId = appDeployer.deploy(request);\n\t\tTimeout timeout = deploymentTimeout();\n\t\tawait().pollInterval(Duration.ofMillis(timeout.pause))\n\t\t\t\t.atMost(Duration.ofMillis(timeout.totalTime))\n\t\t\t\t.untilAsserted(() -> {\n\t\t\tassertThat(appDeployer.status(deploymentId).getState()).isEqualTo(DeploymentState.deployed);\n\t\t});\n\n\t\ttimeout = undeploymentTimeout();\n\t\t// Undeploy\n\t\tappDeployer.undeploy(deploymentId);\n\t\tawait().pollInterval(Duration.ofMillis(timeout.pause))\n\t\t\t\t.atMost(Duration.ofMillis(timeout.totalTime))\n\t\t\t\t.untilAsserted(() -> {\n\t\t\tassertThat(appDeployer.status(deploymentId).getState()).isEqualTo(DeploymentState.unknown);\n\t\t});\n\n\t\tList<Path> afterDirs = getAfterPaths(customWorkDirRoot);\n\t\tassertThat(afterDirs).as(\"Additional working directory not created\").hasSize(beforeDirs.size() + 1);\n\t}\n\n\t@Test\n\tpublic void testZeroPortReportsDeployed() throws IOException {\n\t\tMap<String, String> properties = new HashMap<>();\n\t\tproperties.put(\"server.port\", \"0\");\n\t\tPath tmpPath = new File(System.getProperty(\"java.io.tmpdir\")).toPath();\n\t\tPath customWorkDirRoot = tmpPath.resolve(\"spring-cloud-deployer-app-workdir\");\n\t\tMap<String, String> deploymentProperties = new HashMap<>();\n\t\tdeploymentProperties.put(LocalDeployerProperties.PREFIX + \".working-directories-root\", customWorkDirRoot.toFile().getAbsolutePath());\n\n\t\tAppDefinition definition = new AppDefinition(randomName(), properties);\n\t\tResource resource = testApplication();\n\t\tAppDeploymentRequest request = new AppDeploymentRequest(definition, resource, deploymentProperties);\n\n\t\tList<Path> beforeDirs = getBeforePaths(customWorkDirRoot);\n\n\t\tlogger.info(\"Deploying {}...\", request.getDefinition().getName());\n\t\tString deploymentId = appDeployer().deploy(request);\n\t\tTimeout timeout = deploymentTimeout();\n\t\tawait().pollInterval(Duration.ofMillis(timeout.pause))\n\t\t\t\t.atMost(Duration.ofMillis(timeout.totalTime))\n\t\t\t\t.untilAsserted(() -> {\n\t\t\tassertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.deployed);\n\t\t});\n\n\t\tlogger.info(\"Undeploying {}...\", deploymentId);\n\n\t\ttimeout = undeploymentTimeout();\n\t\tappDeployer().undeploy(deploymentId);\n\t\tawait().pollInterval(Duration.ofMillis(timeout.pause))\n\t\t\t\t.atMost(Duration.ofMillis(timeout.totalTime))\n\t\t\t\t.untilAsserted(() -> {\n\t\t\tassertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.unknown);\n\t\t});\n\n\n\t\tList<Path> afterDirs = getAfterPaths(customWorkDirRoot);\n\t\tassertThat(afterDirs.size()).as(\"Additional working directory not created\").isEqualTo(beforeDirs.size() + 1);\n\t}\n\n\t@Test\n\tpublic void testStartupProbeFail() {\n\t\tMap<String, String> deploymentProperties = new HashMap<>();\n\t\tdeploymentProperties.put(LocalDeployerProperties.PREFIX + \".startup-probe.path\", \"/fake\");\n\n\t\tAppDefinition definition = new AppDefinition(randomName(), null);\n\t\tResource resource = testApplication();\n\t\tAppDeploymentRequest request = new AppDeploymentRequest(definition, resource, deploymentProperties);\n\n\t\tlogger.info(\"Deploying {}...\", request.getDefinition().getName());\n\n\t\tString deploymentId = appDeployer().deploy(request);\n\n\t\tawait().atMost(Duration.ofSeconds(30)).until(() -> {\n\t\t\treturn appDeployer().status(deploymentId).getState() == DeploymentState.deploying;\n\t\t});\n\t\tawait().during(Duration.ofSeconds(10)).atMost(Duration.ofSeconds(12)).until(() -> {\n\t\t\treturn appDeployer().status(deploymentId).getState() == DeploymentState.deploying;\n\t\t});\n\n\t\tlogger.info(\"Undeploying {}...\", deploymentId);\n\n\t\tTimeout timeout = undeploymentTimeout();\n\t\tappDeployer().undeploy(deploymentId);\n\t\tawait().pollInterval(Duration.ofMillis(timeout.pause))\n\t\t\t\t.atMost(Duration.ofMillis(timeout.totalTime))\n\t\t\t\t.untilAsserted(() -> {\n\t\t\tassertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.unknown);\n\t\t});\n\t}\n\n\t@Test\n\tpublic void testStartupProbeSucceed() {\n\t\tMap<String, String> deploymentProperties = new HashMap<>();\n\t\tdeploymentProperties.put(LocalDeployerProperties.PREFIX + \".startup-probe.path\", \"/actuator/info\");\n\n\t\tAppDefinition definition = new AppDefinition(randomName(), null);\n\t\tResource resource = testApplication();\n\t\tAppDeploymentRequest request = new AppDeploymentRequest(definition, resource, deploymentProperties);\n\n\t\tlogger.info(\"Deploying {}...\", request.getDefinition().getName());\n\n\t\tString deploymentId = appDeployer().deploy(request);\n\n\t\tawait().atMost(Duration.ofSeconds(30)).until(() -> {\n\t\t\treturn appDeployer().status(deploymentId).getState() == DeploymentState.deploying;\n\t\t});\n\t\tawait().atMost(Duration.ofSeconds(12)).until(() -> {\n\t\t\treturn appDeployer().status(deploymentId).getState() == DeploymentState.deployed;\n\t\t});\n\n\t\tlogger.info(\"Undeploying {}...\", deploymentId);\n\n\t\tTimeout timeout = undeploymentTimeout();\n\t\tappDeployer().undeploy(deploymentId);\n\t\tawait().pollInterval(Duration.ofMillis(timeout.pause))\n\t\t\t\t.atMost(Duration.ofMillis(timeout.totalTime))\n\t\t\t\t.untilAsserted(() -> {\n\t\t\tassertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.unknown);\n\t\t});\n\t}\n\n\t@Test\n\tpublic void testHealthProbeFail() {\n\t\tMap<String, String> deploymentProperties = new HashMap<>();\n\t\tdeploymentProperties.put(LocalDeployerProperties.PREFIX + \".health-probe.path\", \"/fake\");\n\n\t\tAppDefinition definition = new AppDefinition(randomName(), null);\n\t\tResource resource = testApplication();\n\t\tAppDeploymentRequest request = new AppDeploymentRequest(definition, resource, deploymentProperties);\n\n\t\tlogger.info(\"Deploying {}...\", request.getDefinition().getName());\n\n\t\tString deploymentId = appDeployer().deploy(request);\n\n\t\tawait().during(Duration.ofSeconds(10)).atMost(Duration.ofSeconds(12)).until(() -> {\n\t\t\treturn appDeployer().status(deploymentId).getState() == DeploymentState.failed;\n\t\t});\n\n\t\tlogger.info(\"Undeploying {}...\", deploymentId);\n\n\t\tTimeout timeout = undeploymentTimeout();\n\t\tappDeployer().undeploy(deploymentId);\n\t\tawait().pollInterval(Duration.ofMillis(timeout.pause))\n\t\t\t\t.atMost(Duration.ofMillis(timeout.totalTime))\n\t\t\t\t.untilAsserted(() -> {\n\t\t\tassertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.unknown);\n\t\t});\n\t}\n\n\t@Test\n\tpublic void testHealthProbeSucceed() {\n\t\tMap<String, String> deploymentProperties = new HashMap<>();\n\t\tdeploymentProperties.put(LocalDeployerProperties.PREFIX + \".health-probe.path\", \"/actuator/info\");\n\n\t\tAppDefinition definition = new AppDefinition(randomName(), null);\n\t\tResource resource = testApplication();\n\t\tAppDeploymentRequest request = new AppDeploymentRequest(definition, resource, deploymentProperties);\n\n\t\tlogger.info(\"Deploying {}...\", request.getDefinition().getName());\n\n\t\tString deploymentId = appDeployer().deploy(request);\n\n\t\tawait().atMost(Duration.ofSeconds(30)).until(() -> {\n\t\t\treturn appDeployer().status(deploymentId).getState() == DeploymentState.deployed;\n\t\t});\n\n\t\tawait().during(Duration.ofSeconds(10)).atMost(Duration.ofSeconds(15)).until(() -> {\n\t\t\treturn appDeployer().status(deploymentId).getState() == DeploymentState.deployed;\n\t\t});\n\n\t\tlogger.info(\"Undeploying {}...\", deploymentId);\n\n\t\tTimeout timeout = undeploymentTimeout();\n\t\tappDeployer().undeploy(deploymentId);\n\t\tawait().pollInterval(Duration.ofMillis(timeout.pause))\n\t\t\t\t.atMost(Duration.ofMillis(timeout.totalTime))\n\t\t\t\t.untilAsserted(() -> {\n\t\t\tassertThat(appDeployer().status(deploymentId).getState()).isEqualTo(DeploymentState.unknown);\n\t\t});\n\t}\n\n\tprivate List<Path> getAfterPaths(Path customWorkDirRoot) throws IOException {\n\t\tif (!Files.exists(customWorkDirRoot)) {\n\t\t\treturn new ArrayList<>();\n\t\t}\n\t\treturn Files.walk(customWorkDirRoot, 1)\n\t\t\t\t\t.filter(path -> Files.isDirectory(path))\n\t\t\t\t\t.filter(path -> !path.getFileName().toString().startsWith(\".\"))\n\t\t\t\t\t.collect(Collectors.toList());\n\t}\n\n\tprivate List<Path> getBeforePaths(Path customWorkDirRoot) throws IOException {\n\t\tList<Path> beforeDirs = new ArrayList<>();\n\t\tbeforeDirs.add(customWorkDirRoot);\n\t\tif (Files.exists(customWorkDirRoot)) {\n\t\t\tbeforeDirs = Files.walk(customWorkDirRoot, 1)\n\t\t\t\t\t.filter(path -> Files.isDirectory(path))\n\t\t\t\t\t.collect(Collectors.toList());\n\t\t}\n\t\treturn beforeDirs;\n\t}\n\n\tprivate String getCommandOutput(String cmd) throws IOException {\n\t\tProcess process = Runtime.getRuntime().exec(cmd);\n\t\tBufferedReader stdInput = new BufferedReader(new InputStreamReader(process.getInputStream()));\n\t\treturn stdInput.lines().findFirst().get();\n\t}\n\n\tprivate String getCommandOutputAll(String cmd) throws IOException {\n\t\tProcess process = Runtime.getRuntime().exec(cmd);\n\t\tBufferedReader stdInput = new BufferedReader(new InputStreamReader(process.getInputStream()));\n\t\treturn stdInput.lines().collect(Collectors.joining());\n\t}\n\n\t@Configuration\n\t@EnableConfigurationProperties(LocalDeployerProperties.class)\n\tpublic static class Config {\n\n\t\t@Bean\n\t\tpublic AppDeployer appDeployer(LocalDeployerProperties properties) {\n\t\t\treturn new LocalAppDeployer(properties);\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "src/test/java/org/springframework/cloud/deployer/spi/local/LocalDeployerPropertiesTests.java",
    "content": "/*\n * Copyright 2019-2020 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage org.springframework.cloud.deployer.spi.local;\n\nimport java.nio.file.Paths;\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.condition.EnabledOnOs;\nimport org.junit.jupiter.api.condition.OS;\n\nimport org.springframework.boot.context.properties.EnableConfigurationProperties;\nimport org.springframework.boot.test.context.runner.ApplicationContextRunner;\nimport org.springframework.cloud.deployer.spi.app.AppAdmin;\nimport org.springframework.core.env.StandardEnvironment;\nimport org.springframework.core.env.SystemEnvironmentPropertySource;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\npublic class LocalDeployerPropertiesTests {\n\n\tprivate final ApplicationContextRunner contextRunner = new ApplicationContextRunner();\n\n\t@Test\n\t@EnabledOnOs(OS.LINUX)\n\tpublic void defaultNoPropertiesSet() {\n\t\tthis.contextRunner\n\t\t\t\t.withUserConfiguration(Config1.class)\n\t\t\t\t.run((context) -> {\n\t\t\t\t\tLocalDeployerProperties properties = context.getBean(LocalDeployerProperties.class);\n\t\t\t\t\tassertThat(properties.getDebugPort()).isNull();\n\t\t\t\t\tassertThat(properties.getDebugSuspend()).isEqualTo(LocalDeployerProperties.DebugSuspendType.y);\n\t\t\t\t\tassertThat(properties.isDeleteFilesOnExit()).isTrue();\n\t\t\t\t\tassertThat(properties.getEnvVarsToInherit()).containsExactly(\"TMP\", \"LANG\", \"LANGUAGE\", \"LC_.*\",\n\t\t\t\t\t\t\t\"PATH\", \"SPRING_APPLICATION_JSON\");\n\t\t\t\t\tassertThat(properties.isInheritLogging()).isFalse();\n\t\t\t\t\tassertThat(properties.getJavaCmd()).contains(\"java\");\n\t\t\t\t\tassertThat(properties.getJavaOpts()).isNull();\n\t\t\t\t\tassertThat(properties.getMaximumConcurrentTasks()).isEqualTo(20);\n\t\t\t\t\tassertThat(properties.getPortRange()).isNotNull();\n\t\t\t\t\tassertThat(properties.getPortRange().getLow()).isEqualTo(20000);\n\t\t\t\t\tassertThat(properties.getPortRange().getHigh()).isEqualTo(61000);\n\t\t\t\t\tassertThat(properties.getShutdownTimeout()).isEqualTo(30);\n\t\t\t\t\tassertThat(properties.isUseSpringApplicationJson()).isTrue();\n\t\t\t\t\tassertThat(properties.getDocker().getNetwork()).isEqualTo(\"bridge\");\n\t\t\t\t\tassertThat(properties.getStartupProbe()).isNotNull();\n\t\t\t\t\tassertThat(properties.getStartupProbe().getPath()).isNull();\n\t\t\t\t\tassertThat(properties.getHealthProbe()).isNotNull();\n\t\t\t\t\tassertThat(properties.getHealthProbe().getPath()).isNull();\n\t\t\t\t});\n\t}\n\n\t@Test\n\tpublic void setAllProperties() {\n\t\tthis.contextRunner\n\t\t\t\t.withInitializer(context -> {\n\t\t\t\t\tMap<String, Object> map = new HashMap<>();\n\t\t\t\t\tmap.put(\"spring.cloud.deployer.local.debug-port\", \"8888\");\n\t\t\t\t\tmap.put(\"spring.cloud.deployer.local.debug-suspend\", \"n\");\n\t\t\t\t\tmap.put(\"spring.cloud.deployer.local.delete-files-on-exit\", false);\n\t\t\t\t\tmap.put(\"spring.cloud.deployer.local.env-vars-to-inherit\", \"FOO,BAR\");\n\t\t\t\t\tmap.put(\"spring.cloud.deployer.local.inherit-logging\", true);\n\t\t\t\t\tmap.put(\"spring.cloud.deployer.local.java-cmd\", \"foobar1\");\n\t\t\t\t\tmap.put(\"spring.cloud.deployer.local.java-opts\", \"foobar2\");\n\t\t\t\t\tmap.put(\"spring.cloud.deployer.local.maximum-concurrent-tasks\", 1234);\n\t\t\t\t\tmap.put(\"spring.cloud.deployer.local.port-range.low\", 2345);\n\t\t\t\t\tmap.put(\"spring.cloud.deployer.local.port-range.high\", 2346);\n\t\t\t\t\tmap.put(\"spring.cloud.deployer.local.shutdown-timeout\", 3456);\n\t\t\t\t\tmap.put(\"spring.cloud.deployer.local.use-spring-application-json\", false);\n\t\t\t\t\tmap.put(\"spring.cloud.deployer.local.docker.network\", \"spring-cloud-dataflow-server_default\");\n\t\t\t\t\tmap.put(\"spring.cloud.deployer.local.docker.port-mappings\", \"9091:5678\");\n\t\t\t\t\tmap.put(\"spring.cloud.deployer.local.docker.volume-mounts\", \"/tmp:/opt\");\n\t\t\t\t\tmap.put(\"spring.cloud.deployer.local.startup-probe.path\", \"/path1\");\n\t\t\t\t\tmap.put(\"spring.cloud.deployer.local.health-probe.path\", \"/path2\");\n\t\t\t\t\tmap.put(\"spring.cloud.deployer.local.app-admin.user\",\"user\");\n\t\t\t\t\tmap.put(\"spring.cloud.deployer.local.app-admin.password\",\"password\");\n\n\t\t\t\t\tcontext.getEnvironment().getPropertySources().addLast(new SystemEnvironmentPropertySource(\n\t\t\t\t\t\t\tStandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, map));\n\t\t\t\t})\n\t\t\t\t.withUserConfiguration(Config1.class)\n\t\t\t\t.run((context) -> {\n\t\t\t\t\tLocalDeployerProperties properties = context.getBean(LocalDeployerProperties.class);\n\t\t\t\t\tassertThat(properties.getDebugPort()).isEqualTo(8888);\n\t\t\t\t\tassertThat(properties.getDebugSuspend()).isEqualTo(LocalDeployerProperties.DebugSuspendType.n);\n\t\t\t\t\tassertThat(properties.isDeleteFilesOnExit()).isFalse();\n\t\t\t\t\tassertThat(properties.getEnvVarsToInherit()).containsExactly(\"FOO\", \"BAR\");\n\t\t\t\t\tassertThat(properties.isInheritLogging()).isTrue();\n\t\t\t\t\tassertThat(properties.getJavaCmd()).contains(\"foobar1\");\n\t\t\t\t\tassertThat(properties.getJavaOpts()).contains(\"foobar2\");\n\t\t\t\t\tassertThat(properties.getMaximumConcurrentTasks()).isEqualTo(1234);\n\t\t\t\t\tassertThat(properties.getPortRange()).isNotNull();\n\t\t\t\t\tassertThat(properties.getPortRange().getLow()).isEqualTo(2345);\n\t\t\t\t\tassertThat(properties.getPortRange().getHigh()).isEqualTo(2346);\n\t\t\t\t\tassertThat(properties.getShutdownTimeout()).isEqualTo(3456);\n\t\t\t\t\tassertThat(properties.isUseSpringApplicationJson()).isFalse();\n\t\t\t\t\tassertThat(properties.getDocker().getNetwork()).isEqualTo(\"spring-cloud-dataflow-server_default\");\n\t\t\t\t\tassertThat(properties.getDocker().getPortMappings()).isEqualTo(\"9091:5678\");\n\t\t\t\t\tassertThat(properties.getDocker().getVolumeMounts()).isEqualTo(\"/tmp:/opt\");\n\t\t\t\t\tassertThat(properties.getStartupProbe().getPath()).isEqualTo(\"/path1\");\n\t\t\t\t\tassertThat(properties.getHealthProbe().getPath()).isEqualTo(\"/path2\");\n\t\t\t\t\tassertThat(properties.getAppAdmin().getUser()).isEqualTo(\"user\");\n\t\t\t\t\tassertThat(properties.getAppAdmin().getPassword()).isEqualTo(\"password\");\n\t\t\t\t});\n\t}\n\n\n\t@Test\n\tpublic void setAllPropertiesCamelCase() {\n\t\tthis.contextRunner\n\t\t\t\t.withInitializer(context -> {\n\t\t\t\t\tAppAdmin appAdmin = new AppAdmin();\n\t\t\t\t\tappAdmin.setUser(\"user\");\n\t\t\t\t\tappAdmin.setPassword(\"password\");\n\n\t\t\t\t\tMap<String, Object> map = new HashMap<>();\n\t\t\t\t\tmap.put(\"spring.cloud.deployer.local.debugPort\", \"8888\");\n\t\t\t\t\tmap.put(\"spring.cloud.deployer.local.debugSuspend\", \"n\");\n\t\t\t\t\tmap.put(\"spring.cloud.deployer.local.deleteFilesOnExit\", false);\n\t\t\t\t\tmap.put(\"spring.cloud.deployer.local.envVarsToInherit\", \"FOO,BAR\");\n\t\t\t\t\tmap.put(\"spring.cloud.deployer.local.inheritLogging\", true);\n\t\t\t\t\tmap.put(\"spring.cloud.deployer.local.javaCmd\", \"foobar1\");\n\t\t\t\t\tmap.put(\"spring.cloud.deployer.local.javaOpts\", \"foobar2\");\n\t\t\t\t\tmap.put(\"spring.cloud.deployer.local.maximumConcurrentTasks\", 1234);\n\t\t\t\t\tmap.put(\"spring.cloud.deployer.local.portRange.low\", 2345);\n\t\t\t\t\tmap.put(\"spring.cloud.deployer.local.portRange.high\", 2346);\n\t\t\t\t\tmap.put(\"spring.cloud.deployer.local.shutdownTimeout\", 3456);\n\t\t\t\t\tmap.put(\"spring.cloud.deployer.local.useSpringApplicationJson\", false);\n\t\t\t\t\tmap.put(\"spring.cloud.deployer.local.docker.network\", \"spring-cloud-dataflow-server_default\");\n\t\t\t\t\tmap.put(\"spring.cloud.deployer.local.docker.portMappings\", \"9091:5678\");\n\t\t\t\t\tmap.put(\"spring.cloud.deployer.local.docker.volumeMounts\", \"/tmp:/opt\");\n\t\t\t\t\tmap.put(\"spring.cloud.deployer.local.appAdmin.user\",\"user\");\n\t\t\t\t\tmap.put(\"spring.cloud.deployer.local.appAdmin.password\",\"password\");\n\n\t\t\t\t\tcontext.getEnvironment().getPropertySources().addLast(new SystemEnvironmentPropertySource(\n\t\t\t\t\t\t\tStandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, map));\n\t\t\t\t})\n\t\t\t\t.withUserConfiguration(Config1.class)\n\t\t\t\t.run((context) -> {\n\t\t\t\t\tLocalDeployerProperties properties = context.getBean(LocalDeployerProperties.class);\n\t\t\t\t\tassertThat(properties.getDebugPort()).isEqualTo(8888);\n\t\t\t\t\tassertThat(properties.getDebugSuspend()).isEqualTo(LocalDeployerProperties.DebugSuspendType.n);\n\t\t\t\t\tassertThat(properties.isDeleteFilesOnExit()).isFalse();\n\t\t\t\t\tassertThat(properties.getEnvVarsToInherit()).containsExactly(\"FOO\", \"BAR\");\n\t\t\t\t\tassertThat(properties.isInheritLogging()).isTrue();\n\t\t\t\t\tassertThat(properties.getJavaCmd()).contains(\"foobar1\");\n\t\t\t\t\tassertThat(properties.getJavaOpts()).contains(\"foobar2\");\n\t\t\t\t\tassertThat(properties.getMaximumConcurrentTasks()).isEqualTo(1234);\n\t\t\t\t\tassertThat(properties.getPortRange()).isNotNull();\n\t\t\t\t\tassertThat(properties.getPortRange().getLow()).isEqualTo(2345);\n\t\t\t\t\tassertThat(properties.getPortRange().getHigh()).isEqualTo(2346);\n\t\t\t\t\tassertThat(properties.getShutdownTimeout()).isEqualTo(3456);\n\t\t\t\t\tassertThat(properties.isUseSpringApplicationJson()).isFalse();\n\t\t\t\t\tassertThat(properties.getDocker().getNetwork()).isEqualTo(\"spring-cloud-dataflow-server_default\");\n\t\t\t\t\tassertThat(properties.getDocker().getPortMappings()).isEqualTo(\"9091:5678\");\n\t\t\t\t\tassertThat(properties.getDocker().getVolumeMounts()).isEqualTo(\"/tmp:/opt\");\n\t\t\t\t\tassertThat(properties.getAppAdmin().getUser()).isEqualTo(\"user\");\n\t\t\t\t\tassertThat(properties.getAppAdmin().getPassword()).isEqualTo(\"password\");\n\t\t\t\t});\n\t}\n\n\t@Test\n\t@EnabledOnOs(OS.WINDOWS)\n\tpublic void testOnWindows() {\n\t\tthis.contextRunner\n\t\t\t\t.withInitializer(context -> {\n\t\t\t\t\tMap<String, Object> map = new HashMap<>();\n\t\t\t\t\tmap.put(\"spring.cloud.deployer.local.working-directories-root\", \"file:/C:/tmp\");\n\t\t\t\t\tcontext.getEnvironment().getPropertySources().addLast(new SystemEnvironmentPropertySource(\n\t\t\t\t\t\t\tStandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, map));\n\t\t\t\t})\n\n\t\t\t\t.withUserConfiguration(Config1.class)\n\t\t\t\t.run((context) -> {\n\t\t\t\t\tLocalDeployerProperties properties = context.getBean(LocalDeployerProperties.class);\n\t\t\t\t\tassertThat(properties.getWorkingDirectoriesRoot()).isNotNull();\n\t\t\t\t\tassertThat(properties.getWorkingDirectoriesRoot().toString()).isEqualTo(\"C:\\\\tmp\");\n\t\t\t\t});\n\t}\n\n\t@Test\n\t@EnabledOnOs(OS.LINUX)\n\tpublic void testOnLinux() {\n\t\tthis.contextRunner\n\t\t\t\t.withInitializer(context -> {\n\t\t\t\t\tMap<String, Object> map = new HashMap<>();\n\t\t\t\t\tmap.put(\"spring.cloud.deployer.local.working-directories-root\", \"/tmp\");\n\n\t\t\t\t\tcontext.getEnvironment().getPropertySources().addLast(new SystemEnvironmentPropertySource(\n\t\t\t\t\t\t\tStandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, map));\n\t\t\t\t})\n\t\t\t\t.withUserConfiguration(Config1.class)\n\t\t\t\t.run((context) -> {\n\t\t\t\t\tLocalDeployerProperties properties = context.getBean(LocalDeployerProperties.class);\n\t\t\t\t\tassertThat(properties.getWorkingDirectoriesRoot()).isNotNull();\n\t\t\t\t\tassertThat(properties.getWorkingDirectoriesRoot().toString()).isEqualTo(\"/tmp\");\n\t\t\t\t});\n\t}\n\n\t@Test\n\t@EnabledOnOs(OS.LINUX)\n\tpublic void testCopyProperties() {\n\t\tLocalDeployerProperties from = new LocalDeployerProperties();\n\t\tLocalDeployerProperties to = new LocalDeployerProperties(from);\n\t\tassertThat(from).isEqualTo(to);\n\n\t\tfrom = new LocalDeployerProperties();\n\t\tfrom.setDebugPort(1234);\n\t\tfrom.setDebugSuspend(LocalDeployerProperties.DebugSuspendType.y);\n\t\tfrom.getDocker().setNetwork(\"network\");\n\t\tfrom.setDeleteFilesOnExit(true);\n\t\tfrom.setEnvVarsToInherit(new String[] { \"foobar\" });\n\t\tfrom.setInheritLogging(false);\n\t\tfrom.setJavaCmd(\"javaCmd\");\n\t\tfrom.setJavaOpts(\"javaOpts\");\n\t\tfrom.setMaximumConcurrentTasks(234);\n\t\tfrom.getPortRange().setHigh(2345);\n\t\tfrom.getPortRange().setLow(2344);\n\t\tfrom.setShutdownTimeout(345);\n\t\tfrom.setUseSpringApplicationJson(false);\n\t\tfrom.setWorkingDirectoriesRoot(Paths.get(\"/first\"));\n\t\tto = new LocalDeployerProperties(from);\n\t\tassertThat(from).isEqualTo(to);\n\t}\n\n\t@EnableConfigurationProperties({ LocalDeployerProperties.class })\n\tprivate static class Config1 {\n\t}\n}\n"
  },
  {
    "path": "src/test/java/org/springframework/cloud/deployer/spi/local/LocalDeployerSupportTests.java",
    "content": "/*\n * Copyright 2017-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.local;\n\nimport java.net.MalformedURLException;\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\n\nimport org.springframework.cloud.deployer.spi.core.AppDefinition;\nimport org.springframework.cloud.deployer.spi.core.AppDeploymentRequest;\nimport org.springframework.core.io.ClassPathResource;\nimport org.springframework.core.io.Resource;\nimport org.springframework.test.util.ReflectionTestUtils;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * Tests for the AbstractLocalDeployerSupport\n *\n * @author Thomas Risberg\n */\npublic class LocalDeployerSupportTests {\n\n\tprivate LocalDeployerProperties localDeployerProperties;\n\tprivate AbstractLocalDeployerSupport localDeployerSupport;\n\n\t@BeforeEach\n\tpublic void setUp() {\n\t\tlocalDeployerProperties = new LocalDeployerProperties();\n\t\tlocalDeployerSupport = new AbstractLocalDeployerSupport(this.localDeployerProperties) {};\n\t}\n\n\t@Test\n\tpublic void testAppPropsAsSAJ() throws MalformedURLException {\n\t\tAppDeploymentRequest appDeploymentRequest = createAppDeploymentRequest();\n\n\t\tHashMap<String, String> envVarsToUse = new HashMap<>(appDeploymentRequest.getDefinition().getProperties());\n\t\tMap<String, String> environmentVariables = localDeployerSupport.formatApplicationProperties(appDeploymentRequest,\n\t\t\t\tenvVarsToUse);\n\n\t\tassertThat(environmentVariables).hasSize(1);\n\t\tassertThat(environmentVariables).containsEntry(AbstractLocalDeployerSupport.SPRING_APPLICATION_JSON,\n\t\t\t\t\"{\\\"test.foo\\\":\\\"foo\\\",\\\"test.bar\\\":\\\"bar\\\"}\");\n\t}\n\n\t@Test\n\tpublic void testCalcServerPort() throws MalformedURLException {\n\t\tMap<String, String> applicationProperties = new HashMap<>();\n\t\tMap<String, String> deploymentPropertites = new HashMap<>();\n\t\tList<String> commandLineArgs = new ArrayList<>();\n\n\t\t// test adding to application properties\n\t\tapplicationProperties.put(\"server.port\", \"9292\");\n\t\tAppDefinition definition = new AppDefinition(\"randomApp\", applicationProperties);\n\n\t\tAppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, testResource(),\n\t\t\t\tdeploymentPropertites, commandLineArgs);\n\n\t\tint portToUse = localDeployerSupport.calcServerPort(appDeploymentRequest, false, new HashMap<>());\n\t\tassertThat(portToUse).isEqualTo(9292);\n\n\t\t// test adding to command line args, which has higher precedence than application properties\n\t\tcommandLineArgs.add(LocalTaskLauncher.SERVER_PORT_KEY_COMMAND_LINE_ARG  + 9191);\n\t\tappDeploymentRequest = new AppDeploymentRequest(definition, testResource(),\n\t\t\t\tdeploymentPropertites, commandLineArgs);\n\n\t\tportToUse = localDeployerSupport.calcServerPort(appDeploymentRequest, false, new HashMap<>());\n\t\tassertThat(portToUse).isEqualTo(9191);\n\n\t\t// test using dynamic port assignment\n\t\tportToUse = localDeployerSupport.calcServerPort(appDeploymentRequest, true, new HashMap<>());\n\t\tassertThat(portToUse).isNotEqualTo(9191);\n\t\tassertThat(portToUse).isNotEqualTo(9292);\n\t}\n\n\t@Test\n\tpublic void testShutdownPropertyConfiguresRequestFactory() throws Exception {\n\t\tLocalDeployerProperties properties = new LocalDeployerProperties();\n\t\tproperties.setShutdownTimeout(1);\n\t\tAbstractLocalDeployerSupport abstractLocalDeployerSupport = new AbstractLocalDeployerSupport(properties) {};\n\t\tObject restTemplate = ReflectionTestUtils.getField(abstractLocalDeployerSupport, \"restTemplate\");\n\t\tObject requestFactory = ReflectionTestUtils.getField(restTemplate, \"requestFactory\");\n\t\tObject connectTimeout = ReflectionTestUtils.getField(requestFactory, \"connectTimeout\");\n\t\tObject readTimeout = ReflectionTestUtils.getField(requestFactory, \"readTimeout\");\n\t\tassertThat(connectTimeout).isEqualTo(1000);\n\t\tassertThat(readTimeout).isEqualTo(1000);\n\t}\n\n\t@Test\n\tpublic void testShutdownPropertyNotConfiguresRequestFactory() throws Exception {\n\t\tLocalDeployerProperties properties = new LocalDeployerProperties();\n\t\tproperties.setShutdownTimeout(-1);\n\t\tAbstractLocalDeployerSupport abstractLocalDeployerSupport = new AbstractLocalDeployerSupport(properties) {};\n\t\tObject restTemplate = ReflectionTestUtils.getField(abstractLocalDeployerSupport, \"restTemplate\");\n\t\tObject requestFactory = ReflectionTestUtils.getField(restTemplate, \"requestFactory\");\n\t\tObject connectTimeout =  ReflectionTestUtils.getField(requestFactory,\"connectTimeout\");\n\t\tObject readTimeout = ReflectionTestUtils.getField(requestFactory, \"readTimeout\");\n\t\tassertThat(connectTimeout).isEqualTo(-1);\n\t\tassertThat(readTimeout).isEqualTo(-1);\n\t}\n\n\tprotected AppDeploymentRequest createAppDeploymentRequest() throws MalformedURLException {\n\t\treturn createAppDeploymentRequest(new HashMap<>());\n\t}\n\n\tprotected AppDeploymentRequest createAppDeploymentRequest(Map<String, String> depProps) throws MalformedURLException {\n\t\tMap<String, String> appProperties = new HashMap<>();\n\t\tappProperties.put(\"test.foo\", \"foo\");\n\t\tappProperties.put(\"test.bar\", \"bar\");\n\t\tAppDefinition definition = new AppDefinition(\"randomApp\", appProperties);\n\t\treturn new AppDeploymentRequest(definition, testResource(), depProps);\n\t}\n\n\n\tprotected Resource testResource() {\n\t\treturn new ClassPathResource(\"testResource.txt\");\n\t}\n\n}\n"
  },
  {
    "path": "src/test/java/org/springframework/cloud/deployer/spi/local/LocalTaskLauncherIntegrationTests.java",
    "content": "/*\n * Copyright 2016-2022 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.cloud.deployer.spi.local;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.nio.ByteBuffer;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.time.Duration;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.UUID;\nimport java.util.stream.Collectors;\n\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.junit.jupiter.api.io.TempDir;\n\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.boot.context.properties.EnableConfigurationProperties;\nimport org.springframework.boot.test.context.SpringBootTest;\nimport org.springframework.boot.test.system.CapturedOutput;\nimport org.springframework.boot.test.system.OutputCaptureExtension;\nimport org.springframework.cloud.deployer.resource.docker.DockerResource;\nimport org.springframework.cloud.deployer.spi.core.AppDefinition;\nimport org.springframework.cloud.deployer.spi.core.AppDeploymentRequest;\nimport org.springframework.cloud.deployer.spi.local.LocalTaskLauncherIntegrationTests.Config;\nimport org.springframework.cloud.deployer.spi.task.LaunchState;\nimport org.springframework.cloud.deployer.spi.task.TaskLauncher;\nimport org.springframework.cloud.deployer.spi.test.AbstractIntegrationTests;\nimport org.springframework.cloud.deployer.spi.test.AbstractTaskLauncherIntegrationJUnit5Tests;\nimport org.springframework.cloud.deployer.spi.test.Timeout;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.core.io.Resource;\nimport org.springframework.util.FileSystemUtils;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.awaitility.Awaitility.await;\n\n/**\n * Integration tests for {@link LocalTaskLauncher}.\n *\n * Now supports running with Docker images for tests, just set this env var:\n *\n *   SPRING_CLOUD_DEPLOYER_SPI_TEST_USE_DOCKER=true\n *\n * @author Eric Bottard\n * @author Janne Valkealahti\n * @author David Turanski\n * @author Glenn Renfro\n * @author Ilayaperumal Gopinathan\n * @author Ben Blinebury\n *\n */\n@SpringBootTest(classes = {Config.class, AbstractIntegrationTests.Config.class}, value = {\n\t\t\"maven.remoteRepositories.springRepo.url=https://repo.spring.io/snapshot\" })\n@ExtendWith(OutputCaptureExtension.class)\npublic class LocalTaskLauncherIntegrationTests extends AbstractTaskLauncherIntegrationJUnit5Tests {\n\tprivate static final String SYMBOLIC_LINK = \"symbolic_link.txt\";\n\n\t@Autowired\n\tprivate TaskLauncher taskLauncher;\n\n\t@Value(\"${spring-cloud-deployer-spi-test-use-docker:false}\")\n\tprivate boolean useDocker;\n\n\t@Override\n\tprotected TaskLauncher provideTaskLauncher() {\n\t\treturn taskLauncher;\n\t}\n\n\t@Override\n\tprotected Resource testApplication() {\n\t\tif (useDocker) {\n\t\t\tlogger.info(\"Using Docker image for tests\");\n\t\t\treturn new DockerResource(\"springcloud/spring-cloud-deployer-spi-test-app:latest\");\n\t\t}\n\t\treturn super.testApplication();\n\t}\n\n\t@Override\n\tprotected String randomName() {\n\t\tif (LocalDeployerUtils.isWindows()) {\n\t\t\t// tweak random dir name on win to be shorter\n\t\t\tString uuid = UUID.randomUUID().toString();\n\t\t\tlong l = ByteBuffer.wrap(uuid.toString().getBytes()).getLong();\n\t\t\treturn testName + Long.toString(l, Character.MAX_RADIX);\n\t\t}\n\t\telse {\n\t\t\treturn super.randomName();\n\t\t}\n\t}\n\n\t@Test\n\tpublic void testPassingServerPortViaCommandLineArgs(CapturedOutput output){\n\t\tMap<String, String> appProperties = new HashMap<>();\n\t\tappProperties.put(\"killDelay\", \"0\");\n\t\tappProperties.put(\"exitCode\", \"0\");\n\n\t\tAppDefinition definition = new AppDefinition(this.randomName(), appProperties);\n\n\t\tbasicLaunchAndValidation(definition, null);\n\t\tassertThat(output).contains(\"Logs will be in\");\n\t}\n\n\t@Test\n\tpublic void testBasicLaunchWithSymbolicLink(CapturedOutput output) throws Exception {\n\t\tMap<String, String> appProperties = new HashMap<>();\n\t\tappProperties.put(\"killDelay\", \"0\");\n\t\tappProperties.put(\"exitCode\", \"0\");\n\n\t\tPath symlink = createSymbolicLink();\n\n\t\tAppDefinition definition = new AppDefinition(this.randomName(), appProperties);\n\t\tMap<String, String> deploymentProperties = Collections.singletonMap(\"spring.cloud.deployer.local.workingDirectoriesRoot\", SYMBOLIC_LINK);\n\n\t\tbasicLaunchAndValidation(definition, deploymentProperties);\n\n\t\tassertThat(output).contains(\"Logs will be in\");\n\n\t\tFiles.delete(Paths.get(symlink.toString()));\n\t}\n\n\t@TempDir\n\tprivate File tempDirectory;\n\n\tprivate Path createSymbolicLink() throws IOException {\n\t\tFile testFile = new File(tempDirectory, \"testFile.txt\");\n\n\t\tPath target = testFile.toPath();\n\n\t\tPath link = Paths.get(SYMBOLIC_LINK);\n\n\t\tif (Files.exists(link)) {\n\t\t\tFiles.delete(link);\n\t\t}\n\n\t\treturn Files.createSymbolicLink(link, target);\n\t}\n\n\t@Test\n\tpublic void testInheritLoggingAndWorkDir(CapturedOutput output) throws IOException {\n\n\t\tMap<String, String> appProperties = new HashMap<>();\n\t\tappProperties.put(\"killDelay\", \"0\");\n\t\tappProperties.put(\"exitCode\", \"0\");\n\n\t\tMap<String, String> deploymentProperties = new HashMap<>();\n\t\tdeploymentProperties.put(LocalDeployerProperties.INHERIT_LOGGING, \"true\");\n\t\tPath tmpPath = new File(System.getProperty(\"java.io.tmpdir\")).toPath();\n\t\tPath customWorkDirRoot = tmpPath.resolve(\"spring-cloud-deployer-task-workdir\");\n\t\tdeploymentProperties.put(LocalDeployerProperties.PREFIX + \".working-directories-root\", customWorkDirRoot.toFile().getAbsolutePath());\n\n\t\tAppDefinition definition = new AppDefinition(this.randomName(), appProperties);\n\n\t\tList<Path> beforeDirs = new ArrayList<>();\n\t\tbeforeDirs.add(customWorkDirRoot);\n\t\tif (Files.exists(customWorkDirRoot)) {\n\t\t\tbeforeDirs = Files.walk(customWorkDirRoot, 1)\n\t\t\t\t\t.filter(path -> Files.isDirectory(path))\n\t\t\t\t\t.collect(Collectors.toList());\n\t\t}\n\n\t\tbasicLaunchAndValidation(definition, deploymentProperties);\n\t\tassertThat(output).contains(\"Logs will be inherited.\");\n\n\t\tList<Path> afterDirs = Files.walk(customWorkDirRoot, 1)\n\t\t\t\t.filter(path -> Files.isDirectory(path))\n\t\t\t\t.collect(Collectors.toList());\n\t\tassertThat(afterDirs).as(\"Additional working directory not created\").hasSize(beforeDirs.size() + 1);\n\n\t\t// clean up if test passed\n\t\tFileSystemUtils.deleteRecursively(customWorkDirRoot);\n\t}\n\n\t@Test\n\tpublic void testAppLogRetrieval() {\n\t\tMap<String, String> appProperties = new HashMap<>();\n\t\tappProperties.put(\"killDelay\", \"0\");\n\t\tappProperties.put(\"exitCode\", \"0\");\n\t\tAppDefinition definition = new AppDefinition(randomName(), appProperties);\n\t\tResource resource = testApplication();\n\t\tAppDeploymentRequest request = new AppDeploymentRequest(definition, resource);\n\n\t\tString launchId1 = taskLauncher().launch(request);\n\n\t\tTimeout timeout = deploymentTimeout();\n\n\t\tawait().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> {\n\t\t\tassertThat(taskLauncher().status(launchId1).getState()).isEqualTo(LaunchState.complete);\n        });\n\n\t\tString logContent = taskLauncher().getLog(launchId1);\n\t\tassertThat(logContent).contains(\"Starting DeployerIntegrationTestApplication\");\n\t}\n\n\t@Test\n\tpublic void testDeleteHistoryOnReLaunch() {\n\t\tMap<String, String> appProperties = new HashMap<>();\n\t\tappProperties.put(\"killDelay\", \"0\");\n\t\tappProperties.put(\"exitCode\", \"0\");\n\t\tAppDefinition definition = new AppDefinition(randomName(), appProperties);\n\t\tResource resource = testApplication();\n\t\tAppDeploymentRequest request = new AppDeploymentRequest(definition, resource);\n\n\t\tString launchId1 = taskLauncher().launch(request);\n\n\t\tTimeout timeout = deploymentTimeout();\n\n\t\tawait().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> {\n\t\t\tassertThat(taskLauncher().status(launchId1).getState()).isEqualTo(LaunchState.complete);\n        });\n\n\t\tString launchId2 = taskLauncher().launch(request);\n\n\t\tassertThat(launchId2).isNotEqualTo(launchId1);\n\n\t\ttimeout = deploymentTimeout();\n\n\t\tawait().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> {\n\t\t\tassertThat(taskLauncher().status(launchId2).getState()).isEqualTo(LaunchState.complete);\n        });\n\n\t\tawait().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> {\n\t\t\tassertThat(taskLauncher().status(launchId1).getState()).isEqualTo(LaunchState.unknown);\n        });\n\n\t\tString launchId3 = taskLauncher().launch(request);\n\n\t\tassertThat(launchId3).isNotEqualTo(launchId1);\n\t\tassertThat(launchId3).isNotEqualTo(launchId2);\n\n\t\ttimeout = deploymentTimeout();\n\n\t\tawait().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> {\n\t\t\tassertThat(taskLauncher().status(launchId3).getState()).isEqualTo(LaunchState.complete);\n        });\n\n\t\tawait().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> {\n\t\t\tassertThat(taskLauncher().status(launchId1).getState()).isEqualTo(LaunchState.unknown);\n        });\n\n\t\tawait().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> {\n\t\t\tassertThat(taskLauncher().status(launchId2).getState()).isEqualTo(LaunchState.unknown);\n        });\n\n\t\ttaskLauncher().destroy(definition.getName());\n\t}\n\n\tprivate void basicLaunchAndValidation(AppDefinition definition, Map<String, String> deploymentProperties) {\n\t\tList<String> commandLineArgs = new ArrayList<>(1);\n\t\t// Test to ensure no issues parsing server.port command line arg.\n\t\tcommandLineArgs.add(LocalTaskLauncher.SERVER_PORT_KEY_COMMAND_LINE_ARG + DeployerSocketUtils.findAvailableTcpPort(LocalTaskLauncher.DEFAULT_SERVER_PORT));\n\n\t\tAppDeploymentRequest request = new AppDeploymentRequest(definition, this.testApplication(), deploymentProperties, commandLineArgs);\n\n\n\t\tthis.logger.info(\"Launching {}...\", request.getDefinition().getName());\n\n\t\tString launchId = this.taskLauncher().launch(request);\n\t\tTimeout timeout = this.deploymentTimeout();\n\t\tawait().pollInterval(Duration.ofMillis(1))\n                .atMost(Duration.ofSeconds(30))\n                .untilAsserted(() -> {\n\t\t\tassertThat(taskLauncher.getRunningTaskExecutionCount()).isEqualTo(1);\n        });\n\n\t\tawait().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> {\n\t\t\tassertThat(taskLauncher().status(launchId).getState()).isEqualTo(LaunchState.complete);\n        });\n\n\t\tthis.taskLauncher().destroy(definition.getName());\n\n\t\tawait().pollInterval(Duration.ofMillis(timeout.pause))\n                .atMost(Duration.ofMillis(timeout.totalTime))\n                .untilAsserted(() -> {\n\t\t\tassertThat(taskLauncher.getRunningTaskExecutionCount()).isEqualTo(0);\n        });\n\t}\n\n\t@Configuration\n\t@EnableConfigurationProperties(LocalDeployerProperties.class)\n\tpublic static class Config {\n\n\t\t@Bean\n\t\tpublic TaskLauncher taskLauncher(LocalDeployerProperties properties) {\n\t\t\treturn new LocalTaskLauncher(properties);\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "src/test/java/org/springframework/cloud/deployer/spi/local/RandomPortRangeContextTests.java",
    "content": "/*\n * Copyright 2018-2021 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage org.springframework.cloud.deployer.spi.local;\n\nimport org.junit.jupiter.api.Test;\n\nimport org.springframework.boot.autoconfigure.AutoConfigurations;\nimport org.springframework.boot.test.context.runner.ApplicationContextRunner;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * @author Christian Tzolov\n */\npublic class RandomPortRangeContextTests {\n\n\tprivate final ApplicationContextRunner contextRunner = new ApplicationContextRunner()\n\t\t\t.withConfiguration(AutoConfigurations.of(LocalDeployerAutoConfiguration.class));\n\n\t@Test\n\tpublic void defaultProtRangeProperties() {\n\t\tthis.contextRunner\n\t\t\t\t.withUserConfiguration(LocalDeployerAutoConfiguration.class)\n\t\t\t\t.run((context) -> {\n\t\t\t\t\tassertThat(context).hasSingleBean(LocalDeployerProperties.class);\n\t\t\t\t\tassertThat(context).hasSingleBean(LocalDeployerAutoConfiguration.class);\n\t\t\t\t\tassertThat(context).getBean(LocalDeployerProperties.class)\n\t\t\t\t\t\t\t.hasFieldOrPropertyWithValue(\"portRange.low\", 20000);\n\t\t\t\t\tassertThat(context).getBean(LocalDeployerProperties.class)\n\t\t\t\t\t\t\t.hasFieldOrPropertyWithValue(\"portRange.high\", 61000);\n\t\t\t\t});\n\t}\n\n\t@Test\n\tpublic void presetProtRangeProperties() {\n\t\tthis.contextRunner\n\t\t\t\t.withUserConfiguration(LocalDeployerAutoConfiguration.class)\n\t\t\t\t.withPropertyValues(\"spring.cloud.deployer.local.portRange.low=20001\", \"spring.cloud.deployer.local.portRange.high=20003\")\n\t\t\t\t.run((context) -> {\n\t\t\t\t\tassertThat(context).hasSingleBean(LocalDeployerProperties.class);\n\t\t\t\t\tassertThat(context).hasSingleBean(LocalDeployerAutoConfiguration.class);\n\t\t\t\t\tassertThat(context).getBean(LocalDeployerProperties.class)\n\t\t\t\t\t\t\t.hasFieldOrPropertyWithValue(\"portRange.low\", 20001);\n\t\t\t\t\tassertThat(context).getBean(LocalDeployerProperties.class)\n\t\t\t\t\t\t\t.hasFieldOrPropertyWithValue(\"portRange.high\", 20003);\n\t\t\t\t});\n\t}\n}\n"
  },
  {
    "path": "src/test/java/org/springframework/cloud/deployer/spi/local/RandomPortRangeTests.java",
    "content": "/*\n * Copyright 2018 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage org.springframework.cloud.deployer.spi.local;\n\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.mockito.Mock;\nimport org.mockito.junit.jupiter.MockitoExtension;\n\nimport org.springframework.cloud.deployer.resource.docker.DockerResource;\nimport org.springframework.cloud.deployer.spi.core.AppDeploymentRequest;\nimport org.springframework.core.io.ClassPathResource;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.mockito.Mockito.when;\n\n/**\n * @author Christian Tzolov\n */\n@ExtendWith(MockitoExtension.class)\npublic class RandomPortRangeTests {\n\n\tprivate AbstractLocalDeployerSupport localDeployerSupport;\n\n\t@Mock\n\tAppDeploymentRequest appDeploymentRequest;\n\n\t@BeforeEach\n\tpublic void setUp() {\n\t\tLocalDeployerProperties properties = new LocalDeployerProperties();\n\t\tproperties.getPortRange().setLow(30001);\n\t\tproperties.getPortRange().setHigh(30213);\n\n\t\tproperties.getDocker().getPortRange().setLow(40001);\n\t\tproperties.getDocker().getPortRange().setHigh(40213);\n\n\t\tlocalDeployerSupport = new AbstractLocalDeployerSupport(properties) {};\n\n\t}\n\n\t@Test\n\tpublic void portTests() {\n\t\twhen(appDeploymentRequest.getResource()).thenReturn(new ClassPathResource(\"\"));\n\t\tfor (int i = 0; i < 30; i++) {\n\t\t\tint port = localDeployerSupport.getRandomPort(appDeploymentRequest);\n\t\t\tassertThat(port).isGreaterThanOrEqualTo(30001);\n\t\t\tassertThat(port).isLessThanOrEqualTo(30213 + 5);\n\t\t}\n\t}\n\n\t@Test\n\tpublic void portTests2() {\n\t\twhen(appDeploymentRequest.getResource()).thenReturn(new DockerResource(\"/test:test\"));\n\t\tfor (int i = 0; i < 5; i++) {\n\t\t\tint port = localDeployerSupport.getRandomPort(appDeploymentRequest);\n\t\t\tassertThat(port).isGreaterThanOrEqualTo(40001);\n\t\t\tassertThat(port).isLessThanOrEqualTo(40213 + 5);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "src/test/java/org/springframework/cloud/deployer/spi/local/RandomPortTests.java",
    "content": "/*\n * Copyright 2018 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage org.springframework.cloud.deployer.spi.local;\n\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.mockito.Mock;\nimport org.mockito.junit.jupiter.MockitoExtension;\n\nimport org.springframework.cloud.deployer.spi.core.AppDeploymentRequest;\nimport org.springframework.core.io.ClassPathResource;\n\nimport static org.mockito.Mockito.when;\n\n/**\n * @author Mark Pollack\n */\n@ExtendWith(MockitoExtension.class)\npublic class RandomPortTests {\n\n\tprivate AbstractLocalDeployerSupport localDeployerSupport;\n\n\t@Mock\n\tAppDeploymentRequest appDeploymentRequest;\n\n\t@BeforeEach\n\tpublic void setUp() {\n\t\tlocalDeployerSupport = new AbstractLocalDeployerSupport(new LocalDeployerProperties()) {};\n\t\twhen(appDeploymentRequest.getResource()).thenReturn(new ClassPathResource(\"\"));\n\t}\n\n\t@Test\n\tpublic void portTests() {\n\t\t//No exception should be thrown\n\t\tfor (int i = 0; i < 100; i++) {\n\t\t\tlocalDeployerSupport.getRandomPort(appDeploymentRequest);\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "src/test/resources/dataflow-server-podsecuritycontext.yml",
    "content": "# spring.cloud.deployer.kubernetes.podSecurityContext:\npodSecurityContext:\n  fsGroup: 65534\n  fsGroupChangePolicy: Always\n  runAsUser: 65534\n  runAsGroup: 65534\n  runAsNonRoot: true\n  seLinuxOptions:\n    level: \"s0:c123,c456\"\n  seccompProfile:\n    type: Localhost\n    localhostProfile: my-profiles/profile-allow.json\n  supplementalGroups:\n    - 65534\n    - 65535\n  sysctls:\n    - name: \"kernel.shm_rmid_forced\"\n      value: 0\n    - name: \"net.core.somaxconn\"\n      value: 1024\n  windowsOptions:\n    gmsaCredentialSpec: specA\n    gmsaCredentialSpecName: specA-name\n    hostProcess: true\n    runAsUserName: userA\n"
  },
  {
    "path": "src/test/resources/testResource.txt",
    "content": ""
  }
]