diff --git a/.github/workflows/accessTests.yml b/.github/workflows/accessTests.yml index 01b99abce..45195ef6c 100644 --- a/.github/workflows/accessTests.yml +++ b/.github/workflows/accessTests.yml @@ -22,20 +22,18 @@ jobs: os: [ ubuntu, windows, macos ] runs-on: ${{ matrix.os }}-latest steps: - - name: Setup Go - uses: actions/setup-go@v5 - with: - go-version: 1.22.x - cache: false - - name: Go Cache - uses: actions/cache@v4 - with: - path: ~/go/pkg/mod - key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} - restore-keys: ${{ runner.os }}-go- - name: Checkout code uses: actions/checkout@v4 with: ref: ${{ github.event.pull_request.head.sha }} + + - name: Setup Go with cache + uses: jfrog/.github/actions/install-go-with-cache@main + + - name: Install local Artifactory + uses: jfrog/.github/actions/install-local-artifactory@main + with: + RTLIC: ${{ secrets.RTLIC }} + - name: Run Access tests - run: go test -v github.com/jfrog/jfrog-cli --timeout 0 --test.access --jfrog.url=${{ secrets.PLATFORM_URL }} --jfrog.adminToken=${{ secrets.PLATFORM_ADMIN_TOKEN }} --jfrog.user=${{ secrets.PLATFORM_USER }} --ci.runId=${{ runner.os }}-access + run: go test -v github.com/jfrog/jfrog-cli --timeout 0 --test.access --jfrog.url=http://127.0.0.1:8082 --jfrog.adminToken=${{ env.JFROG_TESTS_LOCAL_ACCESS_TOKEN }} diff --git a/.github/workflows/addReleaseLinks.yml b/.github/workflows/addReleaseLinks.yml index 75622164b..14d44f719 100644 --- a/.github/workflows/addReleaseLinks.yml +++ b/.github/workflows/addReleaseLinks.yml @@ -9,7 +9,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out repository - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Create markdown download links run: | diff --git a/.github/workflows/analysis.yml b/.github/workflows/analysis.yml index b87f99800..5d2fba797 100644 --- a/.github/workflows/analysis.yml +++ b/.github/workflows/analysis.yml @@ -18,21 +18,14 @@ jobs: os: [ ubuntu, windows, macos ] runs-on: ${{ matrix.os }}-latest steps: - - name: Setup Go - uses: actions/setup-go@v5 - with: - go-version: 1.22.x - cache: false - name: Checkout code uses: actions/checkout@v4 with: ref: ${{ github.event.pull_request.head.sha }} - - name: Go Cache - uses: actions/cache@v4 - with: - path: ~/go/pkg/mod - key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} - restore-keys: ${{ runner.os }}-go- + + - name: Setup Go with cache + uses: jfrog/.github/actions/install-go-with-cache@main + - name: Run Go vet run: go vet -v ./... @@ -42,32 +35,25 @@ jobs: steps: - name: Checkout Source uses: actions/checkout@v4 - - name: Setup Go - uses: actions/setup-go@v5 - with: - go-version: 1.22.x - cache: false - - name: Static Code Analysis - uses: golangci/golangci-lint-action@v4 - with: - args: | - --timeout 5m --out-${NO_FUTURE}format colored-line-number --enable errcheck,gosimple,govet,ineffassign,staticcheck,typecheck,unused,gocritic,asasalint,asciicheck,errchkjson,exportloopref,forcetypeassert,makezero,nilerr,unparam,unconvert,wastedassign,usestdlibvars + - name: Setup Go with cache + uses: jfrog/.github/actions/install-go-with-cache@main + + - name: Run golangci linter + uses: jfrog/.github/actions/golangci-lint@main + Go-Sec: name: Go-Sec ubuntu-latest runs-on: ubuntu-latest steps: - name: Checkout Source uses: actions/checkout@v4 - - name: Setup Go - uses: actions/setup-go@v5 - with: - go-version: 1.22.x - cache: false - - name: Run Gosec Security Scanner - uses: securego/gosec@master - with: - args: -exclude G204,G301,G302,G304,G306 -tests -exclude-dir \.*test\.* ./... + + - name: Setup Go with cache + uses: jfrog/.github/actions/install-go-with-cache@main + + - name: Run Go-Sec scanner + uses: jfrog/.github/actions/gosec-scanner@main ShellCheck: name: Shellcheck diff --git a/.github/workflows/artifactoryTests.yml b/.github/workflows/artifactoryTests.yml index e75f55fc4..34197921b 100644 --- a/.github/workflows/artifactoryTests.yml +++ b/.github/workflows/artifactoryTests.yml @@ -23,31 +23,21 @@ jobs: os: [ ubuntu, windows, macos ] runs-on: ${{ matrix.os }}-latest steps: - - name: Setup Go - uses: actions/setup-go@v5 - with: - go-version: 1.22.x - cache: false - name: Checkout code uses: actions/checkout@v4 with: ref: ${{ github.event.pull_request.head.sha }} - - name: Go Cache - uses: actions/cache@v4 + + - name: Setup Go with cache + uses: jfrog/.github/actions/install-go-with-cache@main + + - name: Install local Artifactory + uses: jfrog/.github/actions/install-local-artifactory@main with: - path: ~/go/pkg/mod - key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} - restore-keys: ${{ runner.os }}-go- - - name: Setup Artifactory - run: | - go install github.com/jfrog/jfrog-testing-infra/local-rt-setup@latest - ~/go/bin/local-rt-setup - env: - RTLIC: ${{secrets.RTLIC}} - GOPROXY: direct + RTLIC: ${{ secrets.RTLIC }} - name: Run Artifactory tests - run: go test -v github.com/jfrog/jfrog-cli --timeout 0 --test.artifactory + run: go test -v github.com/jfrog/jfrog-cli --timeout 0 --test.artifactory --jfrog.url=http://127.0.0.1:8082 --jfrog.adminToken=${{ env.JFROG_TESTS_LOCAL_ACCESS_TOKEN }} if: ${{ matrix.suite == 'artifactory' }} - name: Run Artifactory projects tests diff --git a/.github/workflows/cla.yml b/.github/workflows/cla.yml index 6813343d7..cd55a6506 100644 --- a/.github/workflows/cla.yml +++ b/.github/workflows/cla.yml @@ -5,31 +5,14 @@ on: types: [ created ] pull_request_target: types: [ opened, synchronize ] - jobs: CLAssistant: runs-on: ubuntu-latest steps: - - uses: actions-ecosystem/action-regex-match@v2 - id: sign-or-recheck + - name: Run CLA Check + uses: jfrog/.github/actions/cla@main with: - text: ${{ github.event.comment.body }} - regex: '\s*(I have read the CLA Document and I hereby sign the CLA)|(recheck)\s*' - - - name: "CLA Assistant" - if: ${{ steps.sign-or-recheck.outputs.match != '' || github.event_name == 'pull_request_target' }} - # Alpha Release - uses: cla-assistant/github-action@v2.3.0 - env: - # Generated and maintained by GitHub + event_comment_body: ${{ github.event.comment.body }} + event_name: ${{ github.event_name }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - # JFrog organization secret - PERSONAL_ACCESS_TOKEN: ${{ secrets.CLA_SIGN_TOKEN }} - with: - path-to-signatures: "signed_clas.json" - path-to-document: "https://jfrog.com/cla/" - remote-organization-name: "jfrog" - remote-repository-name: "jfrog-signed-clas" - # branch should not be protected - branch: "master" - allowlist: bot* + CLA_SIGN_TOKEN: ${{ secrets.CLA_SIGN_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/distributionTests.yml b/.github/workflows/distributionTests.yml index 81235bbce..595ef011d 100644 --- a/.github/workflows/distributionTests.yml +++ b/.github/workflows/distributionTests.yml @@ -22,20 +22,13 @@ jobs: os: [ ubuntu, windows, macos ] runs-on: ${{ matrix.os }}-latest steps: - - name: Setup Go - uses: actions/setup-go@v5 - with: - go-version: 1.22.x - cache: false - - name: Go Cache - uses: actions/cache@v4 - with: - path: ~/go/pkg/mod - key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} - restore-keys: ${{ runner.os }}-go- + - name: Setup Go with cache + uses: jfrog/.github/actions/install-go-with-cache@main + - name: Checkout code uses: actions/checkout@v4 with: ref: ${{ github.event.pull_request.head.sha }} + - name: Run Distribution tests run: go test -v github.com/jfrog/jfrog-cli --timeout 0 --test.distribution --jfrog.url=${{ secrets.PLATFORM_URL }} --jfrog.adminToken=${{ secrets.PLATFORM_ADMIN_TOKEN }} --jfrog.user=${{ secrets.PLATFORM_USER }} --ci.runId=${{ runner.os }}-distribution diff --git a/.github/workflows/dockerTests.yml b/.github/workflows/dockerTests.yml index 9f48c2ff0..34d313326 100644 --- a/.github/workflows/dockerTests.yml +++ b/.github/workflows/dockerTests.yml @@ -18,21 +18,14 @@ jobs: name: ubuntu-latest runs-on: ubuntu-latest steps: - - name: Setup Go - uses: actions/setup-go@v5 - with: - go-version: 1.22.x - cache: false - name: Checkout code uses: actions/checkout@v4 with: ref: ${{ github.event.pull_request.head.sha }} - - name: Go Cache - uses: actions/cache@v4 - with: - path: ~/go/pkg/mod - key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} - restore-keys: ${{ runner.os }}-go- + + - name: Setup Go with cache + uses: jfrog/.github/actions/install-go-with-cache@main + - name: Containerize Artifactory run: | cd ./testdata/docker/artifactory/ @@ -40,6 +33,7 @@ jobs: env: RTLIC: ${{secrets.RTLIC}} GOPROXY: direct + - name: Wait for Artifactory to finish loading uses: nev7n/wait_for_response@v1 with: @@ -47,5 +41,6 @@ jobs: responseCode: 200 timeout: 600000 interval: 500 + - name: Run Docker tests run: go test -v -timeout 0 --test.docker diff --git a/.github/workflows/frogbot-scan-pull-request.yml b/.github/workflows/frogbot-scan-pull-request.yml index 66c14f5bc..b2aaf5295 100644 --- a/.github/workflows/frogbot-scan-pull-request.yml +++ b/.github/workflows/frogbot-scan-pull-request.yml @@ -12,11 +12,9 @@ jobs: # "frogbot" GitHub environment can approve the pull request to be scanned. environment: frogbot steps: - - name: Setup Go - uses: actions/setup-go@v5 - with: - go-version: 1.22.x - cache: false + - name: Setup Go with cache + uses: jfrog/.github/actions/install-go-with-cache@main + - uses: jfrog/frogbot@v2 env: JFROG_CLI_LOG_LEVEL: "DEBUG" diff --git a/.github/workflows/frogbot-scan-repository.yml b/.github/workflows/frogbot-scan-repository.yml index 7d80466a3..d43ada249 100644 --- a/.github/workflows/frogbot-scan-repository.yml +++ b/.github/workflows/frogbot-scan-repository.yml @@ -17,11 +17,9 @@ jobs: # The repository scanning will be triggered periodically on the following branches. branch: [ "dev" ] steps: - - name: Setup Go - uses: actions/setup-go@v5 - with: - go-version: 1.22.x - cache: false + - name: Setup Go with cache + uses: jfrog/.github/actions/install-go-with-cache@main + - uses: jfrog/frogbot@v2 env: JFROG_CLI_LOG_LEVEL: "DEBUG" diff --git a/.github/workflows/goTests.yml b/.github/workflows/goTests.yml index 2748fa8a8..984103cd6 100644 --- a/.github/workflows/goTests.yml +++ b/.github/workflows/goTests.yml @@ -23,20 +23,13 @@ jobs: os: [ ubuntu, windows, macos ] runs-on: ${{ matrix.os }}-latest steps: - - name: Setup Go - uses: actions/setup-go@v5 - with: - go-version: 1.22.x - cache: false - name: Checkout code uses: actions/checkout@v4 with: ref: ${{ github.event.pull_request.head.sha }} - - name: Go Cache - uses: actions/cache@v4 - with: - path: ~/go/pkg/mod - key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} - restore-keys: ${{ runner.os }}-go- + + - name: Setup Go with cache + uses: jfrog/.github/actions/install-go-with-cache@main + - name: Run Go tests run: go test -v -timeout 0 --test.go --jfrog.url=${{ secrets.PLATFORM_URL }} --jfrog.adminToken=${{ secrets.PLATFORM_ADMIN_TOKEN }} --ci.runId=${{ runner.os }}-go diff --git a/.github/workflows/gradleTests.yml b/.github/workflows/gradleTests.yml index a626ea213..3f5d6a21a 100644 --- a/.github/workflows/gradleTests.yml +++ b/.github/workflows/gradleTests.yml @@ -25,36 +25,29 @@ jobs: env: GRADLE_OPTS: -Dorg.gradle.daemon=false steps: - - name: Setup Go - uses: actions/setup-go@v5 + - name: Checkout code + uses: actions/checkout@v4 with: - go-version: 1.22.x - cache: false + ref: ${{ github.event.pull_request.head.sha }} + - name: Setup Java uses: actions/setup-java@v4 with: distribution: 'temurin' java-version: '11' + - name: Setup Gradle uses: gradle/gradle-build-action@v2 with: gradle-version: ${{ matrix.gradle-version }} - - name: Checkout code - uses: actions/checkout@v4 - with: - ref: ${{ github.event.pull_request.head.sha }} - - name: Go Cache - uses: actions/cache@v4 + + - name: Setup Go with cache + uses: jfrog/.github/actions/install-go-with-cache@main + + - name: Install local Artifactory + uses: jfrog/.github/actions/install-local-artifactory@main with: - path: ~/go/pkg/mod - key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} - restore-keys: ${{ runner.os }}-go- - - name: Setup Artifactory - run: | - go install github.com/jfrog/jfrog-testing-infra/local-rt-setup@latest - ~/go/bin/local-rt-setup - env: - RTLIC: ${{secrets.RTLIC}} - GOPROXY: direct + RTLIC: ${{ secrets.RTLIC }} + - name: Run Gradle tests run: go test -v github.com/jfrog/jfrog-cli --timeout 0 --test.gradle diff --git a/.github/workflows/lifecycleTests.yml b/.github/workflows/lifecycleTests.yml index e00bd8730..e37ffe009 100644 --- a/.github/workflows/lifecycleTests.yml +++ b/.github/workflows/lifecycleTests.yml @@ -22,24 +22,13 @@ jobs: os: [ ubuntu, windows, macos ] runs-on: ${{ matrix.os }}-latest steps: - - name: Setup Go - uses: actions/setup-go@v5 - with: - go-version: 1.22.x - cache: false - - name: Checkout code - uses: actions/checkout@v4 - with: - ref: ${{ github.event.pull_request.head.sha }} - - name: Go Cache - uses: actions/cache@v4 - with: - path: ~/go/pkg/mod - key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} - restore-keys: ${{ runner.os }}-go- - name: Checkout code uses: actions/checkout@v4 with: ref: ${{ github.event.pull_request.head.sha }} + + - name: Setup Go with cache + uses: jfrog/.github/actions/install-go-with-cache@main + - name: Run Lifecycle tests run: go test -v github.com/jfrog/jfrog-cli --timeout 0 --test.lifecycle --jfrog.url=${{ secrets.PLATFORM_URL }} --jfrog.adminToken=${{ secrets.PLATFORM_ADMIN_TOKEN }} --jfrog.user=${{ secrets.PLATFORM_USER }} --ci.runId=${{ runner.os }}-lifecycle diff --git a/.github/workflows/mavenTests.yml b/.github/workflows/mavenTests.yml index 27d9f678e..dd6f6af29 100644 --- a/.github/workflows/mavenTests.yml +++ b/.github/workflows/mavenTests.yml @@ -22,32 +22,24 @@ jobs: os: [ ubuntu, windows, macos ] runs-on: ${{ matrix.os }}-latest steps: - - name: Setup Go - uses: actions/setup-go@v5 - with: - go-version: 1.22.x - cache: false - name: Checkout code uses: actions/checkout@v4 with: ref: ${{ github.event.pull_request.head.sha }} - - name: Go Cache - uses: actions/cache@v4 - with: - path: ~/go/pkg/mod - key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} - restore-keys: ${{ runner.os }}-go- + + - name: Setup Go with cache + uses: jfrog/.github/actions/install-go-with-cache@main + - name: Setup Maven v3.8.8 for macOS uses: stCarolas/setup-maven@v4.5 with: maven-version: 3.8.8 if: runner.os == 'macOS' - - name: Setup Artifactory - run: | - go install github.com/jfrog/jfrog-testing-infra/local-rt-setup@latest - ~/go/bin/local-rt-setup - env: - RTLIC: ${{secrets.RTLIC}} - GOPROXY: direct + + - name: Install local Artifactory + uses: jfrog/.github/actions/install-local-artifactory@main + with: + RTLIC: ${{ secrets.RTLIC }} + - name: Run Maven tests run: go test -v github.com/jfrog/jfrog-cli --timeout 0 --test.maven diff --git a/.github/workflows/npmTests.yml b/.github/workflows/npmTests.yml index 7c94dc7c9..16861d26d 100644 --- a/.github/workflows/npmTests.yml +++ b/.github/workflows/npmTests.yml @@ -22,31 +22,23 @@ jobs: os: [ ubuntu, windows, macos ] runs-on: ${{ matrix.os }}-latest steps: - - name: Setup Go - uses: actions/setup-go@v5 - with: - go-version: 1.22.x - cache: false - - name: Install npm - uses: actions/setup-node@v3 - with: - node-version: "16" - name: Checkout code uses: actions/checkout@v4 with: ref: ${{ github.event.pull_request.head.sha }} - - name: Go Cache - uses: actions/cache@v4 + + - name: Install npm + uses: actions/setup-node@v4 + with: + node-version: "16" + + - name: Setup Go with cache + uses: jfrog/.github/actions/install-go-with-cache@main + + - name: Install local Artifactory + uses: jfrog/.github/actions/install-local-artifactory@main with: - path: ~/go/pkg/mod - key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} - restore-keys: ${{ runner.os }}-go- - - name: Setup Artifactory - run: | - go install github.com/jfrog/jfrog-testing-infra/local-rt-setup@latest - ~/go/bin/local-rt-setup - env: - RTLIC: ${{secrets.RTLIC}} - GOPROXY: direct + RTLIC: ${{ secrets.RTLIC }} + - name: Run npm tests run: go test -v github.com/jfrog/jfrog-cli --timeout 0 --test.npm diff --git a/.github/workflows/nugetTests.yml b/.github/workflows/nugetTests.yml index 72562047b..98ca0e586 100644 --- a/.github/workflows/nugetTests.yml +++ b/.github/workflows/nugetTests.yml @@ -1,4 +1,5 @@ name: NuGet Tests + on: push: branches: @@ -8,10 +9,12 @@ on: # Triggers the workflow on labeled PRs only. pull_request_target: types: [ labeled ] + # Ensures that only the latest commit is running for each PR at a time. concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }}-${{ github.ref }} cancel-in-progress: true + jobs: NuGet-Tests: if: contains(github.event.pull_request.labels.*.name, 'safe to test') || github.event_name == 'push' @@ -22,35 +25,39 @@ jobs: os: [ ubuntu, windows, macos ] runs-on: ${{ matrix.os }}-latest steps: - - name: Setup Go - uses: actions/setup-go@v5 + - name: Checkout code + uses: actions/checkout@v4 with: - go-version: 1.22.x - cache: false + ref: ${{ github.event.pull_request.head.sha }} + + # Install Mono on Ubuntu to run nuget.exe + - name: Install Mono on Ubuntu + if: matrix.os == 'ubuntu' + run: | + sudo apt-get update + sudo apt-get install -y apt-transport-https dirmngr gnupg ca-certificates + sudo apt-key adv --recv-keys --keyserver hkp://keyserver.ubuntu.com:80 3FA7E0328081BFF6A14DA29AA6A19B38D3D831EF + echo "deb https://download.mono-project.com/repo/ubuntu stable-focal main" | sudo tee /etc/apt/sources.list.d/mono-official-stable.list + sudo apt-get update + sudo apt-get install -y mono-complete + - name: Install NuGet uses: nuget/setup-nuget@v2 with: - nuget-version: 6.x - - name: Install dotnet + nuget-version: '6.x' + + - name: Install .NET uses: actions/setup-dotnet@v4 with: - dotnet-version: "6.x" - - name: Checkout code - uses: actions/checkout@v4 - with: - ref: ${{ github.event.pull_request.head.sha }} - - name: Go Cache - uses: actions/cache@v4 + dotnet-version: '6.x' + + - name: Setup Go with cache + uses: jfrog/.github/actions/install-go-with-cache@main + + - name: Install local Artifactory + uses: jfrog/.github/actions/install-local-artifactory@main with: - path: ~/go/pkg/mod - key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} - restore-keys: ${{ runner.os }}-go- - - name: Setup Artifactory - run: | - go install github.com/jfrog/jfrog-testing-infra/local-rt-setup@latest - ~/go/bin/local-rt-setup - env: - RTLIC: ${{secrets.RTLIC}} - GOPROXY: direct + RTLIC: ${{ secrets.RTLIC }} + - name: Run NuGet tests run: go test -v github.com/jfrog/jfrog-cli --timeout 0 --test.nuget diff --git a/.github/workflows/pluginsTests.yml b/.github/workflows/pluginsTests.yml index 4d452fa99..e3c1fcca6 100644 --- a/.github/workflows/pluginsTests.yml +++ b/.github/workflows/pluginsTests.yml @@ -22,27 +22,18 @@ jobs: os: [ ubuntu, windows, macos ] runs-on: ${{ matrix.os }}-latest steps: - - name: Setup Go - uses: actions/setup-go@v5 - with: - go-version: 1.22.x - cache: false - name: Checkout code uses: actions/checkout@v4 with: ref: ${{ github.event.pull_request.head.sha }} - - name: Go Cache - uses: actions/cache@v4 + + - name: Setup Go with cache + uses: jfrog/.github/actions/install-go-with-cache@main + + - name: Install local Artifactory + uses: jfrog/.github/actions/install-local-artifactory@main with: - path: ~/go/pkg/mod - key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} - restore-keys: ${{ runner.os }}-go- - - name: Setup Artifactory - run: | - go install github.com/jfrog/jfrog-testing-infra/local-rt-setup@latest - ~/go/bin/local-rt-setup - env: - RTLIC: ${{secrets.RTLIC}} - GOPROXY: direct + RTLIC: ${{ secrets.RTLIC }} + - name: Run plugins tests run: go test -v github.com/jfrog/jfrog-cli --timeout 0 --test.plugins diff --git a/.github/workflows/podmanTests.yml b/.github/workflows/podmanTests.yml index ef6660317..73858c716 100644 --- a/.github/workflows/podmanTests.yml +++ b/.github/workflows/podmanTests.yml @@ -18,20 +18,13 @@ jobs: name: ubuntu-latest runs-on: ubuntu-latest steps: - - name: Setup Go - uses: actions/setup-go@v5 - with: - go-version: 1.22.x - cache: false - name: Checkout code uses: actions/checkout@v4 with: ref: ${{ github.event.pull_request.head.sha }} - - name: Go Cache - uses: actions/cache@v4 - with: - path: ~/go/pkg/mod - key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} - restore-keys: ${{ runner.os }}-go- + + - name: Setup Go with cache + uses: jfrog/.github/actions/install-go-with-cache@main + - name: Run podman tests run: go test -v -timeout 0 --test.podman --jfrog.url=${{ secrets.PLATFORM_URL }} --jfrog.adminToken=${{ secrets.PLATFORM_ADMIN_TOKEN }} --test.containerRegistry=${{ secrets.CONTAINER_REGISTRY }} diff --git a/.github/workflows/pythonTests.yml b/.github/workflows/pythonTests.yml index cfad39e35..926dc4444 100644 --- a/.github/workflows/pythonTests.yml +++ b/.github/workflows/pythonTests.yml @@ -23,35 +23,32 @@ jobs: os: [ ubuntu, windows, macos ] runs-on: ${{ matrix.os }}-latest steps: - - name: Setup Go - uses: actions/setup-go@v5 + - name: Checkout code + uses: actions/checkout@v4 with: - go-version: 1.22.x - cache: false + ref: ${{ github.event.pull_request.head.sha }} + # Due to a bug in Python 3.12.0 we temporarily use version 3.11.5 - name: Setup Python3 uses: actions/setup-python@v5 with: python-version: "3.11.5" + - name: Setup Pipenv if: ${{ matrix.suite == 'pipenv' }} run: python -m pip install pipenv - - name: Checkout code - uses: actions/checkout@v4 - with: - ref: ${{ github.event.pull_request.head.sha }} - - name: Go Cache - uses: actions/cache@v4 + + - name: Setup Twine + if: ${{ matrix.suite == 'pip' }} + run: python -m pip install twine + + - name: Setup Go with cache + uses: jfrog/.github/actions/install-go-with-cache@main + + - name: Install local Artifactory + uses: jfrog/.github/actions/install-local-artifactory@main with: - path: ~/go/pkg/mod - key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} - restore-keys: ${{ runner.os }}-go- - - name: Setup Artifactory - run: | - go install github.com/jfrog/jfrog-testing-infra/local-rt-setup@latest - ~/go/bin/local-rt-setup - env: - RTLIC: ${{secrets.RTLIC}} - GOPROXY: direct + RTLIC: ${{ secrets.RTLIC }} + - name: Run Python tests run: go test -v github.com/jfrog/jfrog-cli --timeout 0 --test.${{ matrix.suite }} diff --git a/.github/workflows/scriptTests.yml b/.github/workflows/scriptTests.yml index 8d0b466ca..916d25562 100644 --- a/.github/workflows/scriptTests.yml +++ b/.github/workflows/scriptTests.yml @@ -26,7 +26,7 @@ jobs: - os: "macos-latest" - - os: "macos-11" + - os: "macos-12" - os: "windows-latest" osSuffix: ".exe" @@ -35,23 +35,13 @@ jobs: osSuffix: ".exe" runs-on: ${{ matrix.suite.os }} steps: - - name: Setup Go - uses: actions/setup-go@v5 - with: - go-version: 1.22.x - cache: false - - name: Checkout code uses: actions/checkout@v4 with: ref: ${{ github.event.pull_request.head.sha }} - - name: Go Cache - uses: actions/cache@v4 - with: - path: ~/go/pkg/mod - key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} - restore-keys: ${{ runner.os }}-go- + - name: Setup Go with cache + uses: jfrog/.github/actions/install-go-with-cache@main - name: Test install CLI - jf run: | diff --git a/.github/workflows/transferTests.yml b/.github/workflows/transferTests.yml index 4d72090ed..9e061137c 100644 --- a/.github/workflows/transferTests.yml +++ b/.github/workflows/transferTests.yml @@ -26,27 +26,18 @@ jobs: uses: actions/checkout@v4 with: ref: ${{ github.event.pull_request.head.sha }} - - name: Setup Go - uses: actions/setup-go@v5 - with: - go-version: 1.22.x - cache: false - - name: Go Cache - uses: actions/cache@v4 + + - name: Setup Go with cache + uses: jfrog/.github/actions/install-go-with-cache@main + + - name: Install local Artifactory + uses: jfrog/.github/actions/install-local-artifactory@main with: - path: ~/go/pkg/mod - key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} - restore-keys: ${{ runner.os }}-go- - - name: Setup Artifactory - run: | - go install github.com/jfrog/jfrog-testing-infra/local-rt-setup@latest - ~/go/bin/local-rt-setup - env: - RTLIC: ${{secrets.RTLIC}} - GOPROXY: direct + RTLIC: ${{ secrets.RTLIC }} JFROG_HOME: ${{ runner.temp }} + - name: Run transfer tests - run: go test -v github.com/jfrog/jfrog-cli --timeout 0 --test.transfer --test.installDataTransferPlugin --jfrog.targetUrl=${{ secrets.PLATFORM_URL }} --jfrog.targetAdminToken=${{ secrets.PLATFORM_ADMIN_TOKEN }} --jfrog.home=${{ runner.temp }} --ci.runId=${{ runner.os }}-transfer-7 + run: go test -v github.com/jfrog/jfrog-cli --timeout 0 --test.transfer --test.installDataTransferPlugin --jfrog.url=http://127.0.0.1:8082 --jfrog.targetUrl=${{ secrets.PLATFORM_URL }} --jfrog.targetAdminToken=${{ secrets.PLATFORM_ADMIN_TOKEN }} --jfrog.home=${{ runner.temp }} --ci.runId=${{ runner.os }}-transfer-7 Transfer-Artifactory-6-Tests: if: contains(github.event.pull_request.labels.*.name, 'safe to test') || github.event_name == 'push' @@ -57,24 +48,16 @@ jobs: uses: actions/checkout@v4 with: ref: ${{ github.event.pull_request.head.sha }} - - name: Setup Go - uses: actions/setup-go@v5 - with: - go-version: 1.22.x - cache: false - - name: Go Cache - uses: actions/cache@v4 + + - name: Setup Go with cache + uses: jfrog/.github/actions/install-go-with-cache@main + + - name: Install local Artifactory + uses: jfrog/.github/actions/install-local-artifactory@main with: - path: ~/go/pkg/mod - key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} - restore-keys: ${{ runner.os }}-go- - - name: Setup Artifactory 6 - run: | - go install github.com/jfrog/jfrog-testing-infra/local-rt-setup@latest - ~/go/bin/local-rt-setup --rt-version=6.23.21 - env: - RTLIC: ${{secrets.RTLIC}} - GOPROXY: direct + RTLIC: ${{ secrets.RTLIC_V6 }} JFROG_HOME: ${{ runner.temp }} + VERSION: 6.23.21 + - name: Run transfer tests run: go test -v github.com/jfrog/jfrog-cli --timeout 0 --test.transfer --test.installDataTransferPlugin --jfrog.targetUrl=${{ secrets.PLATFORM_URL }} --jfrog.targetAdminToken=${{ secrets.PLATFORM_ADMIN_TOKEN }} --jfrog.home=${{ runner.temp }} --ci.runId=${{ runner.os }}-transfer-6 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 30d6a1ede..db9823882 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -41,9 +41,9 @@ After the build process completes, you will find the `jf` or `jf.exe` executable This project heavily depends on the following modules: - [github.com/jfrog/jfrog-client-go](https://github.com/jfrog/jfrog-client-go) -- [github.com/jfrog/jfrog-cli-core](github.com/jfrog/jfrog-cli-core) -- [github.com/jfrog/build-info-go](github.com/jfrog/build-info-go) -- [github.com/jfrog/gofrog](github.com/jfrog/gofrog) +- [github.com/jfrog/jfrog-cli-core](https://github.com/jfrog/jfrog-cli-core) +- [github.com/jfrog/build-info-go](https://github.com/jfrog/build-info-go) +- [github.com/jfrog/gofrog](https://github.com/jfrog/gofrog) #### Local Development diff --git a/Jenkinsfile b/Jenkinsfile index a496129a8..ebd7e36e0 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -1,4 +1,4 @@ -node("docker") { +node("docker-ubuntu20-xlarge") { cleanWs() // Subtract repo name from the repo url (https://REPO_NAME/ -> REPO_NAME/) withCredentials([string(credentialsId: 'repo21-url', variable: 'REPO21_URL')]) { @@ -8,8 +8,8 @@ node("docker") { } def architectures = [ [pkg: 'jfrog-cli-windows-amd64', goos: 'windows', goarch: 'amd64', fileExtension: '.exe', chocoImage: '${REPO_NAME_21}/jfrog-docker/linuturk/mono-choco'], - [pkg: 'jfrog-cli-linux-386', goos: 'linux', goarch: '386', fileExtension: '', debianImage: '${REPO_NAME_21}/jfrog-docker/i386/ubuntu:16.04', debianArch: 'i386'], - [pkg: 'jfrog-cli-linux-amd64', goos: 'linux', goarch: 'amd64', fileExtension: '', debianImage: '${REPO_NAME_21}/jfrog-docker/ubuntu:16.04', debianArch: 'x86_64', rpmImage: 'tgagor/centos:stream8'], + [pkg: 'jfrog-cli-linux-386', goos: 'linux', goarch: '386', fileExtension: '', debianImage: '${REPO_NAME_21}/jfrog-docker/i386/ubuntu:20.04', debianArch: 'i386'], + [pkg: 'jfrog-cli-linux-amd64', goos: 'linux', goarch: 'amd64', fileExtension: '', debianImage: '${REPO_NAME_21}/jfrog-docker/ubuntu:20.04', debianArch: 'x86_64', rpmImage: 'almalinux:8.10'], [pkg: 'jfrog-cli-linux-arm64', goos: 'linux', goarch: 'arm64', fileExtension: ''], [pkg: 'jfrog-cli-linux-arm', goos: 'linux', goarch: 'arm', fileExtension: ''], [pkg: 'jfrog-cli-mac-386', goos: 'darwin', goarch: 'amd64', fileExtension: ''], @@ -31,7 +31,7 @@ node("docker") { repo = 'jfrog-cli' sh 'rm -rf temp' sh 'mkdir temp' - def goRoot = tool 'go-1.22.3' + def goRoot = tool 'go-1.23.3' env.GOROOT="$goRoot" env.PATH+=":${goRoot}/bin:/tmp/node-${nodeVersion}-linux-x64/bin" env.GO111MODULE="on" diff --git a/README.md b/README.md index e50c09c5f..4a7a31fd6 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ [![Scanned by Frogbot](https://raw.github.com/jfrog/frogbot/master/images/frogbot-badge.svg)](https://github.com/jfrog/frogbot#readme) [![Go Report Card](https://goreportcard.com/badge/github.com/jfrog/jfrog-cli)](https://goreportcard.com/report/github.com/jfrog/jfrog-cli) [![license](https://img.shields.io/badge/License-Apache_2.0-blue.svg?style=flat)](https://raw.githubusercontent.com/jfrog/jfrog-cli/v2/LICENSE) [![](https://img.shields.io/badge/Docs-%F0%9F%93%96-blue)](https://docs.jfrog-applications.jfrog.io/jfrog-applications/jfrog-cli) -[![Go version](https://img.shields.io/github/go-mod/go-version/jfrog/jfrog-cli)](https://tip.golang.org/doc/go1.22) +[![Go version](https://img.shields.io/github/go-mod/go-version/jfrog/jfrog-cli)](https://tip.golang.org/doc/go1.23) diff --git a/access_test.go b/access_test.go index 226325ced..00efa9f11 100644 --- a/access_test.go +++ b/access_test.go @@ -1,24 +1,24 @@ package main import ( - "encoding/base64" "encoding/json" "fmt" + "net/http" + "testing" + + "github.com/jfrog/jfrog-cli-core/v2/artifactory/utils" "github.com/jfrog/jfrog-cli-core/v2/common/commands" - coreEnvSetup "github.com/jfrog/jfrog-cli-core/v2/general/envsetup" "github.com/jfrog/jfrog-cli-core/v2/utils/config" "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" coreTests "github.com/jfrog/jfrog-cli-core/v2/utils/tests" "github.com/jfrog/jfrog-cli/utils/tests" + "github.com/jfrog/jfrog-client-go/access/services" "github.com/jfrog/jfrog-client-go/auth" "github.com/jfrog/jfrog-client-go/http/httpclient" clientUtils "github.com/jfrog/jfrog-client-go/utils" "github.com/jfrog/jfrog-client-go/utils/errorutils" "github.com/jfrog/jfrog-client-go/utils/io/httputils" - clientTestUtils "github.com/jfrog/jfrog-client-go/utils/tests" "github.com/stretchr/testify/assert" - "net/http" - "testing" ) var ( @@ -74,40 +74,11 @@ func authenticateAccess() string { return cred } -func TestSetupInvitedUser(t *testing.T) { - initAccessTest(t) - tempDirPath, createTempDirCallback := coreTests.CreateTempDirWithCallbackAndAssert(t) - defer createTempDirCallback() - setEnvCallBack := clientTestUtils.SetEnvWithCallbackAndAssert(t, coreutils.HomeDir, tempDirPath) - defer setEnvCallBack() - setupServerDetails := &config.ServerDetails{Url: *tests.JfrogUrl, AccessToken: *tests.JfrogAccessToken} - encodedCred := encodeConnectionDetails(setupServerDetails, t) - setupCmd := coreEnvSetup.NewEnvSetupCommand().SetEncodedConnectionDetails(encodedCred) - suffix := setupCmd.SetupAndConfigServer() - assert.Empty(t, suffix) - configs, err := config.GetAllServersConfigs() - assert.NoError(t, err) - assert.Equal(t, 1, len(configs)) - // Verify config values - assert.Equal(t, configs[0].Url, *tests.JfrogUrl) - assert.Equal(t, *tests.JfrogUrl+"artifactory/", configs[0].ArtifactoryUrl) - // Verify token was refreshed - assert.NotEqual(t, *tests.JfrogAccessToken, configs[0].AccessToken) - assert.NotEmpty(t, configs[0].RefreshToken) -} - -func encodeConnectionDetails(serverDetails *config.ServerDetails, t *testing.T) string { - jsonConnectionDetails, err := json.Marshal(serverDetails) - assert.NoError(t, err) - encoded := base64.StdEncoding.EncodeToString(jsonConnectionDetails) - return encoded -} - func TestRefreshableAccessTokens(t *testing.T) { initAccessTest(t) server := &config.ServerDetails{Url: *tests.JfrogUrl, AccessToken: *tests.JfrogAccessToken} - err := coreEnvSetup.GenerateNewLongTermRefreshableAccessToken(server) + err := generateNewLongTermRefreshableAccessToken(server) assert.NoError(t, err) assert.NotEmpty(t, server.RefreshToken) configCmd := commands.NewConfigCommand(commands.AddOrEdit, tests.ServerId).SetDetails(server).SetInteractive(false) @@ -162,6 +133,32 @@ func TestRefreshableAccessTokens(t *testing.T) { cleanArtifactoryTest() } +// Take the short-lived token and generate a long term (1 year expiry) refreshable accessToken. +func generateNewLongTermRefreshableAccessToken(server *config.ServerDetails) (err error) { + accessManager, err := utils.CreateAccessServiceManager(server, false) + if err != nil { + return + } + // Create refreshable accessToken with 1 year expiry from the given short expiry token. + params := createLongExpirationRefreshableTokenParams() + token, err := accessManager.CreateAccessToken(*params) + if err != nil { + return + } + server.AccessToken = token.AccessToken + server.RefreshToken = token.RefreshToken + return +} + +func createLongExpirationRefreshableTokenParams() *services.CreateTokenParams { + params := services.CreateTokenParams{} + // Using the platform's default expiration (1 year by default). + params.ExpiresIn = nil + params.Refreshable = clientUtils.Pointer(true) + params.Audience = "*@*" + return ¶ms +} + // After refreshing an access token, assert that the access token and the refresh token were changed, and the Artifactory refresh token remained empty. func assertAccessTokensChanged(t *testing.T, curAccessToken, curRefreshToken string) (newAccessToken, newRefreshToken string, err error) { var newArtifactoryRefreshToken string @@ -177,94 +174,93 @@ func assertAccessTokensChanged(t *testing.T, curAccessToken, curRefreshToken str } const ( - userScope = "applied-permissions/user" - defaultExpiry = 31536000 + userScope = "applied-permissions/user" ) -var atcTestCases = []struct { - name string - args []string - shouldExpire bool - // The expected expiry or -1 if we use the default expiry value - expectedExpiry int - expectedScope string - expectedRefreshable bool - expectedReference bool -}{ - { - name: "default", - args: []string{"atc"}, - shouldExpire: true, - expectedExpiry: -1, - expectedScope: userScope, - expectedRefreshable: false, - expectedReference: false, - }, - { - name: "explicit user, no expiry", - args: []string{"atc", auth.ExtractUsernameFromAccessToken(*tests.JfrogAccessToken), "--expiry=0"}, - shouldExpire: false, - expectedExpiry: 0, - expectedScope: userScope, - expectedRefreshable: false, - expectedReference: false, - }, - { - name: "refreshable, admin", - args: []string{"atc", "--refreshable", "--grant-admin"}, - shouldExpire: true, - expectedExpiry: -1, - expectedScope: "applied-permissions/admin", - expectedRefreshable: true, - expectedReference: false, - }, - { - name: "reference, custom scope, custom expiry", - args: []string{"atc", "--reference", "--scope=system:metrics:r", "--expiry=123456"}, - shouldExpire: true, - expectedExpiry: 123456, - expectedScope: "system:metrics:r", - expectedRefreshable: false, - expectedReference: true, - }, - { - name: "groups, description", - args: []string{"atc", "--groups=group1,group2", "--description=description"}, - shouldExpire: true, - expectedExpiry: -1, - expectedScope: "applied-permissions/groups:group1,group2", - expectedRefreshable: false, - expectedReference: false, - }, -} - func TestAccessTokenCreate(t *testing.T) { initAccessTest(t) if *tests.JfrogAccessToken == "" { t.Skip("access token create command only supports authorization with access token, but a token is not provided. Skipping...") } - for _, test := range atcTestCases { - t.Run(test.name, func(t *testing.T) { + var testCases = []struct { + name string + args []string + shouldExpire bool + // The expected expiry or -1 if we use the default expiry value + expectedExpiry int + expectedScope string + expectedRefreshable bool + expectedReference bool + }{ + { + name: "default", + args: []string{"atc"}, + shouldExpire: true, + expectedExpiry: -1, + expectedScope: userScope, + expectedRefreshable: false, + expectedReference: false, + }, + { + name: "explicit user, no expiry", + args: []string{"atc", auth.ExtractUsernameFromAccessToken(*tests.JfrogAccessToken), "--expiry=0"}, + shouldExpire: false, + expectedExpiry: 0, + expectedScope: userScope, + expectedRefreshable: false, + expectedReference: false, + }, + { + name: "refreshable, admin", + args: []string{"atc", "--refreshable", "--grant-admin"}, + shouldExpire: true, + expectedExpiry: -1, + expectedScope: "applied-permissions/admin", + expectedRefreshable: true, + expectedReference: false, + }, + { + name: "reference, custom scope, custom expiry", + args: []string{"atc", "--reference", "--scope=system:metrics:r", "--expiry=123456"}, + shouldExpire: true, + expectedExpiry: 123456, + expectedScope: "system:metrics:r", + expectedRefreshable: false, + expectedReference: true, + }, + { + name: "groups, description", + args: []string{"atc", "--groups=group1,group2", "--description=description"}, + shouldExpire: true, + expectedExpiry: -1, + expectedScope: "applied-permissions/groups:group1,group2", + expectedRefreshable: false, + expectedReference: false, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { var token auth.CreateTokenResponseData - output := accessCli.RunCliCmdWithOutput(t, test.args...) + output := accessCli.RunCliCmdWithOutput(t, testCase.args...) assert.NoError(t, json.Unmarshal([]byte(output), &token)) defer revokeToken(t, token.TokenId) - if test.shouldExpire { - if test.expectedExpiry == -1 { + if testCase.shouldExpire { + if testCase.expectedExpiry == -1 { // If expectedExpiry is -1, expect the default expiry assert.Positive(t, *token.ExpiresIn) } else { - assert.EqualValues(t, test.expectedExpiry, *token.ExpiresIn) + assert.EqualValues(t, testCase.expectedExpiry, *token.ExpiresIn) } } else { assert.Nil(t, token.ExpiresIn) } assert.NotEmpty(t, token.AccessToken) - assert.Equal(t, test.expectedScope, token.Scope) - assertNotEmptyIfExpected(t, test.expectedRefreshable, token.RefreshToken) - assertNotEmptyIfExpected(t, test.expectedReference, token.ReferenceToken) + assert.Equal(t, testCase.expectedScope, token.Scope) + assertNotEmptyIfExpected(t, testCase.expectedRefreshable, token.RefreshToken) + assertNotEmptyIfExpected(t, testCase.expectedReference, token.ReferenceToken) // Try pinging Artifactory with the new token. assert.NoError(t, coreTests.NewJfrogCli(execMain, "jfrog rt", diff --git a/artifactory/cli.go b/artifactory/cli.go index 43b3fa110..89a8aab0c 100644 --- a/artifactory/cli.go +++ b/artifactory/cli.go @@ -3,12 +3,15 @@ package artifactory import ( "errors" "fmt" - ioutils "github.com/jfrog/gofrog/io" - "github.com/jfrog/jfrog-cli/utils/accesstoken" + "github.com/jfrog/jfrog-cli/docs/artifactory/cocoapodsconfig" + "github.com/jfrog/jfrog-cli/docs/artifactory/swiftconfig" "os" "strconv" "strings" + ioutils "github.com/jfrog/gofrog/io" + "github.com/jfrog/jfrog-cli/utils/accesstoken" + "github.com/jfrog/gofrog/version" "github.com/jfrog/jfrog-cli-core/v2/artifactory/commands/transferinstall" "github.com/jfrog/jfrog-cli/docs/artifactory/transferplugininstall" @@ -405,6 +408,32 @@ func GetCommands() []cli.Command { return cliutils.RunNativeCmdWithDeprecationWarning("gradle", project.Gradle, c, buildtools.GradleCmd) }, }, + { + Name: "cocoapods-config", + Hidden: true, + Aliases: []string{"cocoapodsc"}, + Flags: cliutils.GetCommandFlags(cliutils.CocoapodsConfig), + Usage: gradleconfig.GetDescription(), + HelpName: corecommon.CreateUsage("rt cocoapods-config", cocoapodsconfig.GetDescription(), cocoapodsconfig.Usage), + ArgsUsage: common.CreateEnvVars(), + BashComplete: corecommon.CreateBashCompletionFunc(), + Action: func(c *cli.Context) error { + return cliutils.RunConfigCmdWithDeprecationWarning("cocoapodsc", "rt", project.Cocoapods, c, cliutils.CreateConfigCmd) + }, + }, + { + Name: "swift-config", + Hidden: true, + Aliases: []string{"swiftc"}, + Flags: cliutils.GetCommandFlags(cliutils.SwiftConfig), + Usage: gradleconfig.GetDescription(), + HelpName: corecommon.CreateUsage("rt swift-config", swiftconfig.GetDescription(), swiftconfig.Usage), + ArgsUsage: common.CreateEnvVars(), + BashComplete: corecommon.CreateBashCompletionFunc(), + Action: func(c *cli.Context) error { + return cliutils.RunConfigCmdWithDeprecationWarning("swiftc", "rt", project.Swift, c, cliutils.CreateConfigCmd) + }, + }, { Name: "docker-promote", Flags: cliutils.GetCommandFlags(cliutils.DockerPromote), @@ -1148,7 +1177,7 @@ func BuildDockerCreateCmd(c *cli.Context) error { return err } buildDockerCreateCommand := container.NewBuildDockerCreateCommand() - if err := buildDockerCreateCommand.SetImageNameWithDigest(imageNameWithDigestFile); err != nil { + if err = buildDockerCreateCommand.SetImageNameWithDigest(imageNameWithDigestFile); err != nil { return err } buildDockerCreateCommand.SetRepo(sourceRepo).SetServerDetails(artDetails).SetBuildConfiguration(buildConfiguration) @@ -1344,7 +1373,7 @@ func uploadCmd(c *cli.Context) (err error) { if err != nil { return } - printDeploymentView, detailedSummary := log.IsStdErrTerminal(), c.Bool("detailed-summary") + printDeploymentView, detailedSummary := log.IsStdErrTerminal(), cliutils.GetDetailedSummary(c) uploadCmd.SetUploadConfiguration(configuration).SetBuildConfiguration(buildConfiguration).SetSpec(uploadSpec).SetServerDetails(rtDetails).SetDryRun(c.Bool("dry-run")).SetSyncDeletesPath(c.String("sync-deletes")).SetQuiet(cliutils.GetQuietValue(c)).SetDetailedSummary(detailedSummary || printDeploymentView).SetRetries(retries).SetRetryWaitMilliSecs(retryWaitTime) if uploadCmd.ShouldPrompt() && !coreutils.AskYesNo("Sync-deletes may delete some artifacts in Artifactory. Are you sure you want to continue?\n"+ @@ -1666,7 +1695,7 @@ func buildPublishCmd(c *cli.Context) error { if err != nil { return err } - buildPublishCmd := buildinfo.NewBuildPublishCommand().SetServerDetails(rtDetails).SetBuildConfiguration(buildConfiguration).SetConfig(buildInfoConfiguration).SetDetailedSummary(c.Bool("detailed-summary")) + buildPublishCmd := buildinfo.NewBuildPublishCommand().SetServerDetails(rtDetails).SetBuildConfiguration(buildConfiguration).SetConfig(buildInfoConfiguration).SetDetailedSummary(cliutils.GetDetailedSummary(c)) err = commands.Exec(buildPublishCmd) if buildPublishCmd.IsDetailedSummary() { @@ -2648,7 +2677,9 @@ func createDefaultDownloadSpec(c *cli.Context) (*spec.SpecFiles, error) { func setTransitiveInDownloadSpec(downloadSpec *spec.SpecFiles) { transitive := os.Getenv(coreutils.TransitiveDownload) if transitive == "" { - return + if transitive = os.Getenv(coreutils.TransitiveDownloadExperimental); transitive == "" { + return + } } for fileIndex := 0; fileIndex < len(downloadSpec.Files); fileIndex++ { downloadSpec.Files[fileIndex].Transitive = transitive diff --git a/artifactory_test.go b/artifactory_test.go index df72662ee..9ab9cd2b1 100644 --- a/artifactory_test.go +++ b/artifactory_test.go @@ -33,7 +33,6 @@ import ( "github.com/jfrog/jfrog-cli-core/v2/artifactory/utils" commonCliUtils "github.com/jfrog/jfrog-cli-core/v2/common/cliutils" "github.com/jfrog/jfrog-cli-core/v2/common/commands" - "github.com/jfrog/jfrog-cli-core/v2/common/project" "github.com/jfrog/jfrog-cli-core/v2/common/spec" commontests "github.com/jfrog/jfrog-cli-core/v2/common/tests" "github.com/jfrog/jfrog-cli-core/v2/utils/config" @@ -1543,8 +1542,7 @@ func TestArtifactorySelfSignedCert(t *testing.T) { if reader != nil { readerCloseAndAssert(t, reader) } - _, isUrlErr := err.(*url.Error) - assert.True(t, isUrlErr, "Expected a connection failure, since reverse proxy didn't load self-signed-certs. Connection however is successful", err) + assert.ErrorContains(t, err, "certificate", "Expected a connection failure, since reverse proxy didn't load self-signed-certs. Connection however is successful") // Set insecureTls to true and run again. We expect the command to succeed. serverDetails.InsecureTls = true @@ -1606,8 +1604,7 @@ func TestArtifactoryClientCert(t *testing.T) { if reader != nil { readerCloseAndAssert(t, reader) } - _, isUrlErr := err.(*url.Error) - assert.True(t, isUrlErr, "Expected a connection failure, since client did not provide a client certificate. Connection however is successful") + assert.ErrorContains(t, err, "certificate", "Expected a connection failure, since client did not provide a client certificate. Connection however is successful") // Inject client certificates, we expect the search to succeed serverDetails.ClientCertPath = certificate.CertFile @@ -1747,8 +1744,8 @@ func testArtifactoryProxy(t *testing.T, isHttps bool) { func prepareArtifactoryUrlForProxyTest(t *testing.T) string { rtUrl, err := url.Parse(serverDetails.ArtifactoryUrl) assert.NoError(t, err) - rtHost, port, err := net.SplitHostPort(rtUrl.Host) - assert.NoError(t, err) + rtHost := rtUrl.Hostname() + port := rtUrl.Port() if rtHost == "localhost" || rtHost == "127.0.0.1" { externalIp, err := getExternalIP() assert.NoError(t, err) @@ -1764,13 +1761,12 @@ func checkForErrDueToMissingProxy(spec *spec.SpecFiles, t *testing.T) { if reader != nil { readerCloseAndAssert(t, reader) } - _, isUrlErr := err.(*url.Error) - assert.True(t, isUrlErr, "Expected the request to fails, since the proxy is down.", err) + assert.ErrorContains(t, err, "proxy", "Expected the request to fails, since the proxy is down.", err) } func checkIfServerIsUp(port, proxyScheme string, useClientCerts bool) error { tr := &http.Transport{ - //#nosec G402 + //#nosec G402 jfrog-ignore - false positive TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, } @@ -1797,6 +1793,7 @@ func checkIfServerIsUp(port, proxyScheme string, useClientCerts bool) error { } tr.TLSClientConfig.Certificates = []tls.Certificate{cert} } + // jfrog-ignore - false positive client := &http.Client{Transport: tr} for attempt := 0; attempt < 20; attempt++ { @@ -2092,7 +2089,7 @@ func TestArtifactoryUploadExcludeByCli2Regex(t *testing.T) { assert.NoError(t, err, "Couldn't create file") // Upload files - runRt(t, "upload", filepath.ToSlash(absDirPath)+"(.*)", tests.RtRepo1, "--exclusions=(.*c)liTestFile1.*", "--regexp=true", "--flat=true") + runRt(t, "upload", filepath.ToSlash(absDirPath)+"/(.*)", tests.RtRepo1, "--exclusions=(.*c)liTestFile1.*", "--regexp=true", "--flat=true") // Check files exists in artifactory searchFilePath, err := tests.CreateSpec(tests.SearchRepo1ByInSuffix) @@ -3442,114 +3439,6 @@ func TestArtifactoryDownloadByBuildNoPatternUsingSimpleDownload(t *testing.T) { cleanArtifactoryTest() } -func TestArtifactoryDownloadByArchiveEntriesCli(t *testing.T) { - initArtifactoryTest(t, "") - uploadSpecFile, err := tests.CreateSpec(tests.ArchiveEntriesUpload) - assert.NoError(t, err) - - // Upload archives - runRt(t, "upload", "--spec="+uploadSpecFile) - - // Trigger archive indexing on the repo. - triggerArchiveIndexing(t) - - // Create executor for running with retries - retryExecutor := createRetryExecutorForArchiveEntries(tests.GetBuildArchiveEntriesDownloadCli(), - []string{"dl", tests.RtRepo1, "out/", "--archive-entries=(*)c1.in", "--flat=true"}) - - // Perform download by archive-entries only the archives containing c1.in, and validate results - assert.NoError(t, retryExecutor.Execute()) - - // Cleanup - cleanArtifactoryTest() -} - -func triggerArchiveIndexing(t *testing.T) { - client, err := httpclient.ClientBuilder().Build() - assert.NoError(t, err) - resp, _, err := client.SendPost(serverDetails.ArtifactoryUrl+"api/archiveIndex/"+tests.RtRepo1, []byte{}, artHttpDetails, "") - if err != nil { - assert.NoError(t, err, "archive indexing failed") - return - } - assert.Equal(t, http.StatusAccepted, resp.StatusCode, "archive indexing failed") - // Indexing buffer - time.Sleep(3 * time.Second) -} - -func TestArtifactoryDownloadByArchiveEntriesSpecificPathCli(t *testing.T) { - initArtifactoryTest(t, "") - uploadSpecFile, err := tests.CreateSpec(tests.ArchiveEntriesUpload) - assert.NoError(t, err) - - // Upload archives - runRt(t, "upload", "--spec="+uploadSpecFile) - - // Trigger archive indexing on the repo. - triggerArchiveIndexing(t) - - // Create executor for running with retries - retryExecutor := createRetryExecutorForArchiveEntries(tests.GetBuildArchiveEntriesSpecificPathDownload(), - []string{"dl", tests.RtRepo1, "out/", "--archive-entries=b/c/c1.in", "--flat=true"}) - - // Perform download by archive-entries only the archives containing c1.in, and validate results - assert.NoError(t, retryExecutor.Execute()) - - // Cleanup - cleanArtifactoryTest() -} - -func TestArtifactoryDownloadByArchiveEntriesSpec(t *testing.T) { - initArtifactoryTest(t, "") - uploadSpecFile, err := tests.CreateSpec(tests.ArchiveEntriesUpload) - assert.NoError(t, err) - downloadSpecFile, err := tests.CreateSpec(tests.ArchiveEntriesDownload) - assert.NoError(t, err) - - // Upload archives - runRt(t, "upload", "--spec="+uploadSpecFile) - - // Trigger archive indexing on the repo. - triggerArchiveIndexing(t) - - // Create executor for running with retries - retryExecutor := createRetryExecutorForArchiveEntries(tests.GetBuildArchiveEntriesDownloadSpec(), - []string{"dl", "--spec=" + downloadSpecFile}) - - // Perform download by archive-entries only the archives containing d1.in, and validate results - assert.NoError(t, retryExecutor.Execute()) - - // Cleanup - cleanArtifactoryTest() -} - -func createRetryExecutorForArchiveEntries(expected []string, args []string) *clientutils.RetryExecutor { - return &clientutils.RetryExecutor{ - MaxRetries: 120, - // RetriesIntervalMilliSecs in milliseconds - RetriesIntervalMilliSecs: 1 * 1000, - ErrorMessage: "Waiting for Artifactory to index archives...", - ExecutionHandler: func() (bool, error) { - // Execute the requested cli command - err := artifactoryCli.Exec(args...) - if err != nil { - return true, err - } - err = validateDownloadByArchiveEntries(expected) - if err != nil { - return false, err - } - return false, nil - }, - } -} - -func validateDownloadByArchiveEntries(expected []string) error { - // Validate files are downloaded as expected - paths, _ := fileutils.ListFilesRecursiveWalkIntoDirSymlink(tests.Out, false) - return tests.ValidateListsIdentical(expected, paths) -} - func TestArtifactoryDownloadExcludeByCli(t *testing.T) { initArtifactoryTest(t, "") @@ -5652,82 +5541,6 @@ func readerGetErrorAndAssert(t *testing.T, reader *content.ContentReader) { assert.NoError(t, reader.GetError(), "Couldn't get reader error") } -func TestProjectInitMaven(t *testing.T) { - testProjectInit(t, "multiproject", coreutils.Maven) -} - -func TestProjectInitGradle(t *testing.T) { - testProjectInit(t, "gradleproject", coreutils.Gradle) -} - -func TestProjectInitNpm(t *testing.T) { - testProjectInit(t, "npmproject", coreutils.Npm) -} - -func TestProjectInitGo(t *testing.T) { - testProjectInit(t, "dependency", coreutils.Go) -} - -func TestProjectInitPip(t *testing.T) { - testProjectInit(t, "requirementsproject", coreutils.Pip) -} - -func TestProjectInitNuget(t *testing.T) { - testProjectInit(t, "multipackagesconfig", coreutils.Nuget) -} - -func testProjectInit(t *testing.T, projectExampleName string, technology coreutils.Technology) { - initArtifactoryTest(t, "") - defer cleanArtifactoryTest() - // Create temp JFrog home dir - tmpHomeDir, deleteHomeDir := coretests.CreateTempDirWithCallbackAndAssert(t) - defer deleteHomeDir() - clientTestUtils.SetEnvAndAssert(t, coreutils.HomeDir, tmpHomeDir) - _, err := createServerConfigAndReturnPassphrase(t) - assert.NoError(t, err) - - // Copy a simple project in a temp work dir - tmpWorkDir, deleteWorkDir := coretests.CreateTempDirWithCallbackAndAssert(t) - defer deleteWorkDir() - testdataSrc := filepath.Join(filepath.FromSlash(tests.GetTestResourcesPath()), technology.String(), projectExampleName) - err = biutils.CopyDir(testdataSrc, tmpWorkDir, true, nil) - assert.NoError(t, err) - if technology == coreutils.Go { - goModeOriginalPath := filepath.Join(tmpWorkDir, "createGoProject_go.mod_suffix") - goModeTargetPath := filepath.Join(tmpWorkDir, "go.mod") - assert.NoError(t, os.Rename(goModeOriginalPath, goModeTargetPath)) - } - - // Run cd command to temp dir. - currentWd, err := os.Getwd() - assert.NoError(t, err) - changeDirBack := clientTestUtils.ChangeDirWithCallback(t, currentWd, tmpWorkDir) - defer changeDirBack() - // Run JFrog project init - err = platformCli.WithoutCredentials().Exec("project", "init", "--path", tmpWorkDir, "--server-id="+tests.ServerId) - assert.NoError(t, err) - // Validate correctness of .jfrog/projects/$technology.yml - validateProjectYamlFile(t, tmpWorkDir, technology.String()) - // Validate correctness of .jfrog/projects/build.yml - validateBuildYamlFile(t, tmpWorkDir) -} - -func validateProjectYamlFile(t *testing.T, projectDir, technology string) { - techConfig, err := project.ReadConfigFile(filepath.Join(projectDir, ".jfrog", "projects", technology+".yaml"), project.YAML) - if assert.NoError(t, err) { - assert.Equal(t, technology, techConfig.GetString("type")) - assert.Equal(t, tests.ServerId, techConfig.GetString("resolver.serverId")) - assert.Equal(t, tests.ServerId, techConfig.GetString("deployer.serverId")) - } -} - -func validateBuildYamlFile(t *testing.T, projectDir string) { - techConfig, err := project.ReadConfigFile(filepath.Join(projectDir, ".jfrog", "projects", "build.yaml"), project.YAML) - assert.NoError(t, err) - assert.Equal(t, "build", techConfig.GetString("type")) - assert.Equal(t, filepath.Base(projectDir+"/"), techConfig.GetString("name")) -} - func TestTerraformPublish(t *testing.T) { testTerraformPublish(t, false) } diff --git a/build/deb_rpm/v2-jf/build-scripts/pack.sh b/build/deb_rpm/v2-jf/build-scripts/pack.sh index 7a02590aa..c08c3f0fc 100755 --- a/build/deb_rpm/v2-jf/build-scripts/pack.sh +++ b/build/deb_rpm/v2-jf/build-scripts/pack.sh @@ -2,10 +2,6 @@ # This file is responsible for building rpm and deb package for jfrog-cli installer -# This will contain hold the list of supported architectures which can be built by default. -# Although by passing a different --rpm-build-image or --rpm-build-image, artifacts of different architectures can be built -SUPPORTED_DEFAULT_ARCH_LIST="x86_64" - JFROG_CLI_HOME="$(cd "$(dirname "${BASH_SOURCE[0]}")/../" && pwd)" JFROG_CLI_PKG="$JFROG_CLI_HOME/pkg" JFROG_CLI_PREFIX="jfrog-cli" @@ -244,22 +240,6 @@ createPackage(){ esac } -setBuildImage(){ - local arch="$1" - - [ -n "${arch}" ] || errorExit "Architecture is not passed to setBuildImage method" - - case "$1" in - x86_64) - RPM_BUILD_IMAGE="centos:7" - DEB_BUILD_IMAGE="ubuntu:16.04" - ;; - *) - errorExit "Provided architecture is not supported : $arch. Supported list [ ${SUPPORTED_DEFAULT_ARCH_LIST} ]" - ;; - esac -} - main(){ while [[ $# -gt 0 ]]; do case "$1" in @@ -275,10 +255,6 @@ main(){ JFROG_CLI_VERSION="$2" shift 2 ;; - --arch) - setBuildImage "$2" - shift 2 - ;; --rpm-arch) JFROG_CLI_RPM_ARCH="$2" shift 2 @@ -322,11 +298,10 @@ main(){ esac done + : "${flavours:="rpm deb"}" : "${JFROG_CLI_RUN_TEST:="false"}" - : "${RPM_BUILD_IMAGE:="centos:8"}" - : "${RPM_SIGN_IMAGE:="centos:7"}" - : "${DEB_BUILD_IMAGE:="ubuntu:16.04"}" + : "${RPM_SIGN_IMAGE:="${RPM_BUILD_IMAGE}"}" : "${DEB_TEST_IMAGE:="${DEB_BUILD_IMAGE}"}" : "${RPM_TEST_IMAGE:="${RPM_BUILD_IMAGE}"}" : "${JFROG_CLI_RELEASE_VERSION:="1"}" diff --git a/build/deb_rpm/v2-jf/build-scripts/rpm-sign.sh b/build/deb_rpm/v2-jf/build-scripts/rpm-sign.sh index f76d1d0c7..05bb219a2 100755 --- a/build/deb_rpm/v2-jf/build-scripts/rpm-sign.sh +++ b/build/deb_rpm/v2-jf/build-scripts/rpm-sign.sh @@ -1,30 +1,48 @@ #!/bin/bash log(){ - echo "$1" + echo "$1" +} + +debug_info(){ + echo "=== DEBUG INFO ===" + echo "Current User: $(whoami)" + echo "GPG Version: $(gpg --version)" + echo "GPG_TTY: $GPG_TTY" + echo "TTY: $(tty)" + echo "Files in /root/.gnupg:" + ls -la /root/.gnupg + echo "Environment Variables:" + env + echo "===================" } -# Use the given key to configure the rpm macro. This is needed to sign an rpm. -# Arguments: -# - gpgKeyFile : key file location (in PEM format) to be used for signing the rpm -# The structure of the key content should be as follows, -# -----BEGIN PGP PUBLIC KEY BLOCK----- -# Version: GnuPG v1.4.7 (MingW32) -# ..... -# -----END PGP PUBLIC KEY BLOCK----- -# -----BEGIN PGP PRIVATE KEY BLOCK----- -# Version: GnuPG v1.4.7 (MingW32) -# ..... -# -----END PGP PRIVATE KEY BLOCK----- -# - keyID : id of the provided key rpmInitSigning(){ local gpgKeyFile="${KEY_FILE}" local keyID="${KEY_ID}" log "Initializing rpm sign..." - gpg --allow-secret-key-import --import "${gpgKeyFile}" && \ - gpg --export -a "${keyID}" > /tmp/tmpFile && \ + # Start the GPG agent + local gpg_agent_output + gpg_agent_output=$(gpg-agent --daemon --allow-preset-passphrase) + eval "$gpg_agent_output" + + # Set GPG_TTY if possible + local tty_value + if tty -s; then + tty_value=$(tty) + export GPG_TTY="$tty_value" + else + export GPG_TTY="/dev/null" + fi + + # Debug info + debug_info + + # Import the GPG key + gpg --batch --import "${gpgKeyFile}" || { echo "ERROR: Failed to import GPG key"; exit 1; } + gpg --batch --export -a "${keyID}" > /tmp/tmpFile || { echo "ERROR: Failed to export GPG key"; exit 1; } if rpm --import /tmp/tmpFile && rpm -q gpg-pubkey --qf '%{name}-%{version}-%{release} --> %{summary}\n' | grep "${keyID}"; then echo "RPM signature initialization succeeded." else @@ -32,8 +50,7 @@ rpmInitSigning(){ exit 1 fi - rpmEditRpmMacro "${keyID}" || \ - { echo "ERROR: Configuring rpm macro failed!" >&2; exit 1; } + rpmEditRpmMacro "${keyID}" || { echo "ERROR: Configuring rpm macro failed!" >&2; exit 1; } } rpmEditRpmMacro(){ @@ -44,27 +61,21 @@ rpmEditRpmMacro(){ %_gpg_path /root/.gnupg %_gpg_name ${keyID} %_gpgbin /usr/bin/gpg +%_gpg_sign_cmd %{__gpg} gpg --batch --pinentry-mode loopback --passphrase-file /tmp/passphrase --detach-sign --armor --yes --no-secmem-warning -u %{_gpg_name} -o %{__signature_filename} %{__plaintext_filename} RPM_MACRO_CONTENT } -expect_script() { - cat << End-of-text #No white space between << and End-of-text -spawn rpm --resign $RPM_FILE_SIGNED -expect -exact "Enter pass phrase: " -send -- "$PASSPHRASE\r" -expect eof -exit -End-of-text - -} - sign_rpm() { echo "Signing RPM..." - cp -f "${RPM_FILE}" "${RPM_FILE_SIGNED}" || \ - { echo "ERROR: Copying ${RPM_FILE} to ${RPM_FILE_SIGNED} failed! " >&2; exit 1; } - expect_script | /usr/bin/expect -f - - cp -f "${RPM_FILE_SIGNED}" "${RPM_FILE}" || \ - { echo "ERROR: Copying ${RPM_FILE_SIGNED} to ${RPM_FILE} failed! " >&2; exit 1; } + echo "${PASSPHRASE}" > /tmp/passphrase + cp -f "${RPM_FILE}" "${RPM_FILE_SIGNED}" || { echo "ERROR: Copying ${RPM_FILE} to ${RPM_FILE_SIGNED} failed! " >&2; exit 1; } + + gpg --batch --pinentry-mode loopback --passphrase-file /tmp/passphrase --detach-sign --armor --yes --no-secmem-warning -u "${KEY_ID}" -o "${RPM_FILE_SIGNED}.asc" "${RPM_FILE_SIGNED}" || { echo "ERROR: GPG signing failed!"; exit 1; } + + rpm --addsign "${RPM_FILE_SIGNED}" || { echo "ERROR: RPM signing failed!"; exit 1; } + + cp -f "${RPM_FILE_SIGNED}" "${RPM_FILE}" || { echo "ERROR: Copying ${RPM_FILE_SIGNED} to ${RPM_FILE} failed! " >&2; exit 1; } + rm /tmp/passphrase } KEY_FILE="${1}" @@ -72,5 +83,6 @@ KEY_ID="${2}" export PASSPHRASE="${3}" RPM_FILE="${4}" RPM_FILE_SIGNED="/tmp/jfrog-cli-rpm-signed.rpm" + rpmInitSigning sign_rpm diff --git a/build/deb_rpm/v2/build-scripts/pack.sh b/build/deb_rpm/v2/build-scripts/pack.sh index 90e904962..0d48817dd 100755 --- a/build/deb_rpm/v2/build-scripts/pack.sh +++ b/build/deb_rpm/v2/build-scripts/pack.sh @@ -2,10 +2,6 @@ # This file is responsible for building rpm and deb package for jfrog-cli installer -# This will contain hold the list of supported architectures which can be built by default. -# Although by passing a different --rpm-build-image or --rpm-build-image, artifacts of different architectures can be built -SUPPORTED_DEFAULT_ARCH_LIST="x86_64" - JFROG_CLI_HOME="$(cd "$(dirname "${BASH_SOURCE[0]}")/../" && pwd)" JFROG_CLI_PKG="$JFROG_CLI_HOME/pkg" JFROG_CLI_PREFIX="jfrog-cli" @@ -46,11 +42,11 @@ errorExit() { } checkDockerAccess() { -if docker -v > /dev/null 2>&1 && docker ps > /dev/null 2>&1; then - log "Docker is available" "DEBUG" -else - errorExit "Must run as a user that can execute docker commands" -fi + if docker -v > /dev/null 2>&1 && docker ps > /dev/null 2>&1; then + log "Docker is available" "DEBUG" + else + errorExit "Must run as a user that can execute docker commands" + fi } exitWithUsage(){ @@ -63,7 +59,7 @@ createDEBPackage(){ local flavour="deb" # cleanup old files and containers - rm -f "${JFROG_CLI_PKG}/${JFROG_CLI_PREFIX}*${VERSION_FORMATTED}*.${flavour}" + rm -f "${JFROG_CLI_PKG}/${JFROG_CLI_PREFIX}*${VERSION_FORMATTED}*.${flavour}" docker rm -f "${RPM_BUILDER_NAME}" 2>/dev/null log "Building ${JFROG_CLI_PREFIX} ${flavour} ${JFROG_CLI_VERSION} on ${DEB_BUILD_IMAGE} image" @@ -244,22 +240,6 @@ createPackage(){ esac } -setBuildImage(){ - local arch="$1" - - [ -n "${arch}" ] || errorExit "Architecture is not passed to setBuildImage method" - - case "$1" in - x86_64) - RPM_BUILD_IMAGE="centos:7" - DEB_BUILD_IMAGE="ubuntu:16.04" - ;; - *) - errorExit "Provided architecture is not supported : $arch. Supported list [ ${SUPPORTED_DEFAULT_ARCH_LIST} ]" - ;; - esac -} - main(){ while [[ $# -gt 0 ]]; do case "$1" in @@ -275,10 +255,6 @@ main(){ JFROG_CLI_VERSION="$2" shift 2 ;; - --arch) - setBuildImage "$2" - shift 2 - ;; --rpm-arch) JFROG_CLI_RPM_ARCH="$2" shift 2 @@ -322,11 +298,10 @@ main(){ esac done + : "${flavours:="rpm deb"}" : "${JFROG_CLI_RUN_TEST:="false"}" - : "${RPM_BUILD_IMAGE:="centos:8"}" - : "${RPM_SIGN_IMAGE:="centos:7"}" - : "${DEB_BUILD_IMAGE:="ubuntu:16.04"}" + : "${RPM_SIGN_IMAGE:="${RPM_BUILD_IMAGE}"}" : "${DEB_TEST_IMAGE:="${DEB_BUILD_IMAGE}"}" : "${RPM_TEST_IMAGE:="${RPM_BUILD_IMAGE}"}" : "${JFROG_CLI_RELEASE_VERSION:="1"}" diff --git a/build/deb_rpm/v2/build-scripts/rpm-sign.sh b/build/deb_rpm/v2/build-scripts/rpm-sign.sh index f76d1d0c7..05bb219a2 100755 --- a/build/deb_rpm/v2/build-scripts/rpm-sign.sh +++ b/build/deb_rpm/v2/build-scripts/rpm-sign.sh @@ -1,30 +1,48 @@ #!/bin/bash log(){ - echo "$1" + echo "$1" +} + +debug_info(){ + echo "=== DEBUG INFO ===" + echo "Current User: $(whoami)" + echo "GPG Version: $(gpg --version)" + echo "GPG_TTY: $GPG_TTY" + echo "TTY: $(tty)" + echo "Files in /root/.gnupg:" + ls -la /root/.gnupg + echo "Environment Variables:" + env + echo "===================" } -# Use the given key to configure the rpm macro. This is needed to sign an rpm. -# Arguments: -# - gpgKeyFile : key file location (in PEM format) to be used for signing the rpm -# The structure of the key content should be as follows, -# -----BEGIN PGP PUBLIC KEY BLOCK----- -# Version: GnuPG v1.4.7 (MingW32) -# ..... -# -----END PGP PUBLIC KEY BLOCK----- -# -----BEGIN PGP PRIVATE KEY BLOCK----- -# Version: GnuPG v1.4.7 (MingW32) -# ..... -# -----END PGP PRIVATE KEY BLOCK----- -# - keyID : id of the provided key rpmInitSigning(){ local gpgKeyFile="${KEY_FILE}" local keyID="${KEY_ID}" log "Initializing rpm sign..." - gpg --allow-secret-key-import --import "${gpgKeyFile}" && \ - gpg --export -a "${keyID}" > /tmp/tmpFile && \ + # Start the GPG agent + local gpg_agent_output + gpg_agent_output=$(gpg-agent --daemon --allow-preset-passphrase) + eval "$gpg_agent_output" + + # Set GPG_TTY if possible + local tty_value + if tty -s; then + tty_value=$(tty) + export GPG_TTY="$tty_value" + else + export GPG_TTY="/dev/null" + fi + + # Debug info + debug_info + + # Import the GPG key + gpg --batch --import "${gpgKeyFile}" || { echo "ERROR: Failed to import GPG key"; exit 1; } + gpg --batch --export -a "${keyID}" > /tmp/tmpFile || { echo "ERROR: Failed to export GPG key"; exit 1; } if rpm --import /tmp/tmpFile && rpm -q gpg-pubkey --qf '%{name}-%{version}-%{release} --> %{summary}\n' | grep "${keyID}"; then echo "RPM signature initialization succeeded." else @@ -32,8 +50,7 @@ rpmInitSigning(){ exit 1 fi - rpmEditRpmMacro "${keyID}" || \ - { echo "ERROR: Configuring rpm macro failed!" >&2; exit 1; } + rpmEditRpmMacro "${keyID}" || { echo "ERROR: Configuring rpm macro failed!" >&2; exit 1; } } rpmEditRpmMacro(){ @@ -44,27 +61,21 @@ rpmEditRpmMacro(){ %_gpg_path /root/.gnupg %_gpg_name ${keyID} %_gpgbin /usr/bin/gpg +%_gpg_sign_cmd %{__gpg} gpg --batch --pinentry-mode loopback --passphrase-file /tmp/passphrase --detach-sign --armor --yes --no-secmem-warning -u %{_gpg_name} -o %{__signature_filename} %{__plaintext_filename} RPM_MACRO_CONTENT } -expect_script() { - cat << End-of-text #No white space between << and End-of-text -spawn rpm --resign $RPM_FILE_SIGNED -expect -exact "Enter pass phrase: " -send -- "$PASSPHRASE\r" -expect eof -exit -End-of-text - -} - sign_rpm() { echo "Signing RPM..." - cp -f "${RPM_FILE}" "${RPM_FILE_SIGNED}" || \ - { echo "ERROR: Copying ${RPM_FILE} to ${RPM_FILE_SIGNED} failed! " >&2; exit 1; } - expect_script | /usr/bin/expect -f - - cp -f "${RPM_FILE_SIGNED}" "${RPM_FILE}" || \ - { echo "ERROR: Copying ${RPM_FILE_SIGNED} to ${RPM_FILE} failed! " >&2; exit 1; } + echo "${PASSPHRASE}" > /tmp/passphrase + cp -f "${RPM_FILE}" "${RPM_FILE_SIGNED}" || { echo "ERROR: Copying ${RPM_FILE} to ${RPM_FILE_SIGNED} failed! " >&2; exit 1; } + + gpg --batch --pinentry-mode loopback --passphrase-file /tmp/passphrase --detach-sign --armor --yes --no-secmem-warning -u "${KEY_ID}" -o "${RPM_FILE_SIGNED}.asc" "${RPM_FILE_SIGNED}" || { echo "ERROR: GPG signing failed!"; exit 1; } + + rpm --addsign "${RPM_FILE_SIGNED}" || { echo "ERROR: RPM signing failed!"; exit 1; } + + cp -f "${RPM_FILE_SIGNED}" "${RPM_FILE}" || { echo "ERROR: Copying ${RPM_FILE_SIGNED} to ${RPM_FILE} failed! " >&2; exit 1; } + rm /tmp/passphrase } KEY_FILE="${1}" @@ -72,5 +83,6 @@ KEY_ID="${2}" export PASSPHRASE="${3}" RPM_FILE="${4}" RPM_FILE_SIGNED="/tmp/jfrog-cli-rpm-signed.rpm" + rpmInitSigning sign_rpm diff --git a/build/docker/full/Dockerfile b/build/docker/full/Dockerfile index 289eecb3f..9ba205b95 100644 --- a/build/docker/full/Dockerfile +++ b/build/docker/full/Dockerfile @@ -1,6 +1,6 @@ ARG repo_name_21 # Remove ${repo_name_21} to pull from Docker Hub. -FROM ${repo_name_21}/jfrog-docker/golang:1.22 as builder +FROM ${repo_name_21}/jfrog-docker/golang:1.23 as builder ARG image_name=jfrog-cli-full ARG cli_executable_name WORKDIR /${image_name} diff --git a/build/docker/slim/Dockerfile b/build/docker/slim/Dockerfile index 5233a175c..a915326c2 100644 --- a/build/docker/slim/Dockerfile +++ b/build/docker/slim/Dockerfile @@ -1,6 +1,6 @@ ARG repo_name_21 # Remove ${repo_name_21} to pull from Docker Hub. -FROM ${repo_name_21}/jfrog-docker/golang:1.22-alpine as builder +FROM ${repo_name_21}/jfrog-docker/golang:1.23.3-alpine as builder ARG image_name=jfrog-cli ARG cli_executable_name WORKDIR /${image_name} diff --git a/build/npm/v2-jf/package-lock.json b/build/npm/v2-jf/package-lock.json index 03260e7f3..d4b178d50 100644 --- a/build/npm/v2-jf/package-lock.json +++ b/build/npm/v2-jf/package-lock.json @@ -1,5 +1,5 @@ { "name": "jfrog-cli-v2-jf", - "version": "2.56.1", + "version": "2.71.3", "lockfileVersion": 1 } diff --git a/build/npm/v2-jf/package.json b/build/npm/v2-jf/package.json index c50e4f9e3..66a80fec1 100644 --- a/build/npm/v2-jf/package.json +++ b/build/npm/v2-jf/package.json @@ -1,6 +1,6 @@ { "name": "jfrog-cli-v2-jf", - "version": "2.56.1", + "version": "2.71.3", "description": "🐸 Command-line interface for JFrog Artifactory, Xray, Distribution, Pipelines and Mission Control 🐸", "homepage": "https://github.com/jfrog/jfrog-cli", "preferGlobal": true, diff --git a/build/npm/v2/package-lock.json b/build/npm/v2/package-lock.json index d02acb3ac..1ad0735b1 100644 --- a/build/npm/v2/package-lock.json +++ b/build/npm/v2/package-lock.json @@ -1,5 +1,5 @@ { "name": "jfrog-cli-v2", - "version": "2.56.1", + "version": "2.71.3", "lockfileVersion": 2 } diff --git a/build/npm/v2/package.json b/build/npm/v2/package.json index f28f79fb2..643e0fb76 100644 --- a/build/npm/v2/package.json +++ b/build/npm/v2/package.json @@ -1,6 +1,6 @@ { "name": "jfrog-cli-v2", - "version": "2.56.1", + "version": "2.71.3", "description": "🐸 Command-line interface for JFrog Artifactory, Xray, Distribution, Pipelines and Mission Control 🐸", "homepage": "https://github.com/jfrog/jfrog-cli", "preferGlobal": true, diff --git a/buildinfo_test.go b/buildinfo_test.go index b783aa706..c72eac195 100644 --- a/buildinfo_test.go +++ b/buildinfo_test.go @@ -874,7 +874,7 @@ func validateBuildAddDepsBuildInfo(t *testing.T, buildInfoTestParams buildAddDep return } buildInfo := publishedBuildInfo.BuildInfo - if buildInfo.Modules == nil || len(buildInfo.Modules) == 0 { + if len(buildInfo.Modules) == 0 { buildInfoString, err := json.Marshal(buildInfo) assert.NoError(t, err) // Case no module was not created diff --git a/buildtools/cli.go b/buildtools/cli.go index 9dfc3de4b..fa7d87993 100644 --- a/buildtools/cli.go +++ b/buildtools/cli.go @@ -3,6 +3,7 @@ package buildtools import ( "errors" "fmt" + "github.com/jfrog/jfrog-cli-security/utils/techutils" "os" "strconv" "strings" @@ -32,6 +33,7 @@ import ( "github.com/jfrog/jfrog-cli-security/commands/scan" terraformdocs "github.com/jfrog/jfrog-cli/docs/artifactory/terraform" "github.com/jfrog/jfrog-cli/docs/artifactory/terraformconfig" + twinedocs "github.com/jfrog/jfrog-cli/docs/artifactory/twine" "github.com/jfrog/jfrog-cli/docs/buildtools/docker" dotnetdocs "github.com/jfrog/jfrog-cli/docs/buildtools/dotnet" "github.com/jfrog/jfrog-cli/docs/buildtools/dotnetconfig" @@ -91,7 +93,10 @@ func GetCommands() []cli.Command { SkipFlagParsing: true, BashComplete: corecommon.CreateBashCompletionFunc(), Category: buildToolsCategory, - Action: MvnCmd, + Action: func(c *cli.Context) (err error) { + cmdName, _ := getCommandName(c.Args()) + return securityCLI.WrapCmdWithCurationPostFailureRun(c, MvnCmd, techutils.Maven, cmdName) + }, }, { Name: "gradle-config", @@ -215,7 +220,10 @@ func GetCommands() []cli.Command { SkipFlagParsing: true, BashComplete: corecommon.CreateBashCompletionFunc(), Category: buildToolsCategory, - Action: GoCmd, + Action: func(c *cli.Context) (err error) { + cmdName, _ := getCommandName(c.Args()) + return securityCLI.WrapCmdWithCurationPostFailureRun(c, GoCmd, techutils.Go, cmdName) + }, }, { Name: "go-publish", @@ -252,7 +260,10 @@ func GetCommands() []cli.Command { SkipFlagParsing: true, BashComplete: corecommon.CreateBashCompletionFunc(), Category: buildToolsCategory, - Action: PipCmd, + Action: func(c *cli.Context) (err error) { + cmdName, _ := getCommandName(c.Args()) + return securityCLI.WrapCmdWithCurationPostFailureRun(c, PipCmd, techutils.Pip, cmdName) + }, }, { Name: "pipenv-config", @@ -325,9 +336,13 @@ func GetCommands() []cli.Command { SkipFlagParsing: true, BashComplete: corecommon.CreateBashCompletionFunc("install", "i", "isntall", "add", "ci", "publish", "p"), Category: buildToolsCategory, - Action: func(c *cli.Context) error { + Action: func(c *cli.Context) (errFromCmd error) { cmdName, _ := getCommandName(c.Args()) - return npmGenericCmd(c, cmdName, false) + return securityCLI.WrapCmdWithCurationPostFailureRun(c, + func(c *cli.Context) error { + return npmGenericCmd(c, cmdName, false) + }, + techutils.Npm, cmdName) }, }, { @@ -388,6 +403,18 @@ func GetCommands() []cli.Command { Category: buildToolsCategory, Action: terraformCmd, }, + { + Name: "twine", + Flags: cliutils.GetCommandFlags(cliutils.Twine), + Usage: twinedocs.GetDescription(), + HelpName: corecommon.CreateUsage("twine", twinedocs.GetDescription(), twinedocs.Usage), + UsageText: twinedocs.GetArguments(), + ArgsUsage: common.CreateEnvVars(), + SkipFlagParsing: true, + BashComplete: corecommon.CreateBashCompletionFunc(), + Category: buildToolsCategory, + Action: twineCmd, + }, }) } @@ -396,13 +423,11 @@ func MvnCmd(c *cli.Context) (err error) { return err } - configFilePath, exists, err := project.GetProjectConfFilePath(project.Maven) + configFilePath, err := getProjectConfigPathOrThrow(project.Maven, "mvn", "mvn-config") if err != nil { return err } - if !exists { - return errors.New("no config file was found! Before running the mvn command on a project for the first time, the project should be configured with the mvn-config command") - } + if c.NArg() < 1 { return cliutils.WrongNumberOfArgumentsHandler(c) } @@ -455,13 +480,11 @@ func GradleCmd(c *cli.Context) (err error) { return err } - configFilePath, exists, err := project.GetProjectConfFilePath(project.Gradle) + configFilePath, err := getProjectConfigPathOrThrow(project.Gradle, "gradle", "gradle-config") if err != nil { return err } - if !exists { - return errors.New("no config file was found! Before running the gradle command on a project for the first time, the project should be configured with the gradle-config command") - } + // Found a config file. Continue as native command. if c.NArg() < 1 { return cliutils.WrongNumberOfArgumentsHandler(c) @@ -511,13 +534,10 @@ func YarnCmd(c *cli.Context) error { return err } - configFilePath, exists, err := project.GetProjectConfFilePath(project.Yarn) + configFilePath, err := getProjectConfigPathOrThrow(project.Yarn, "yarn", "yarn-config") if err != nil { return err } - if !exists { - return fmt.Errorf("no config file was found! Before running the yarn command on a project for the first time, the project should be configured using the yarn-config command") - } yarnCmd := yarn.NewYarnCommand().SetConfigFilePath(configFilePath).SetArgs(c.Args()) return commands.Exec(yarnCmd) @@ -530,15 +550,12 @@ func NugetCmd(c *cli.Context) error { if c.NArg() < 1 { return cliutils.WrongNumberOfArgumentsHandler(c) } - configFilePath, exists, err := project.GetProjectConfFilePath(project.Nuget) + + configFilePath, err := getProjectConfigPathOrThrow(project.Nuget, "nuget", "nuget-config") if err != nil { return err } - if !exists { - return fmt.Errorf("no config file was found! Before running the nuget command on a project for the first time, the project should be configured using the nuget-config command") - } - rtDetails, targetRepo, useNugetV2, err := getNugetAndDotnetConfigFields(configFilePath) if err != nil { return err @@ -570,13 +587,10 @@ func DotnetCmd(c *cli.Context) error { } // Get configuration file path. - configFilePath, exists, err := project.GetProjectConfFilePath(project.Dotnet) + configFilePath, err := getProjectConfigPathOrThrow(project.Dotnet, "dotnet", "dotnet-config") if err != nil { return err } - if !exists { - return fmt.Errorf("no config file was found! Before running the dotnet command on a project for the first time, the project should be configured using the dotnet-config command") - } rtDetails, targetRepo, useNugetV2, err := getNugetAndDotnetConfigFields(configFilePath) if err != nil { @@ -639,6 +653,9 @@ func extractThreadsFlag(args []string) (cleanArgs []string, threadsCount int, er } func GoCmd(c *cli.Context) error { + if show, err := cliutils.ShowCmdHelpIfNeeded(c, c.Args()); show || err != nil { + return err + } configFilePath, err := goCmdVerification(c) if err != nil { return err @@ -650,6 +667,9 @@ func GoCmd(c *cli.Context) error { } func GoPublishCmd(c *cli.Context) (err error) { + if show, err := cliutils.ShowCmdHelpIfNeeded(c, c.Args()); show || err != nil { + return err + } configFilePath, err := goCmdVerification(c) if err != nil { return err @@ -670,20 +690,15 @@ func GoPublishCmd(c *cli.Context) (err error) { } func goCmdVerification(c *cli.Context) (string, error) { - if show, err := cliutils.ShowCmdHelpIfNeeded(c, c.Args()); show || err != nil { - return "", err - } if c.NArg() < 1 { return "", cliutils.WrongNumberOfArgumentsHandler(c) } - configFilePath, exists, err := project.GetProjectConfFilePath(project.Go) + + configFilePath, err := getProjectConfigPathOrThrow(project.Go, "go", "go-config") if err != nil { return "", err } - // Verify config file is found. - if !exists { - return "", fmt.Errorf("no config file was found! Before running the go command on a project for the first time, the project should be configured using the go-config command") - } + log.Debug("Go config file was found in:", configFilePath) return configFilePath, nil } @@ -723,7 +738,7 @@ func pullCmd(c *cli.Context, image string) error { if show, err := cliutils.ShowGenericCmdHelpIfNeeded(c, c.Args(), "dockerpullhelp"); show || err != nil { return err } - _, rtDetails, _, skipLogin, filteredDockerArgs, buildConfiguration, err := commandsUtils.ExtractDockerOptionsFromArgs(c.Args()) + _, rtDetails, _, skipLogin, filteredDockerArgs, buildConfiguration, err := extractDockerOptionsFromArgs(c.Args()) if err != nil { return err } @@ -746,7 +761,7 @@ func pushCmd(c *cli.Context, image string) (err error) { if show, err := cliutils.ShowCmdHelpIfNeeded(c, c.Args()); show || err != nil { return err } - threads, rtDetails, detailedSummary, skipLogin, filteredDockerArgs, buildConfiguration, err := commandsUtils.ExtractDockerOptionsFromArgs(c.Args()) + threads, rtDetails, detailedSummary, skipLogin, filteredDockerArgs, buildConfiguration, err := extractDockerOptionsFromArgs(c.Args()) if err != nil { return } @@ -779,7 +794,7 @@ func dockerNativeCmd(c *cli.Context) error { if show, err := cliutils.ShowCmdHelpIfNeeded(c, c.Args()); show || err != nil { return err } - _, _, _, _, cleanArgs, _, err := commandsUtils.ExtractDockerOptionsFromArgs(c.Args()) + _, _, _, _, cleanArgs, _, err := extractDockerOptionsFromArgs(c.Args()) if err != nil { return err } @@ -787,6 +802,34 @@ func dockerNativeCmd(c *cli.Context) error { return cm.RunNativeCmd(cleanArgs) } +// Remove all the none docker CLI flags from args. +func extractDockerOptionsFromArgs(args []string) (threads int, serverDetails *coreConfig.ServerDetails, detailedSummary, skipLogin bool, cleanArgs []string, buildConfig *build.BuildConfiguration, err error) { + cleanArgs = append([]string(nil), args...) + var serverId string + cleanArgs, serverId, err = coreutils.ExtractServerIdFromCommand(cleanArgs) + if err != nil { + return + } + serverDetails, err = coreConfig.GetSpecificConfig(serverId, true, true) + if err != nil { + return + } + cleanArgs, threads, err = coreutils.ExtractThreadsFromArgs(cleanArgs, 3) + if err != nil { + return + } + cleanArgs, detailedSummary, err = coreutils.ExtractDetailedSummaryFromArgs(cleanArgs) + if err != nil { + return + } + cleanArgs, skipLogin, err = coreutils.ExtractSkipLoginFromArgs(cleanArgs) + if err != nil { + return + } + cleanArgs, buildConfig, err = build.ExtractBuildDetailsFromArgs(cleanArgs) + return +} + // Assuming command name is the first argument that isn't a flag. // Returns the command name, and the filtered arguments slice without it. func getCommandName(orgArgs []string) (string, []string) { @@ -831,6 +874,7 @@ func npmGenericCmd(c *cli.Context, cmdName string, collectBuildInfoIfRequested b // Run generic npm command. npmCmd := npm.NewNpmCommand(cmdName, collectBuildInfoIfRequested) + configFilePath, args, err := GetNpmConfigAndArgs(c) if err != nil { return err @@ -872,13 +916,9 @@ func NpmPublishCmd(c *cli.Context) (err error) { } func GetNpmConfigAndArgs(c *cli.Context) (configFilePath string, args []string, err error) { - configFilePath, exists, err := project.GetProjectConfFilePath(project.Npm) + configFilePath, err = getProjectConfigPathOrThrow(project.Npm, "npm", "npm-config") if err != nil { - return "", nil, err - } - - if !exists { - return "", nil, errorutils.CheckErrorf("no config file was found! Before running the npm command on a project for the first time, the project should be configured using the npm-config command") + return } _, args = getCommandName(c.Args()) return @@ -921,19 +961,20 @@ func pythonCmd(c *cli.Context, projectType project.ProjectType) error { cmdName, filteredArgs := getCommandName(orgArgs) switch projectType { case project.Pip: - pythonCommand := python.NewPipCommand() - pythonCommand.SetServerDetails(rtDetails).SetRepo(pythonConfig.TargetRepo()).SetCommandName(cmdName).SetArgs(filteredArgs) - return commands.Exec(pythonCommand) + pipCommand := python.NewPipCommand() + pipCommand.SetServerDetails(rtDetails).SetRepo(pythonConfig.TargetRepo()).SetCommandName(cmdName).SetArgs(filteredArgs) + return commands.Exec(pipCommand) case project.Pipenv: - pythonCommand := python.NewPipenvCommand() - pythonCommand.SetServerDetails(rtDetails).SetRepo(pythonConfig.TargetRepo()).SetCommandName(cmdName).SetArgs(filteredArgs) - return commands.Exec(pythonCommand) + pipenvCommand := python.NewPipenvCommand() + pipenvCommand.SetServerDetails(rtDetails).SetRepo(pythonConfig.TargetRepo()).SetCommandName(cmdName).SetArgs(filteredArgs) + return commands.Exec(pipenvCommand) case project.Poetry: - pythonCommand := python.NewPoetryCommand() - pythonCommand.SetServerDetails(rtDetails).SetRepo(pythonConfig.TargetRepo()).SetCommandName(cmdName).SetArgs(filteredArgs) - return commands.Exec(pythonCommand) + poetryCommand := python.NewPoetryCommand() + poetryCommand.SetServerDetails(rtDetails).SetRepo(pythonConfig.TargetRepo()).SetCommandName(cmdName).SetArgs(filteredArgs) + return commands.Exec(poetryCommand) + default: + return errorutils.CheckErrorf("%s is not supported", projectType) } - return errorutils.CheckErrorf("%s is not supported", projectType) } func terraformCmd(c *cli.Context) error { @@ -955,13 +996,9 @@ func terraformCmd(c *cli.Context) error { } func getTerraformConfigAndArgs(c *cli.Context) (configFilePath string, args []string, err error) { - configFilePath, exists, err := project.GetProjectConfFilePath(project.Terraform) + configFilePath, err = getProjectConfigPathOrThrow(project.Terraform, "terraform", "terraform-config") if err != nil { - return "", nil, err - } - - if !exists { - return "", nil, errors.New("no config file was found! Before running the terraform command on a project for the first time, the project should be configured using the terraform-config command") + return } args = cliutils.ExtractCommand(c) return @@ -977,3 +1014,63 @@ func terraformPublishCmd(configFilePath string, args []string, c *cli.Context) e result := terraformCmd.Result() return cliutils.PrintBriefSummaryReport(result.SuccessCount(), result.FailCount(), cliutils.IsFailNoOp(c), err) } + +func getProjectConfigPathOrThrow(projectType project.ProjectType, cmdName, configCmdName string) (configFilePath string, err error) { + configFilePath, exists, err := project.GetProjectConfFilePath(projectType) + if err != nil { + return + } + if !exists { + return "", errorutils.CheckErrorf(getMissingConfigErrMsg(cmdName, configCmdName)) + } + return +} + +func getMissingConfigErrMsg(cmdName, configCmdName string) string { + return fmt.Sprintf("no config file was found! Before running the 'jf %s' command on a project for the first time, the project should be configured with the 'jf %s' command", cmdName, configCmdName) +} + +func twineCmd(c *cli.Context) error { + if show, err := cliutils.ShowCmdHelpIfNeeded(c, c.Args()); show || err != nil { + return err + } + serverDetails, targetRepo, err := getTwineConfigAndArgs() + if err != nil { + return err + } + cmdName, filteredArgs := getCommandName(cliutils.ExtractCommand(c)) + return python.NewTwineCommand(cmdName).SetServerDetails(serverDetails).SetTargetRepo(targetRepo).SetArgs(filteredArgs).Run() +} + +func getTwineConfigAndArgs() (serverDetails *coreConfig.ServerDetails, targetRepo string, err error) { + configFilePath, err := getTwineConfigPath() + if err != nil { + return + } + + vConfig, err := project.ReadConfigFile(configFilePath, project.YAML) + if err != nil { + return nil, "", fmt.Errorf("failed while reading configuration file '%s'. Error: %s", configFilePath, err.Error()) + } + projectConfig, err := project.GetRepoConfigByPrefix(configFilePath, project.ProjectConfigDeployerPrefix, vConfig) + if err != nil { + return nil, "", err + } + serverDetails, err = projectConfig.ServerDetails() + if err != nil { + return nil, "", err + } + targetRepo = projectConfig.TargetRepo() + return +} + +func getTwineConfigPath() (configFilePath string, err error) { + var exists bool + for _, projectType := range []project.ProjectType{project.Pip, project.Pipenv} { + configFilePath, exists, err = project.GetProjectConfFilePath(projectType) + if err != nil || exists { + return + } + } + return "", errorutils.CheckErrorf(getMissingConfigErrMsg("twine", "pip-config OR pipenv-config")) +} diff --git a/distribution/cli.go b/distribution/cli.go index 0410cfee2..8242da2c0 100644 --- a/distribution/cli.go +++ b/distribution/cli.go @@ -317,7 +317,7 @@ func createDistributionDetailsByFlags(c *cli.Context) (*coreConfig.ServerDetails return nil, err } if dsDetails.DistributionUrl == "" { - return nil, errors.New("the --dist-url option is mandatory") + return nil, errors.New("no JFrog Distribution URL specified, either via the --url flag or as part of the server configuration") } return dsDetails, nil } diff --git a/docker_test.go b/docker_test.go index 154dd78e7..9a49a8a3c 100644 --- a/docker_test.go +++ b/docker_test.go @@ -3,6 +3,7 @@ package main import ( "context" "fmt" + "github.com/docker/docker/api/types/mount" "os" "path" "path/filepath" @@ -208,7 +209,7 @@ func TestPushFatManifestImage(t *testing.T) { Privileged(). Networks(rtNetwork). Name("buildx_container"). - Mount(workspace, "/workspace", false). + Mount(mount.Mount{Type: mount.TypeBind, Source: workspace, Target: "/workspace", ReadOnly: false}). Cmd("--insecure-registry", tests.RtContainerHostName). // Docker daemon take times to load. In order to check if it's available we wait for a log message to indications that the Docker daemon has finished initializing. WaitFor(wait.ForLog("API listen on /var/run/docker.sock").WithStartupTimeout(5*time.Minute)). @@ -493,9 +494,10 @@ func runKaniko(t *testing.T, imageToPush string) string { _, err = tests.NewContainerRequest(). Image(kanikoImage). Networks(rtNetwork). - Mount(workspace, "/workspace", false). - Mount(credentialsFile, "/kaniko/.docker/config.json", true). - Cmd("--dockerfile="+dockerFile, "--destination="+imageToPush, "--insecure", "--skip-tls-verify", "--image-name-with-digest-file="+KanikoOutputFile). + Mount( + mount.Mount{Type: mount.TypeBind, Source: workspace, Target: "/workspace", ReadOnly: false}, + mount.Mount{Type: mount.TypeBind, Source: credentialsFile, Target: "/kaniko/.docker/config.json", ReadOnly: true}). + Cmd("--dockerfile=/workspace/"+dockerFile, "--destination="+imageToPush, "--insecure", "--skip-tls-verify", "--image-name-with-digest-file="+KanikoOutputFile). WaitFor(wait.ForExit().WithExitTimeout(300000*time.Millisecond)). Build(context.Background(), true) assert.NoError(t, err) diff --git a/docs/artifactory/cocoapodsconfig/help.go b/docs/artifactory/cocoapodsconfig/help.go new file mode 100644 index 000000000..a334e48a1 --- /dev/null +++ b/docs/artifactory/cocoapodsconfig/help.go @@ -0,0 +1,7 @@ +package cocoapodsconfig + +var Usage = []string{"rt cocoapods-config [command options]"} + +func GetDescription() string { + return "Generate cocoapods build configuration." +} diff --git a/docs/artifactory/download/help.go b/docs/artifactory/download/help.go index e1965c2c6..422c27602 100644 --- a/docs/artifactory/download/help.go +++ b/docs/artifactory/download/help.go @@ -5,7 +5,7 @@ import "github.com/jfrog/jfrog-cli/docs/common" var Usage = []string{"rt dl [command options] [target pattern]", "rt dl --spec= [command options]"} -var EnvVar = []string{common.JfrogCliTransitiveDownloadExperimental, common.JfrogCliFailNoOp} +var EnvVar = []string{common.JfrogCliTransitiveDownload, common.JfrogCliFailNoOp} func GetDescription() string { return "Download files from Artifactory to local file system." diff --git a/docs/artifactory/swiftconfig/help.go b/docs/artifactory/swiftconfig/help.go new file mode 100644 index 000000000..2a6581948 --- /dev/null +++ b/docs/artifactory/swiftconfig/help.go @@ -0,0 +1,7 @@ +package swiftconfig + +var Usage = []string{"rt swift-config [command options]"} + +func GetDescription() string { + return "Generate swift build configuration." +} diff --git a/docs/artifactory/twine/help.go b/docs/artifactory/twine/help.go new file mode 100644 index 000000000..9fb5af93d --- /dev/null +++ b/docs/artifactory/twine/help.go @@ -0,0 +1,12 @@ +package twinedocs + +var Usage = []string{"twine [command options]"} + +func GetDescription() string { + return "Runs twine " +} + +func GetArguments() string { + return ` twine commands + Arguments and options for the twine command.` +} diff --git a/docs/common/env.go b/docs/common/env.go index 141f5a5c7..f5f0d3b18 100644 --- a/docs/common/env.go +++ b/docs/common/env.go @@ -1,9 +1,10 @@ package common import ( - "github.com/jfrog/jfrog-client-go/artifactory/services" "strings" + "github.com/jfrog/jfrog-client-go/artifactory/services" + "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" ) @@ -53,11 +54,12 @@ const ( Can be optionally used with the JFROG_CLI_PLUGINS_SERVER environment variable. Determines the name of the local repository to use.` - JfrogCliTransitiveDownloadExperimental = ` JFROG_CLI_TRANSITIVE_DOWNLOAD_EXPERIMENTAL + JfrogCliTransitiveDownload = ` JFROG_CLI_TRANSITIVE_DOWNLOAD [Default: false] - Set to true to look for artifacts also in remote repositories when using the 'rt download' command. - The search will run on the first five remote repositories within the virtual repository. - This feature is experimental and available on Artifactory version 7.17.0 or higher.` + Set this option to true to include remote repositories in artifact searches when using the 'rt download' command. + The search will target the first five remote repositories within the virtual repository. + This feature is available starting from Artifactory version 7.17.0. + NOTE: Enabling this option may increase the load on Artifactory instances that are proxied by multiple remote repositories. ` JfrogCliReleasesRepo = ` JFROG_CLI_RELEASES_REPO Configured Artifactory repository name from which to download the jar needed by the mvn/gradle command. @@ -74,16 +76,17 @@ const ( JfrogCliMinChecksumDeploySizeKb = ` JFROG_CLI_MIN_CHECKSUM_DEPLOY_SIZE_KB [Default: 10] Minimum file size in KB for which JFrog CLI performs checksum deploy optimization. - Support with upload command` + Supported by the upload command` JfrogCliFailNoOp = ` JFROG_CLI_FAIL_NO_OP [Default: false] Set to true if you'd like the command to return exit code 2 in case of no files are affected. Support by the following commands: copy, delete, delete-props, set-props, download, move, search and upload` - JfrogCliUploadEmptyArchive = ` ` + services.JfrogCliUploadEmptyArchiveEnv + ` + JfrogCliUploadEmptyArchive = ` ` + services.JfrogCliUploadEmptyArchiveEnv + ` [Default: false] - Set to true if you'd like to upload an empty archive when '--archive' is set but all files were excluded by exclusions pattern. ` + Set to true if you'd like to upload an empty archive when '--archive' is set but all files were excluded by exclusions pattern. + Supported by the upload command` JfrogCliEncryptionKey = ` JFROG_CLI_ENCRYPTION_KEY If provided, encrypt the sensitive data stored in the config with the provided key. Must be exactly 32 characters.` @@ -91,6 +94,11 @@ const ( JfrogCliAvoidNewVersionWarning = ` JFROG_CLI_AVOID_NEW_VERSION_WARNING [Default: false] Set to true if you'd like to avoid checking the latest available JFrog CLI version and printing warning when it newer than the current one. ` + + JfrogCliCommandSummaryOutputDirectory = ` JFROG_CLI_COMMAND_SUMMARY_OUTPUT_DIR + Defines the directory path where the command summaries data is stored. + Every command will have its own individual directory within this base directory. + . ` ) var ( @@ -120,7 +128,7 @@ func GetGlobalEnvVars() string { Ci, JfrogCliPluginsServer, JfrogCliPluginsRepo, - JfrogCliTransitiveDownloadExperimental, + JfrogCliTransitiveDownload, JfrogCliReleasesRepo, JfrogCliDependenciesDir, JfrogCliMinChecksumDeploySizeKb, @@ -129,7 +137,8 @@ func GetGlobalEnvVars() string { JfrogCliEnvExclude, JfrogCliFailNoOp, JfrogCliEncryptionKey, - JfrogCliAvoidNewVersionWarning) + JfrogCliAvoidNewVersionWarning, + JfrogCliCommandSummaryOutputDirectory) } func CreateEnvVars(envVars ...string) string { diff --git a/docs/general/ai/help.go b/docs/general/ai/help.go index 17404995e..3ae224eb0 100644 --- a/docs/general/ai/help.go +++ b/docs/general/ai/help.go @@ -3,5 +3,5 @@ package ai var Usage = []string{"how"} func GetDescription() string { - return "Ask questions about JFrog CLI commands and their usage." + return "An AI-powered interface that converts natural language inputs into AI-generated JFrog CLI commands." } diff --git a/docs/general/cisetup/help.go b/docs/general/cisetup/help.go deleted file mode 100644 index c3bdbfdb3..000000000 --- a/docs/general/cisetup/help.go +++ /dev/null @@ -1,7 +0,0 @@ -package cisetup - -var Usage = []string{"ci-setup "} - -func GetDescription() string { - return "Set up a CI pipeline with the JFrog Platform." -} diff --git a/docs/general/project/init/help.go b/docs/general/project/init/help.go deleted file mode 100644 index b8f8506fc..000000000 --- a/docs/general/project/init/help.go +++ /dev/null @@ -1,8 +0,0 @@ -package projectinit - -var Usage = []string{"project init", - "project init "} - -func GetDescription() string { - return "Initialize a new project environment." -} diff --git a/docs/general/summary/help.go b/docs/general/summary/help.go new file mode 100644 index 000000000..6bb556945 --- /dev/null +++ b/docs/general/summary/help.go @@ -0,0 +1,9 @@ +package summary + +var Usage = []string{"csm"} + +func GetDescription() string { + return `Generates a Summary of recorded CLI commands there were executed on the current machine. + The report is generated in Markdown format and saved in the directory stored in the JFROG_CLI_COMMAND_SUMMARY_OUTPUT_DIR environment variable. +` +} diff --git a/general/ai/cli.go b/general/ai/cli.go index 5d513b6fd..8ef6c0e71 100644 --- a/general/ai/cli.go +++ b/general/ai/cli.go @@ -1,91 +1,151 @@ package ai import ( + "bufio" "bytes" "encoding/json" "errors" "fmt" "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" - "github.com/jfrog/jfrog-cli-core/v2/utils/ioutils" "github.com/jfrog/jfrog-cli/utils/cliutils" + "github.com/jfrog/jfrog-client-go/artifactory/services/utils" "github.com/jfrog/jfrog-client-go/http/httpclient" + clientutils "github.com/jfrog/jfrog-client-go/utils" "github.com/jfrog/jfrog-client-go/utils/errorutils" "github.com/jfrog/jfrog-client-go/utils/log" + "github.com/manifoldco/promptui" "github.com/urfave/cli" "io" "net/http" + "os" "strings" ) type ApiCommand string const ( - cliAiApiPath = "https://cli-ai.jfrog.info/" - questionApi ApiCommand = "ask" - feedbackApi ApiCommand = "feedback" + cliAiAppApiUrl = "https://cli-ai-app.jfrog.io/api/" + askRateLimitHeader = "X-JFrog-CLI-AI" + // The latest version of the terms and conditions for using the AI interface. (https://docs.jfrog-applications.jfrog.io/jfrog-applications/jfrog-cli/cli-ai/terms) + aiTermsRevision = 1 ) -type questionBody struct { - Question string `json:"question"` -} +type ApiType string -type feedbackBody struct { - questionBody - LlmAnswer string `json:"llm_answer"` - IsAccurate bool `json:"is_accurate"` - ExpectedAnswer string `json:"expected_answer"` -} +const ( + ask ApiType = "ask" + feedback ApiType = "feedback" +) func HowCmd(c *cli.Context) error { if show, err := cliutils.ShowCmdHelpIfNeeded(c, c.Args()); show || err != nil { return err } - if c.NArg() < 1 { + if c.NArg() > 0 { return cliutils.WrongNumberOfArgumentsHandler(c) } + log.Output(coreutils.PrintLink("This AI-powered interface converts natural language inputs into AI-generated JFrog CLI commands.\n" + + "For more information about this interface, see https://docs.jfrog-applications.jfrog.io/jfrog-applications/jfrog-cli/cli-ai\n" + + "Try it out by typing a question, such as: 'How can I upload all .zip files from user/mylibs/ to the libs-local repository in Artifactory?'\n" + + "Note: JFrog AI Assistant is in beta and currently supports primarily Artifactory and Xray commands.\n")) - args := cliutils.ExtractCommand(c) - question := questionBody{Question: fmt.Sprintf("How %s", strings.Join(args, " "))} - llmAnswer, err := askQuestion(question) - if err != nil { - return err - } - if strings.ToLower(llmAnswer) == "i dont know" { - log.Output("The current version of the AI model does not support this type of command yet.") - return nil - } - log.Output("AI generated JFrog CLI command:") - err = coreutils.PrintTable("", "", coreutils.PrintTitle(llmAnswer), false) - if err != nil { + // Ask the user to agree to the terms and conditions. If the user does not agree, the command will not proceed. + // Ask this only once per JFrog CLI installation, unless the terms are updated. + if agreed, err := handleAiTermsAgreement(); err != nil { return err + } else if !agreed { + // If the user does not agree to the terms, the command will not proceed. + return reportTermsDisagreement() } - feedback := feedbackBody{questionBody: question, LlmAnswer: llmAnswer} - feedback.getUserFeedback() - if err = sendFeedback(feedback); err != nil { - return err + for { + var question string + scanner := bufio.NewScanner(os.Stdin) + fmt.Print("🐸 Your request:\n ") + for { + // Ask the user for a question + scanner.Scan() + question = strings.TrimSpace(scanner.Text()) + if question != "" { + // If the user entered a question, break the loop + break + } + } + fmt.Print("\n🤖 Generated command:\n") + llmAnswer, err := askQuestion(question) + if err != nil { + return err + } + validResponse := strings.HasPrefix(llmAnswer, "jf") + // Print the generated command within a styled table frame. + if validResponse { + coreutils.PrintMessageInsideFrame(coreutils.PrintBoldTitle(llmAnswer), " ") + } else { + log.Output(" " + coreutils.PrintYellow(llmAnswer)) + } + + // If the response is a valid JFrog CLI command, ask the user for feedback. + if validResponse { + log.Output() + if err = handleResponseFeedback(); err != nil { + return err + } + } + + log.Output("\n" + coreutils.PrintComment("-------------------") + "\n") } - log.Output("Thank you for your feedback!") - return nil } -func (fb *feedbackBody) getUserFeedback() { - fb.IsAccurate = coreutils.AskYesNo(coreutils.PrintLink("Is the provided command accurate?"), true) - if !fb.IsAccurate { - ioutils.ScanFromConsole("Please provide the exact command you expected (Example: 'jf rt u ...')", &fb.ExpectedAnswer, "") - } +type questionBody struct { + Question string `json:"question"` } -func askQuestion(question questionBody) (response string, err error) { - return sendRequestToCliAiServer(question, questionApi) +func askQuestion(question string) (response string, err error) { + return sendRestAPI(ask, questionBody{Question: question}) } -func sendFeedback(feedback feedbackBody) (err error) { - _, err = sendRequestToCliAiServer(feedback, feedbackApi) +type feedbackBody struct { + IsGoodResponse *bool `json:"is_good_response,omitempty"` + IsAgreedTerms *bool `json:"is_agreed_terms,omitempty"` +} + +func handleResponseFeedback() (err error) { + isGoodResponse, err := getUserFeedback() + if err != nil { + return + } + _, err = sendRestAPI(feedback, feedbackBody{IsGoodResponse: &isGoodResponse}) + return +} + +func reportTermsDisagreement() (err error) { + _, err = sendRestAPI(feedback, feedbackBody{IsAgreedTerms: clientutils.Pointer(false)}) return } -func sendRequestToCliAiServer(content interface{}, apiCommand ApiCommand) (response string, err error) { +func getUserFeedback() (bool, error) { + // Customize the template to place the options on the same line as the question + templates := &promptui.SelectTemplates{ + Label: "{{ . }}", + Active: " 👉 {{ . | cyan }}", + Inactive: " {{ . }}", + Selected: "🙏 Thanks for your feedback!", + } + + prompt := promptui.Select{ + Label: "⭐ Rate this response:", + Items: []string{"👍 Good response!", "👎 Could be better..."}, + Templates: templates, + HideHelp: true, + } + selected, _, err := prompt.Run() + if err != nil { + return false, err + } + return selected == 0, nil +} + +func sendRestAPI(apiType ApiType, content interface{}) (response string, err error) { contentBytes, err := json.Marshal(content) if errorutils.CheckError(err) != nil { return @@ -94,14 +154,18 @@ func sendRequestToCliAiServer(content interface{}, apiCommand ApiCommand) (respo if errorutils.CheckError(err) != nil { return } - req, err := http.NewRequest(http.MethodPost, cliAiApiPath+string(apiCommand), bytes.NewBuffer(contentBytes)) + req, err := http.NewRequest(http.MethodPost, cliAiAppApiUrl+string(apiType), bytes.NewBuffer(contentBytes)) if errorutils.CheckError(err) != nil { return } req.Header.Set("Content-Type", "application/json") + if apiType == ask { + req.Header.Set(askRateLimitHeader, "true") + } log.Debug(fmt.Sprintf("Sending HTTP %s request to: %s", req.Method, req.URL)) resp, err := client.GetClient().Do(req) - if errorutils.CheckError(err) != nil { + if err != nil { + err = errorutils.CheckErrorf("CLI-AI server is not available. Please check your network or try again later.") return } if resp == nil { @@ -109,25 +173,53 @@ func sendRequestToCliAiServer(content interface{}, apiCommand ApiCommand) (respo return } if err = errorutils.CheckResponseStatus(resp, http.StatusOK); err != nil { - if resp.StatusCode == http.StatusInternalServerError { - err = errorutils.CheckErrorf("AI model Endpoint is not available.\n" + err.Error()) - } else if resp.StatusCode == http.StatusNotFound { - err = errorutils.CheckErrorf("CLI-AI app server is no available. Note that the this command is supported while inside JFrog's internal network only.\n" + err.Error()) + switch resp.StatusCode { + case http.StatusInternalServerError: + err = errorutils.CheckErrorf("JFrog CLI-AI model endpoint is not available. Please try again later.") + case http.StatusNotAcceptable: + err = errorutils.CheckErrorf("The system is currently handling multiple requests from other users\n" + + "Please try submitting your question again in a few minutes. Thank you for your patience!") + default: + err = errorutils.CheckErrorf("JFrog CLI-AI server is not available. Please check your network or try again later:\n" + err.Error()) } return } - if apiCommand == questionApi { - defer func() { - if resp.Body != nil { - err = errors.Join(err, errorutils.CheckError(resp.Body.Close())) - } - }() - var body []byte - body, err = io.ReadAll(resp.Body) - if errorutils.CheckError(err) != nil { - return + + if apiType == feedback { + // If the API is feedback, no response is expected + return + } + + defer func() { + if resp.Body != nil { + err = errors.Join(err, errorutils.CheckError(resp.Body.Close())) } - response = strings.TrimSpace(string(body)) + }() + var body []byte + // Limit size of response body to 10MB + body, err = io.ReadAll(io.LimitReader(resp.Body, 10*utils.SizeMiB)) + if errorutils.CheckError(err) != nil { + return } + response = strings.TrimSpace(string(body)) return } + +func handleAiTermsAgreement() (bool, error) { + latestTermsVer, err := cliutils.GetLatestAiTermsRevision() + if err != nil { + return false, err + } + if latestTermsVer == nil || *latestTermsVer < aiTermsRevision { + if !coreutils.AskYesNo("By using this interface, you agree to the terms of JFrog's AI Addendum on behalf of your organization as an active JFrog customer.\n"+ + "Review these terms at "+coreutils.PrintLink("https://docs.jfrog-applications.jfrog.io/jfrog-applications/jfrog-cli/cli-ai/terms")+ + "\nDo you agree?", false) { + return false, nil + } + if err = cliutils.SetLatestAiTermsRevision(aiTermsRevision); err != nil { + return false, err + } + log.Output() + } + return true, nil +} diff --git a/general/cisetup/cisetup.go b/general/cisetup/cisetup.go deleted file mode 100644 index 07af9a7c1..000000000 --- a/general/cisetup/cisetup.go +++ /dev/null @@ -1,1046 +0,0 @@ -package cisetup - -import ( - "bytes" - "encoding/json" - "fmt" - "golang.org/x/exp/slices" - "os" - "path" - "path/filepath" - "strings" - "syscall" - - "github.com/go-git/go-git/v5" - "github.com/go-git/go-git/v5/plumbing" - "github.com/go-git/go-git/v5/plumbing/transport" - "github.com/go-git/go-git/v5/plumbing/transport/http" - "github.com/jfrog/jfrog-cli-core/v2/artifactory/commands/buildinfo" - "github.com/jfrog/jfrog-cli-core/v2/artifactory/commands/permissiontarget" - "github.com/jfrog/jfrog-cli-core/v2/artifactory/commands/usersmanagement" - "github.com/jfrog/jfrog-cli-core/v2/common/build" - coreCommonCommands "github.com/jfrog/jfrog-cli-core/v2/common/commands" - "github.com/jfrog/jfrog-cli-core/v2/general/cisetup" - repoutils "github.com/jfrog/jfrog-cli-core/v2/general/project" - utilsConfig "github.com/jfrog/jfrog-cli-core/v2/utils/config" - "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" - "github.com/jfrog/jfrog-cli-core/v2/utils/ioutils" - buildinfocmd "github.com/jfrog/jfrog-client-go/artifactory/buildinfo" - "github.com/jfrog/jfrog-client-go/artifactory/services" - clientConfig "github.com/jfrog/jfrog-client-go/config" - "github.com/jfrog/jfrog-client-go/pipelines" - pipelinesservices "github.com/jfrog/jfrog-client-go/pipelines/services" - "github.com/jfrog/jfrog-client-go/utils" - "github.com/jfrog/jfrog-client-go/utils/errorutils" - "github.com/jfrog/jfrog-client-go/utils/io/fileutils" - "github.com/jfrog/jfrog-client-go/utils/log" - xrayservices "github.com/jfrog/jfrog-client-go/xray/services" - xrayutils "github.com/jfrog/jfrog-client-go/xray/services/utils" - "golang.org/x/term" -) - -const ( - VcsConfigFile = "jfrog-cli-vcs.conf" - DefaultFirstBuildNumber = "0" - DefaultWorkspace = "./ci-setup-workspace" - pipelineUiPath = "ui/pipelines/myPipelines/default/" - permissionTargetName = "jfrog-ide-developer-pt" - permissionTargetTemplate = `{"build":{"include-patterns":"**","actions-groups":{"%s":"read"}},"name":"%s"}` - pttFileName = "ci-setup-ptt" - ideGroupName = "jfrog-ide-developer-group" - ideUserName = "ide-developer" - ideUserPassPlaceholder = "" - ideUserEmailPlaceholder = "" - createUserTemplate = `jfrog rt user-create "%s" "%s" "%s" --users-groups="%s" --server-id="%s"` - maxRepoCreationAttempts = 200 -) - -type CiSetupCommand struct { - defaultData *cisetup.CiSetupData - data *cisetup.CiSetupData -} - -func RunCiSetupCmd() error { - cc := &CiSetupCommand{} - err := logBeginningInstructions() - if err != nil { - return err - } - err = cc.prepareConfigurationData() - if err != nil { - return err - } - err = cc.Run() - if err != nil { - return err - } - return saveVcsConf(cc.data) -} - -func logBeginningInstructions() error { - instructions := []string{ - "", - coreutils.PrintTitle("About this command"), - "This command sets up a basic CI pipeline which uses the JFrog Platform.", - "It currently supports Maven, Gradle and npm, but additional package managers will be added in the future.", - "The following CI providers are currently supported: JFrog Pipelines, Jenkins and GitHub Actions.", - "The command takes care of configuring JFrog Artifactory and JFrog Xray for you.", - "", - coreutils.PrintTitle("Important"), - " 1. If you don't have a JFrog Platform instance with admin credentials, head over to https://jfrog.com/start-free/ and get one for free.", - " 2. When asked to provide credentials for your JFrog Platform and Git provider, please make sure the credentials have admin privileges.", - " 3. You can exit the command by hitting 'control + C' at any time. The values you provided before exiting are saved (with the exception of passwords and tokens) and will be set as defaults the next time you run the command.", - "", "", - } - return writeToScreen(strings.Join(instructions, "\n")) -} - -func inactivePipelinesNote(pipelinesUrl string) error { - instructions := []string{ - "", - coreutils.PrintTitle("JFrog Pipelines"), - "It looks like your JFrog platform does not include JFrog Pipelines or it is currently inactive.", - pipelinesUrl, - "", - } - return writeToScreen(strings.Join(instructions, "\n")) -} - -func (cc *CiSetupCommand) prepareConfigurationData() error { - // If data is nil, initialize a new one - if cc.data == nil { - cc.data = new(cisetup.CiSetupData) - } - - // Get previous vcs data if exists - defaultData, err := readVcsConf() - cc.defaultData = defaultData - return err -} - -func readVcsConf() (*cisetup.CiSetupData, error) { - conf := &cisetup.CiSetupData{} - homeDirPath, err := coreutils.GetJfrogHomeDir() - if err != nil { - return nil, err - } - confPath := filepath.Join(homeDirPath, VcsConfigFile) - exists, err := fileutils.IsFileExists(confPath, false) - if err != nil { - return nil, err - } - if !exists { - return conf, nil - } - configFile, err := fileutils.ReadFile(confPath) - if err != nil { - return nil, err - } - err = json.Unmarshal(configFile, conf) - return conf, errorutils.CheckError(err) -} - -func saveVcsConf(conf *cisetup.CiSetupData) error { - homeDirPath, err := coreutils.GetJfrogHomeDir() - if err != nil { - return err - } - bytesContent, err := json.Marshal(conf) - if err != nil { - return errorutils.CheckError(err) - } - var content bytes.Buffer - err = json.Indent(&content, bytesContent, "", " ") - if err != nil { - return errorutils.CheckError(err) - } - err = os.WriteFile(filepath.Join(homeDirPath, VcsConfigFile), content.Bytes(), 0600) - return errorutils.CheckError(err) -} - -func (cc *CiSetupCommand) Run() error { - // Run JFrog config command - err := runConfigCmd() - if err != nil { - return err - } - // Basic VCS questionnaire (URLs, Credentials, etc'...) - err = cc.gitPhase() - err = saveIfNoError(err, cc.data) - if err != nil { - return err - } - // Ask the user which CI he tries to setup - cc.ciProviderPhase() - err = saveVcsConf(cc.data) - if err != nil { - return err - } - // Interactively create Artifactory repository based on the detected technologies and ongoing user input - err = cc.artifactoryConfigPhase() - err = saveIfNoError(err, cc.data) - if err != nil { - return err - } - // Publish empty build info. - err = cc.publishFirstBuild() - err = saveIfNoError(err, cc.data) - if err != nil { - return err - } - // Configure Xray to scan the new build. - err = cc.xrayConfigPhase() - err = saveIfNoError(err, cc.data) - if err != nil { - return err - } - var ciSpecificInstructions []string - switch cc.data.CiType { - case cisetup.Pipelines: - // Configure pipelines, create and stage pipelines.yml. - ciFileName, err := cc.runPipelinesPhase() - if err != nil { - return err - } - ciSpecificInstructions, err = cc.getPipelinesCompletionInstruction(ciFileName) - if err != nil { - return err - } - case cisetup.Jenkins: - // Create and stage Jenkinsfile. - _, err := cc.runJenkinsPhase() - if err != nil { - return err - } - ciSpecificInstructions = cc.getJenkinsCompletionInstruction() - case cisetup.GithubActions: - // Create and stage main.yml. - ciFileName, err := cc.runGithubActionsPhase() - if err != nil { - return err - } - ciSpecificInstructions = cc.getGithubActionsCompletionInstruction(ciFileName) - } - // Create group and permission target if needed. - err = runIdePhase() - if err != nil { - return err - } - return cc.logCompletionInstruction(ciSpecificInstructions) -} - -func saveIfNoError(errCheck error, conf *cisetup.CiSetupData) error { - if errCheck != nil { - return errCheck - } - return saveVcsConf(conf) -} - -func runIdePhase() error { - serverDetails, err := utilsConfig.GetSpecificConfig(cisetup.ConfigServerId, false, false) - if err != nil { - return err - } - err = createGroup(serverDetails) - if err != nil { - return err - } - return createPermissionTarget(serverDetails) -} - -func createGroup(serverDetails *utilsConfig.ServerDetails) error { - log.Info("Creating group...") - groupCreateCmd := usersmanagement.NewGroupCreateCommand() - groupCreateCmd.SetName(ideGroupName).SetServerDetails(serverDetails).SetReplaceIfExists(false) - err := groupCreateCmd.Run() - if err != nil { - if _, ok := err.(*services.GroupAlreadyExistsError); !ok { - return err - } - log.Debug("Group already exists, skipping...") - } - return nil -} - -func createPermissionTarget(serverDetails *utilsConfig.ServerDetails) error { - ptTemplate := fmt.Sprintf(permissionTargetTemplate, ideGroupName, permissionTargetName) - tempDir, err := fileutils.CreateTempDir() - if err != nil { - return err - } - pttPath := filepath.Join(tempDir, pttFileName) - err = os.WriteFile(pttPath, []byte(ptTemplate), 0600) - if err != nil { - return err - } - permissionTargetCreateCmd := permissiontarget.NewPermissionTargetCreateCommand() - permissionTargetCreateCmd.SetTemplatePath(pttPath).SetServerDetails(serverDetails).SetVars("") - err = permissionTargetCreateCmd.Run() - if err != nil { - if _, ok := err.(*services.PermissionTargetAlreadyExistsError); !ok { - return err - } - log.Debug("Permission target already exists, skipping...") - } - return nil -} - -func writeToScreen(content string) error { - _, err := fmt.Fprint(os.Stderr, content) - return errorutils.CheckError(err) -} - -func getPipelinesToken() (string, error) { - var err error - var byteToken []byte - for len(byteToken) == 0 { - err = writeToScreen("Please provide a JFrog Pipelines admin token (To generate the token, " + - "log into the JFrog Platform UI --> Administration --> Identity and Access --> Access Tokens --> Generate Admin Token): ") - if err != nil { - return "", err - } - //nolint:unconvert - byteToken, err = term.ReadPassword(int(syscall.Stdin)) - if err != nil { - return "", errorutils.CheckError(err) - } - // New-line required after the access token input: - log.Output() - } - return string(byteToken), nil -} - -func runConfigCmd() (err error) { - for { - configCmd := coreCommonCommands.NewConfigCommand(coreCommonCommands.AddOrEdit, cisetup.ConfigServerId).SetInteractive(true).SetEncPassword(true) - err = configCmd.Run() - if err != nil { - log.Error(err) - continue - } - // Validate JFrog credentials by execute get repo command - serviceDetails, err := utilsConfig.GetSpecificConfig(cisetup.ConfigServerId, false, false) - if err != nil { - return err - } - _, err = GetAllRepos(serviceDetails, "", "") - if err == nil { - return nil - } - log.Error(err) - } -} - -func (cc *CiSetupCommand) runJenkinsPhase() (string, error) { - generator := cisetup.JenkinsfileDslGenerator{ - SetupData: cc.data, - } - jenkinsfileBytes, jenkinsfileName, err := generator.GenerateDsl() - if err != nil { - return "", err - } - - err = cc.saveCiConfigToFile(jenkinsfileBytes, cisetup.JenkinsfileName) - if err != nil { - return "", err - } - err = cc.stageCiConfigFile(cisetup.JenkinsfileName) - if err != nil { - return "", err - } - return jenkinsfileName, nil -} - -func (cc *CiSetupCommand) runGithubActionsPhase() (string, error) { - generator := cisetup.GithubActionsGenerator{ - SetupData: cc.data, - } - GithubActionsYamlBytes, GithubActionsName, err := generator.Generate() - if err != nil { - return "", err - } - err = os.MkdirAll(filepath.Join(cc.data.LocalDirPath, cisetup.GithubActionsDir), 0744) - if err != nil { - return "", errorutils.CheckError(err) - } - err = cc.saveCiConfigToFile(GithubActionsYamlBytes, cisetup.GithubActionsFilePath) - if err != nil { - return "", err - } - err = cc.stageCiConfigFile(cisetup.GithubActionsFilePath) - - return GithubActionsName, err -} - -func (cc *CiSetupCommand) runPipelinesPhase() (string, error) { - var vcsIntName string - var rtIntName string - var err error - configurator := cisetup.JFrogPipelinesConfigurator{ - SetupData: cc.data, PipelinesToken: "", - } - // Ask for token and config pipelines. Run again if authentication problem. - for { - // Ask for pipelines token. - configurator.PipelinesToken, err = getPipelinesToken() - if err != nil { - return "", err - } - // Run Pipelines setup - vcsIntName, rtIntName, err = configurator.Config() - // If no error occurred, continue with flow. Elseif unauthorized error, ask for token again. - if err == nil { - break - } - if _, ok := err.(*pipelinesservices.IntegrationUnauthorizedError); !ok { - return "", err - } - log.Debug(err.Error()) - log.Info("There seems to be an authorization problem with the pipelines token you entered. Please try again.") - } - generator := cisetup.JFrogPipelinesYamlGenerator{ - VcsIntName: vcsIntName, - RtIntName: rtIntName, - SetupData: cc.data, - } - pipelinesYamlBytes, pipelineName, err := generator.Generate() - if err != nil { - return "", err - } - - err = cc.saveCiConfigToFile(pipelinesYamlBytes, cisetup.PipelinesYamlName) - if err != nil { - return "", err - } - err = cc.stageCiConfigFile(cisetup.PipelinesYamlName) - if err != nil { - return "", err - } - return pipelineName, nil -} - -func (cc *CiSetupCommand) saveCiConfigToFile(ciConfig []byte, fileName string) error { - filePath := filepath.Join(cc.data.LocalDirPath, fileName) - log.Info(fmt.Sprintf("Generating %s at: %q ...", fileName, filePath)) - return os.WriteFile(filePath, ciConfig, 0644) -} - -func (cc *CiSetupCommand) getPipelinesCompletionInstruction(pipelinesFileName string) ([]string, error) { - serviceDetails, err := utilsConfig.GetSpecificConfig(cisetup.ConfigServerId, false, false) - if err != nil { - return []string{}, err - } - - return []string{"", coreutils.PrintTitle("Completing the setup"), - "We configured the JFrog Platform and generated a pipelines.yml for you.", - "To complete the setup, add the new pipelines.yml to your git repository by running the following commands:", - "", - "\t cd " + cc.data.LocalDirPath, - "\t git commit -m \"Add " + pipelinesFileName + "\"", - "\t git push", - "", - "Although your pipeline is configured, it hasn't run yet.", - "It will run and become visible in the following URL, after the next git commit:", - getPipelineUiPath(serviceDetails.Url, pipelinesFileName), ""}, nil -} - -func (cc *CiSetupCommand) getJenkinsCompletionInstruction() []string { - JenkinsCompletionInstruction := []string{"", coreutils.PrintTitle("Completing the setup"), - "We configured the JFrog Platform and generated a Jenkinsfile file for you under " + cc.data.LocalDirPath, - "To complete the setup, follow these steps:", - "* Open the Jenkinsfile for edit."} - // HOME env instructions relevant only for Maven - if cc.data.BuiltTechnology.Type == coreutils.Maven || cc.data.BuiltTechnology.Type == coreutils.Gradle { - JenkinsCompletionInstruction = append(JenkinsCompletionInstruction, - "* Inside the 'environment' section, set the value of the HOME ENV variable,", - " to the Maven installation directory on the Jenkins agent (the directory which includes the 'bin' directory).") - } - - JenkinsCompletionInstruction = append(JenkinsCompletionInstruction, - "* If cloning the code from git requires credentials, modify the 'git' step as described", - " in the comment inside the 'Clone' step.", - "* Create credentials with 'rt-cred-id' as its ID in: Jenkins > Configure System > credentials > 'username with password' > ID: 'rt-cred-id' )", - " Read more about this here - https://www.jenkins.io/doc/book/using/using-credentials/", - "* Add the new Jenkinsfile to your git repository by running the following commands:", - "", - "\t cd "+cc.data.LocalDirPath, - "\t git commit -m \"Add Jenkinsfile\"", - "\t git push", - "", - "* Create a Pipelines job in Jenkins, and configure it to pull the new Jenkinsfile from git.", - "* Run the new Jenkins job. ", "") - - return JenkinsCompletionInstruction -} - -func (cc *CiSetupCommand) getGithubActionsCompletionInstruction(githubActionFileName string) []string { - return []string{"", coreutils.PrintTitle("Completing the setup"), - "We configured the JFrog Platform and generated a GitHub Actions workflow file", - "named " + cisetup.GithubActionsFileName + " for you under " + cisetup.GithubActionsDir + ".", - "", - "To complete the setup, follow these steps:", - "* Run the following JFrog CLI command:", - "", - "\t jfrog c export " + cisetup.ConfigServerId, - "", - "* Copy the displayed token into your clipboard and save it as a secret", - " named JF_ARTIFACTORY_SECRET_1 on GitHub.", - "* Add the new workflow file to your git repository by running the following commands:", - "", - "\t cd " + cc.data.LocalDirPath, - "\t git commit -m \"Add " + githubActionFileName + "\"", - "\t git push", - "", - "* View the build running on GitHub.", - ""} -} - -func (cc *CiSetupCommand) logCompletionInstruction(ciSpecificInstructions []string) error { - instructions := append(slices.Clone(ciSpecificInstructions), - coreutils.PrintTitle("Allowing developers to access this pipeline from their IDE"), - "You have the option of viewing the new pipeline's runs from within IntelliJ IDEA.", - "To achieve this, follow these steps:", - " 1. Make sure the latest version of the JFrog Plugin is installed on IntelliJ IDEA:", - " https://www.jfrog.com/confluence/display/JFROG/JFrog+IntelliJ+IDEA+Plugin", - " 2. Create a JFrog user for the IDE by running the following command:", - "", - "\t "+fmt.Sprintf(createUserTemplate, ideUserName, ideUserPassPlaceholder, ideUserEmailPlaceholder, ideGroupName, cisetup.ConfigServerId), - "", - " 3. In IDEA, under 'JFrog Global Configuration', set the JFrog Platform URL and the user you created.", - " 4. In IDEA, under 'JFrog CI Integration', set * as the 'Build name pattern'.", - " 5. In IDEA, open the 'JFrog' panel at the bottom of the screen, choose the 'CI' tab to see the CI information.", - "", - ) - return writeToScreen(strings.Join(instructions, "\n")) -} - -func getPipelineUiPath(pipelinesUrl, pipelineName string) string { - return utils.AddTrailingSlashIfNeeded(pipelinesUrl) + pipelineUiPath + pipelineName -} - -func (cc *CiSetupCommand) publishFirstBuild() (err error) { - cc.data.BuildName = fmt.Sprintf("%s-%s", cc.data.RepositoryName, cc.data.GitBranch) - // Run BAG Command (in order to publish the first, empty, build info) - buildAddGitConfigurationCmd := buildinfo.NewBuildAddGitCommand().SetDotGitPath(cc.data.LocalDirPath).SetServerId(cisetup.ConfigServerId) - buildConfiguration := build.NewBuildConfiguration(cc.data.BuildName, DefaultFirstBuildNumber, "", "") - buildAddGitConfigurationCmd = buildAddGitConfigurationCmd.SetBuildConfiguration(buildConfiguration) - log.Info("Generating an initial build-info...") - err = coreCommonCommands.Exec(buildAddGitConfigurationCmd) - if err != nil { - return err - } - // Run BP Command. - serviceDetails, err := utilsConfig.GetSpecificConfig(cisetup.ConfigServerId, false, false) - if err != nil { - return err - } - buildInfoConfiguration := buildinfocmd.Configuration{DryRun: false} - buildPublishCmd := buildinfo.NewBuildPublishCommand().SetServerDetails(serviceDetails).SetBuildConfiguration(buildConfiguration).SetConfig(&buildInfoConfiguration) - return coreCommonCommands.Exec(buildPublishCmd) -} - -func (cc *CiSetupCommand) xrayConfigPhase() (err error) { - serviceDetails, err := utilsConfig.GetSpecificConfig(cisetup.ConfigServerId, false, false) - if err != nil { - return err - } - xrayManager, err := CreateXrayServiceManager(serviceDetails) - if err != nil { - return err - } - // Index the build. - buildsToIndex := []string{cc.data.BuildName} - err = xrayManager.AddBuildsToIndexing(buildsToIndex) - if err != nil { - return err - } - // Create new default policy. - policyParams := xrayutils.NewPolicyParams() - policyParams.Name = "ci-pipeline-security-policy" - policyParams.Type = xrayutils.Security - policyParams.Description = "Basic Security policy." - policyParams.Rules = []xrayutils.PolicyRule{ - { - Name: "min-severity-rule", - Criteria: *xrayutils.CreateSeverityPolicyCriteria(xrayutils.Critical), - Priority: 1, - }, - } - err = xrayManager.CreatePolicy(policyParams) - if err != nil { - // In case the error is from type PolicyAlreadyExistsError, we should continue with the regular flow. - if paeErr, ok := err.(*xrayservices.PolicyAlreadyExistsError); !ok { - return err - } else { - log.Debug(paeErr.InnerError) - } - } - // Create new default watcher. - watchParams := xrayutils.NewWatchParams() - watchParams.Name = "ci-pipeline-watch-all" - watchParams.Description = "CI Pipeline Build Watch" - watchParams.Active = true - watchParams.Builds.Type = xrayutils.WatchBuildAll - watchParams.Policies = []xrayutils.AssignedPolicy{ - { - Name: policyParams.Name, - Type: "security", - }, - } - - err = xrayManager.CreateWatch(watchParams) - if err != nil { - // In case the error is from type WatchAlreadyExistsError, we should continue with the regular flow. - if waeErr, ok := err.(*xrayservices.WatchAlreadyExistsError); !ok { - return err - } else { - log.Debug(waeErr.InnerError) - err = nil - } - } - return -} - -func (cc *CiSetupCommand) artifactoryConfigPhase() (err error) { - err = cc.printDetectedTechs() - if err != nil { - return err - } - // First create repositories for the selected technology. - for tech := range cc.data.DetectedTechnologies { - if coreutils.AskYesNo(fmt.Sprintf("Would you like to use %s to build the code?", tech), true) { - cc.data.BuiltTechnology = &cisetup.TechnologyInfo{Type: tech} - err = cc.interactivelyCreateRepos(tech) - if err != nil { - return - } - cc.getBuildCmd() - return nil - } - } - return errorutils.CheckErrorf("at least one of the supported technologies is expected to be chosen for building") -} - -func (cc *CiSetupCommand) printDetectedTechs() error { - var techs []string - for tech := range cc.data.DetectedTechnologies { - techs = append(techs, string(tech)) - } - if len(techs) == 0 { - return errorutils.CheckErrorf("no supported technology was found in the project") - } - return writeToScreen("The next step is to provide the commands to build your code. It looks like the code is built with " + coreutils.ListToText(techs) + ".\n") -} - -func (cc *CiSetupCommand) getBuildCmd() { - defaultBuildCmd := buildCmdByTech[cc.data.BuiltTechnology.Type] - // Use the cached build command only if the chosen built technology wasn't changed. - if cc.defaultData.BuiltTechnology != nil && cc.defaultData.BuiltTechnology.Type == cc.data.BuiltTechnology.Type { - if cc.defaultData.BuiltTechnology.BuildCmd != "" { - defaultBuildCmd = cc.defaultData.BuiltTechnology.BuildCmd - } - } - // Ask for working build command. - prompt := "Please provide a single-line " + string(cc.data.BuiltTechnology.Type) + " build command." - ioutils.ScanFromConsole(prompt, &cc.data.BuiltTechnology.BuildCmd, defaultBuildCmd) -} - -func getRepoSelectionFromUser(repos *[]services.RepositoryDetails, promptString string) (repo string, err error) { - shouldPromptSelection := len(*repos) > 0 - if shouldPromptSelection { - // Ask if the user would like us to create a new repo or to choose from the existing repositories list - repo, err = promptARepoSelection(repos, promptString) - if err != nil { - return "", err - } - } else { - repo = NewRepository - } - return repo, nil -} - -func handleNewLocalRepository(serviceDetails *utilsConfig.ServerDetails, technologyType coreutils.Technology) (repo string) { - // Create local repository - for { - var newLocalRepo string - ioutils.ScanFromConsole("Repository Name", &newLocalRepo, repoutils.RepoDefaultName[technologyType][repoutils.Local]) - err := CreateLocalRepo(serviceDetails, technologyType, newLocalRepo) - if err != nil { - log.Error(err) - } else { - return newLocalRepo - } - } -} - -func (cc *CiSetupCommand) interactivelyCreateRepos(technologyType coreutils.Technology) (err error) { - serviceDetails, err := utilsConfig.GetSpecificConfig(cisetup.ConfigServerId, false, false) - if err != nil { - return err - } - // Get all relevant local to choose from - localRepos, err := GetAllRepos(serviceDetails, repoutils.Local, string(technologyType)) - if err != nil { - return err - } - deployerRepoType := "" - if technologyType == coreutils.Maven { - deployerRepoType = "releases " - } - localRepo, err := getRepoSelectionFromUser(localRepos, fmt.Sprintf("Create or select an Artifactory %sRepository to deploy the build artifacts to", deployerRepoType)) - if err != nil { - return err - } - if localRepo == NewRepository { - localRepo = handleNewLocalRepository(serviceDetails, technologyType) - } - cc.data.BuiltTechnology.LocalReleasesRepo = localRepo - if technologyType == coreutils.Maven { - localRepo, err = getRepoSelectionFromUser(localRepos, "Create or select an Artifactory snapshots Repository to deploy the build artifacts to") - if err != nil { - return err - } - if localRepo == NewRepository { - localRepo = handleNewLocalRepository(serviceDetails, technologyType) - } - } - cc.data.BuiltTechnology.LocalSnapshotsRepo = localRepo - // Get all relevant remotes to choose from - remoteRepos, err := GetAllRepos(serviceDetails, repoutils.Remote, string(technologyType)) - if err != nil { - return err - } - remoteRepo, err := getRepoSelectionFromUser(remoteRepos, "Create or select an Artifactory remote repository to resolve (download) 3rd party dependencies for your build") - if err != nil { - return err - } - // The user choose to create a new remote repo - if remoteRepo == NewRepository { - for { - var repoName, repoUrl string - ioutils.ScanFromConsole("Repository Name", &repoName, repoutils.RepoDefaultName[technologyType][repoutils.Remote]) - ioutils.ScanFromConsole("Repository URL", &repoUrl, repoutils.RepoDefaultName[technologyType][repoutils.RemoteUrl]) - err = CreateRemoteRepo(serviceDetails, technologyType, repoName, repoUrl) - if err != nil { - log.Error(err) - } else { - remoteRepo = repoName - for { - // Create a new virtual repository as well - ioutils.ScanFromConsole(fmt.Sprintf("Choose a name for a new virtual repository which will include %q remote repo", remoteRepo), - &repoName, repoutils.RepoDefaultName[technologyType][repoutils.Virtual]) - err = CreateVirtualRepo(serviceDetails, technologyType, repoName, remoteRepo) - if err != nil { - log.Error(err) - } else { - // We created both remote and virtual repositories successfully - cc.data.BuiltTechnology.VirtualRepo = repoName - return - } - } - } - } - } - // Else, the user choose an existing remote repo - virtualRepos, err := GetAllRepos(serviceDetails, repoutils.Virtual, string(technologyType)) - chosenVirtualRepo := "" - if err != nil { - return err - } - for _, repo := range *virtualRepos { - virtualRepo, err := GetVirtualRepo(serviceDetails, repo.Key) - if err != nil { - return err - } - if contains(virtualRepo.Repositories, remoteRepo) { - chosenVirtualRepo = repo.Key - } - } - if chosenVirtualRepo == "" { - virtualRepoName := repoutils.RepoDefaultName[technologyType][repoutils.Virtual] - for i := 1; i < maxRepoCreationAttempts; i++ { - _, err := GetVirtualRepo(serviceDetails, virtualRepoName) - if err == nil { - err = CreateVirtualRepo(serviceDetails, technologyType, virtualRepoName, remoteRepo) - if err != nil { - return err - } - break - } else { - virtualRepoName = fmt.Sprintf("%s-%d", virtualRepoName, i) - } - } - if err != nil { - return err - } - chosenVirtualRepo = virtualRepoName - } - cc.data.BuiltTechnology.VirtualRepo = chosenVirtualRepo - return -} - -func promptARepoSelection(repoDetails *[]services.RepositoryDetails, promptMsg string) (selectedRepoName string, err error) { - - selectableItems := []ioutils.PromptItem{{Option: NewRepository, TargetValue: &selectedRepoName}} - for _, repo := range *repoDetails { - selectableItems = append(selectableItems, ioutils.PromptItem{Option: repo.Key, TargetValue: &selectedRepoName, DefaultValue: repo.Url}) - } - log.Output(promptMsg) - err = ioutils.SelectString(selectableItems, "", true, func(item ioutils.PromptItem) { - *item.TargetValue = item.Option - }) - return -} - -func promptGitProviderSelection() (selected string, err error) { - gitProviders := []cisetup.GitProvider{ - cisetup.Github, - cisetup.GithubEnterprise, - cisetup.Bitbucket, - cisetup.BitbucketServer, - cisetup.Gitlab, - } - - var selectableItems []ioutils.PromptItem - for _, provider := range gitProviders { - selectableItems = append(selectableItems, ioutils.PromptItem{Option: string(provider), TargetValue: &selected}) - } - log.Output("Choose your project Git provider:") - err = ioutils.SelectString(selectableItems, "", false, func(item ioutils.PromptItem) { - *item.TargetValue = item.Option - }) - return -} - -func promptCiProviderSelection() (selected string, err error) { - ciTypes := []cisetup.CiType{ - cisetup.Pipelines, - cisetup.Jenkins, - cisetup.GithubActions, - } - - var selectableItems []ioutils.PromptItem - for _, ci := range ciTypes { - selectableItems = append(selectableItems, ioutils.PromptItem{Option: string(ci), TargetValue: &selected}) - } - log.Output("Select a CI provider:") - err = ioutils.SelectString(selectableItems, "", false, func(item ioutils.PromptItem) { - *item.TargetValue = item.Option - }) - return -} - -func (cc *CiSetupCommand) prepareVcsData() (err error) { - cc.data.LocalDirPath = DefaultWorkspace - for i := 1; i < maxRepoCreationAttempts; i++ { - err = fileutils.CreateDirIfNotExist(cc.data.LocalDirPath) - if err != nil { - return err - } - dirEmpty, err := fileutils.IsDirEmpty(cc.data.LocalDirPath) - if err != nil { - return err - } - if dirEmpty { - break - } else { - cc.data.LocalDirPath = fmt.Sprintf("%s-%d", DefaultWorkspace, i) - } - } - if err != nil { - return - } - err = cc.cloneProject() - if err != nil { - return - } - err = cc.detectTechnologies() - return -} - -func (cc *CiSetupCommand) cloneProject() (err error) { - // Create the desired path if necessary - err = os.MkdirAll(cc.data.LocalDirPath, os.ModePerm) - if err != nil { - return errorutils.CheckError(err) - } - cloneOption := &git.CloneOptions{ - URL: cc.data.VcsCredentials.Url, - Auth: createCredentials(&cc.data.VcsCredentials), - // Enable git submodules clone if their any. - RecurseSubmodules: git.DefaultSubmoduleRecursionDepth, - } - if cc.data.GitBranch != "" { - cloneOption.ReferenceName = plumbing.ReferenceName(fmt.Sprintf("refs/heads/%s", cc.data.GitBranch)) - } - err = cc.extractRepositoryName() - if err != nil { - return - } - // Clone the given repository to the given directory from the given branch - log.Info(fmt.Sprintf("Cloning project %q from: %q into: %q", cc.data.RepositoryName, cc.data.VcsCredentials.Url, cc.data.LocalDirPath)) - cloneRepo, err := git.PlainClone(cc.data.LocalDirPath, false, cloneOption) - if err != nil { - return errorutils.CheckError(err) - } - if cc.data.GitBranch == "" { - err = cc.extractDefaultBranchName(cloneRepo) - } - return errorutils.CheckError(err) -} - -func (cc *CiSetupCommand) stageCiConfigFile(ciFileName string) error { - log.Info(fmt.Sprintf("Staging %s for git commit...", ciFileName)) - repo, err := git.PlainOpen(cc.data.LocalDirPath) - if err != nil { - return errorutils.CheckError(err) - } - worktree, err := repo.Worktree() - if err != nil { - return errorutils.CheckError(err) - } - _, err = worktree.Add(ciFileName) - return errorutils.CheckError(err) -} - -func (cc *CiSetupCommand) extractRepositoryName() error { - vcsUrl := cc.data.VcsCredentials.Url - if vcsUrl == "" { - return errorutils.CheckErrorf("vcs URL should not be empty") - } - // Trim trailing "/" if one exists - vcsUrl = strings.TrimSuffix(vcsUrl, "/") - cc.data.VcsCredentials.Url = vcsUrl - - // Split vcs url. - splitUrl := strings.Split(vcsUrl, "/") - if len(splitUrl) < 3 { - return errorutils.CheckErrorf("unexpected URL. URL is expected to contain the git provider URL, domain and repository names") - } - cc.data.RepositoryName = strings.TrimSuffix(splitUrl[len(splitUrl)-1], ".git") - cc.data.ProjectDomain = splitUrl[len(splitUrl)-2] - cc.data.VcsBaseUrl = strings.Join(splitUrl[:len(splitUrl)-2], "/") - return nil -} - -func (cc *CiSetupCommand) extractDefaultBranchName(repo *git.Repository) error { - headRef, err := repo.Head() - if err != nil { - return err - } - defaultBranch := path.Base(string(headRef.Name())) // refs/heads/branchName > branchName - cc.data.GitBranch = defaultBranch - return nil -} - -func (cc *CiSetupCommand) detectTechnologies() (err error) { - cc.data.DetectedTechnologies, err = coreutils.DetectTechnologies(cc.data.LocalDirPath, true, true) - return -} - -func createCredentials(serviceDetails *cisetup.VcsServerDetails) (auth transport.AuthMethod) { - var password, username string - if serviceDetails.AccessToken != "" { - password = serviceDetails.AccessToken - // Authentication fails if the username string is empty. This can be anything except an empty string... - username = "user" - } else { - password = serviceDetails.Password - username = serviceDetails.User - } - return &http.BasicAuth{Username: username, Password: password} -} - -func (cc *CiSetupCommand) gitPhase() (err error) { - for { - gitProvider, err := promptGitProviderSelection() - if err != nil { - log.Error(err) - continue - } - cc.data.GitProvider = cisetup.GitProvider(gitProvider) - ioutils.ScanFromConsole("Git project URL", &cc.data.VcsCredentials.Url, cc.defaultData.VcsCredentials.Url) - ioutils.ScanFromConsole("Git username", &cc.data.VcsCredentials.User, cc.defaultData.VcsCredentials.User) - err = writeToScreen("Git access token (requires admin permissions): ") - if err != nil { - return err - } - //nolint:unconvert - byteToken, err := term.ReadPassword(int(syscall.Stdin)) - if err != nil { - log.Error(err) - continue - } - // New-line required after the access token input: - log.Output() - cc.data.VcsCredentials.AccessToken = string(byteToken) - cc.defaultData.GitBranch = "" - gitBranchCaption := "Git branch" - if cc.defaultData.GitBranch == "" { - gitBranchCaption += " (Leave blank for default)" - } - ioutils.ScanFromConsole(gitBranchCaption, &cc.data.GitBranch, cc.defaultData.GitBranch) - err = cc.prepareVcsData() - if err != nil { - log.Error(err) - } else { - return nil - } - } -} - -func (cc *CiSetupCommand) ciProviderPhase() { - for { - ciType, err := promptCiProviderSelection() - if err != nil { - log.Error(err) - continue - } - if ciType == cisetup.Pipelines { - // validate that pipelines is available. - serviceDetails, err := utilsConfig.GetSpecificConfig(cisetup.ConfigServerId, false, false) - if err != nil { - log.Error(err) - continue - } - pipelinesDetails := *serviceDetails - pipelinesDetails.AccessToken = "" - pipelinesDetails.User = "" - pipelinesDetails.Password = "" - - pAuth, err := pipelinesDetails.CreatePipelinesAuthConfig() - if err != nil { - log.Error(err) - continue - } - serviceConfig, err := clientConfig.NewConfigBuilder(). - SetServiceDetails(pAuth). - SetDryRun(false). - Build() - if err != nil { - log.Error(err) - continue - } - pipelinesMgr, err := pipelines.New(serviceConfig) - if err != nil { - log.Error(err) - continue - } - _, err = pipelinesMgr.GetSystemInfo() - if err == nil { - cc.data.CiType = cisetup.CiType(ciType) - return - } - log.Error(err) - if _, ok := err.(*pipelinesservices.PipelinesNotAvailableError); ok { - err = inactivePipelinesNote(pipelinesDetails.PipelinesUrl) - if err != nil { - log.Error(err) - } - } - } else { // The user doesn't choose Pipelines. - cc.data.CiType = cisetup.CiType(ciType) - return - } - } -} diff --git a/general/cisetup/cisetup_test.go b/general/cisetup/cisetup_test.go deleted file mode 100644 index 28e430dda..000000000 --- a/general/cisetup/cisetup_test.go +++ /dev/null @@ -1,46 +0,0 @@ -package cisetup - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestExtractRepositoryName(t *testing.T) { - tests := []struct { - name string - repoUrl string - expectedProjectName string - expectedProjectDomain string - expectedBaseUrl string - errorExpected bool - }{ - {"simpleURL", "https://github.com/jfrog/jfrog-cli", "jfrog-cli", "jfrog", "https://github.com", false}, - {"URLWithTrailingDash", "https://github.com/jfrog/jfrog-cli/", "jfrog-cli", "jfrog", "https://github.com", false}, - {"URLWithGitExtension", "https://github.com/jfrog/jfrog-cli.git", "jfrog-cli", "jfrog", "https://github.com", false}, - {"noProtocol", "localhost:1234/jfrog/jfrog-cli.git", "jfrog-cli", "jfrog", "localhost:1234", false}, - {"emptyURL", "", "", "", "", true}, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - cc := &CiSetupCommand{} - err := cc.prepareConfigurationData() - if err != nil { - assert.NoError(t, err) - return - } - cc.data.VcsCredentials.Url = test.repoUrl - - err = cc.extractRepositoryName() - if test.errorExpected { - assert.Error(t, err) - } else { - assert.NoError(t, err) - assert.Equal(t, test.expectedProjectName, cc.data.RepositoryName) - assert.Equal(t, test.expectedProjectDomain, cc.data.ProjectDomain) - assert.Equal(t, test.expectedBaseUrl, cc.data.VcsBaseUrl) - } - }) - } -} diff --git a/general/cisetup/utils.go b/general/cisetup/utils.go deleted file mode 100644 index 94e025169..000000000 --- a/general/cisetup/utils.go +++ /dev/null @@ -1,102 +0,0 @@ -package cisetup - -import ( - artUtils "github.com/jfrog/jfrog-cli-core/v2/artifactory/utils" - utilsconfig "github.com/jfrog/jfrog-cli-core/v2/utils/config" - "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" - "github.com/jfrog/jfrog-client-go/artifactory/services" - "github.com/jfrog/jfrog-client-go/config" - "github.com/jfrog/jfrog-client-go/xray" -) - -const ( - NewRepository = "[Create new repository]" - - // Build commands defaults - mavenDefaultBuildCmd = "mvn clean install" - gradleDefaultBuildCmd = "gradle clean artifactoryPublish" - npmDefaultBuildCmd = "npm install" -) - -var buildCmdByTech = map[coreutils.Technology]string{ - coreutils.Maven: mavenDefaultBuildCmd, - coreutils.Gradle: gradleDefaultBuildCmd, - coreutils.Npm: npmDefaultBuildCmd, -} - -func CreateXrayServiceManager(serviceDetails *utilsconfig.ServerDetails) (*xray.XrayServicesManager, error) { - xrayDetails, err := serviceDetails.CreateXrayAuthConfig() - if err != nil { - return nil, err - } - serviceConfig, err := config.NewConfigBuilder(). - SetServiceDetails(xrayDetails). - Build() - if err != nil { - return nil, err - } - return xray.New(serviceConfig) -} - -func GetAllRepos(serviceDetails *utilsconfig.ServerDetails, repoType, packageType string) (*[]services.RepositoryDetails, error) { - servicesManager, err := artUtils.CreateServiceManager(serviceDetails, -1, 0, false) - if err != nil { - return nil, err - } - filterParams := services.RepositoriesFilterParams{RepoType: repoType, PackageType: packageType} - return servicesManager.GetAllRepositoriesFiltered(filterParams) -} - -func GetVirtualRepo(serviceDetails *utilsconfig.ServerDetails, repoKey string) (*services.VirtualRepositoryBaseParams, error) { - servicesManager, err := artUtils.CreateServiceManager(serviceDetails, -1, 0, false) - if err != nil { - return nil, err - } - virtualRepoDetails := services.VirtualRepositoryBaseParams{} - err = servicesManager.GetRepository(repoKey, &virtualRepoDetails) - return &virtualRepoDetails, err -} - -func CreateLocalRepo(serviceDetails *utilsconfig.ServerDetails, technologyType coreutils.Technology, repoName string) error { - servicesManager, err := artUtils.CreateServiceManager(serviceDetails, -1, 0, false) - if err != nil { - return err - } - params := services.NewLocalRepositoryBaseParams() - params.PackageType = string(technologyType) - params.Key = repoName - return servicesManager.CreateLocalRepositoryWithParams(params) -} - -func CreateRemoteRepo(serviceDetails *utilsconfig.ServerDetails, technologyType coreutils.Technology, repoName, remoteUrl string) error { - servicesManager, err := artUtils.CreateServiceManager(serviceDetails, -1, 0, false) - if err != nil { - return err - } - params := services.NewRemoteRepositoryBaseParams() - params.PackageType = string(technologyType) - params.Key = repoName - params.Url = remoteUrl - return servicesManager.CreateRemoteRepositoryWithParams(params) -} - -func CreateVirtualRepo(serviceDetails *utilsconfig.ServerDetails, technologyType coreutils.Technology, repoName string, repositories ...string) error { - servicesManager, err := artUtils.CreateServiceManager(serviceDetails, -1, 0, false) - if err != nil { - return err - } - params := services.NewVirtualRepositoryBaseParams() - params.PackageType = string(technologyType) - params.Key = repoName - params.Repositories = repositories - return servicesManager.CreateVirtualRepositoryWithParams(params) -} - -func contains(arr []string, str string) bool { - for _, element := range arr { - if element == str { - return true - } - } - return false -} diff --git a/general/envsetup/envsetup.go b/general/envsetup/envsetup.go deleted file mode 100644 index db6cee91b..000000000 --- a/general/envsetup/envsetup.go +++ /dev/null @@ -1,32 +0,0 @@ -package envsetup - -import ( - "github.com/jfrog/jfrog-cli-core/v2/common/progressbar" - "github.com/jfrog/jfrog-cli-core/v2/general/envsetup" - "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" - "github.com/jfrog/jfrog-cli/utils/cliutils" - "github.com/jfrog/jfrog-client-go/utils/log" - "github.com/urfave/cli" -) - -const ( - registrationPageURL = coreutils.JFrogComUrl + "start-free/cli/" -) - -func RunEnvSetupCmd(c *cli.Context, outputFormat envsetup.OutputFormat) error { - base64Credentials := "" - if outputFormat == envsetup.Human { - if c.NArg() > 1 { - return cliutils.WrongNumberOfArgumentsHandler(c) - } - if c.NArg() == 1 { - base64Credentials = c.Args().Get(0) - } else { - // Setup new user - log.Output(coreutils.PrintTitle("We'll now set up a FREE JFrog environment in the cloud for you, and configure your local machine to use it.")) - log.Output("Your environment will be ready in less than a minute.") - } - } - setupCmd := envsetup.NewEnvSetupCommand().SetRegistrationURL(registrationPageURL).SetEncodedConnectionDetails(base64Credentials).SetOutputFormat(outputFormat) - return progressbar.ExecWithProgress(setupCmd) -} diff --git a/general/invite/invite.go b/general/invite/invite.go deleted file mode 100644 index 5d6dd589e..000000000 --- a/general/invite/invite.go +++ /dev/null @@ -1,20 +0,0 @@ -package invite - -import ( - "github.com/jfrog/jfrog-cli-core/v2/common/commands" - "github.com/jfrog/jfrog-cli-core/v2/general/invite" - "github.com/jfrog/jfrog-cli/utils/cliutils" - "github.com/urfave/cli" -) - -func RunInviteCmd(c *cli.Context) error { - if c.NArg() != 1 { - return cliutils.WrongNumberOfArgumentsHandler(c) - } - serverDetails, err := cliutils.CreateArtifactoryDetailsByFlags(c) - if err != nil { - return err - } - inviteCmd := invite.NewInviteCommand().SetInvitedEmail(c.Args().Get(0)).SetServerDetails(serverDetails) - return commands.Exec(inviteCmd) -} diff --git a/general/project/cli.go b/general/project/cli.go deleted file mode 100644 index 27a2bc54a..000000000 --- a/general/project/cli.go +++ /dev/null @@ -1,52 +0,0 @@ -package project - -import ( - "os" - "path/filepath" - - "github.com/jfrog/jfrog-cli-core/v2/common/commands" - corecommon "github.com/jfrog/jfrog-cli-core/v2/docs/common" - projectlogic "github.com/jfrog/jfrog-cli-core/v2/general/project" - projectinit "github.com/jfrog/jfrog-cli/docs/general/project/init" - clientutils "github.com/jfrog/jfrog-client-go/utils" - "github.com/jfrog/jfrog-client-go/utils/errorutils" - "github.com/urfave/cli" - - "github.com/jfrog/jfrog-cli/utils/cliutils" -) - -func GetCommands() []cli.Command { - return cliutils.GetSortedCommands(cli.CommandsByName{ - { - Name: "init", - Hidden: true, - Usage: projectinit.GetDescription(), - Flags: cliutils.GetCommandFlags(cliutils.InitProject), - HelpName: corecommon.CreateUsage("project init", projectinit.GetDescription(), projectinit.Usage), - BashComplete: corecommon.CreateBashCompletionFunc(), - Action: initProject, - }, - }) -} - -func initProject(c *cli.Context) error { - if c.NArg() > 1 { - return cliutils.WrongNumberOfArgumentsHandler(c) - } - // The default project path is the current directory - path, err := os.Getwd() - if errorutils.CheckError(err) != nil { - return err - } - if c.NArg() == 1 { - path = c.Args().Get(0) - path, err = filepath.Abs(path) - if errorutils.CheckError(err) != nil { - return err - } - } - path = clientutils.AddTrailingSlashIfNeeded(path) - initCmd := projectlogic.NewProjectInitCommand() - initCmd.SetProjectPath(path).SetServerId(c.String("server-id")) - return commands.Exec(initCmd) -} diff --git a/general/summary/cli.go b/general/summary/cli.go new file mode 100644 index 000000000..5736c2a2f --- /dev/null +++ b/general/summary/cli.go @@ -0,0 +1,313 @@ +package summary + +import ( + "errors" + "fmt" + "github.com/jfrog/jfrog-cli-core/v2/artifactory/utils/commandsummary" + "github.com/jfrog/jfrog-cli-security/utils/results/output" + "github.com/jfrog/jfrog-cli/utils/cliutils" + "os" + "path/filepath" + "strings" + + "github.com/jfrog/jfrog-cli-core/v2/artifactory/utils" + commonCliUtils "github.com/jfrog/jfrog-cli-core/v2/common/cliutils" + coreConfig "github.com/jfrog/jfrog-cli-core/v2/utils/config" + "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" + "github.com/jfrog/jfrog-client-go/utils/log" + "github.com/urfave/cli" +) + +type MarkdownSection string + +const ( + Security MarkdownSection = "security" + BuildInfo MarkdownSection = "build-info" + Upload MarkdownSection = "upload" +) + +const ( + markdownFileName = "markdown.md" + finalSarifFileName = "final.sarif" +) + +var markdownSections = []MarkdownSection{Security, BuildInfo, Upload} + +func (ms MarkdownSection) String() string { + return string(ms) +} + +// Generates a combined markdown from all sections, and aggregates multiple SARIF files into one. +func FinalizeCommandSummaries(c *cli.Context) error { + if !shouldGenerateSummary() { + return fmt.Errorf("unable to generate the command summary because the output directory is not specified."+ + " Please ensure that the environment variable '%s' is set before running your commands to enable summary generation", coreutils.SummaryOutputDirPathEnv) + } + + if err := generateSummaryMarkdown(c); err != nil { + return err + } + + return aggregatedCodeScanningSarifs() +} + +// generateSummaryMarkdown creates a summary of recorded CLI commands in Markdown format. +func generateSummaryMarkdown(c *cli.Context) error { + // Get URL and Version to generate summary links + serverUrl, majorVersion, err := extractServerUrlAndVersion(c) + if err != nil { + return fmt.Errorf("failed to get server URL or major version: %v. This means markdown URLs will be invalid", err) + } + + if err = commandsummary.InitMarkdownGenerationValues(serverUrl, majorVersion); err != nil { + return fmt.Errorf("failed to initialize command summary values: %w", err) + } + + // Invoke each section's markdown generation function + for _, section := range markdownSections { + if err := invokeSectionMarkdownGeneration(section); err != nil { + log.Warn("Failed to generate markdown for section:", section, err) + } + } + + // Combine all sections into a single Markdown file + finalMarkdown, err := mergeMarkdownFiles() + if err != nil { + return fmt.Errorf("error combining markdown files: %w", err) + } + + // Saves the final Markdown to the root directory of the command summaries + return saveMarkdownToFileSystem(finalMarkdown) +} + +func aggregatedCodeScanningSarifs() error { + files, err := getSarifFiles() + if err != nil { + return err + } + if len(files) == 0 { + log.Debug("No sarif reports were found") + return nil + } + finalSarif, err := output.CombineSarifOutputFiles(files) + if err != nil { + return err + } + return saveFinalSarifToFileSystem(string(finalSarif)) +} + +func getSarifReportsDir() string { + return filepath.Join(os.Getenv(coreutils.SummaryOutputDirPathEnv), commandsummary.OutputDirName, string(Security), string(commandsummary.SarifReport)) +} + +// The CLI generates summaries in sections, with each section as a separate Markdown file. +// This function merges all sections into a single Markdown file and saves it in the root of the +// command summary output directory. +func mergeMarkdownFiles() (string, error) { + var combinedMarkdown strings.Builder + for _, section := range markdownSections { + sectionContent, err := getSectionMarkdownContent(section) + if err != nil { + return "", fmt.Errorf("error getting markdown content for section %s: %w", section, err) + } + if _, err := combinedMarkdown.WriteString(sectionContent); err != nil { + return "", fmt.Errorf("error writing markdown content for section %s: %w", section, err) + } + } + return combinedMarkdown.String(), nil +} + +// saveMarkdownToFileSystem saves markdown content in the specified directory. +func saveMarkdownToFileSystem(finalMarkdown string) (err error) { + if finalMarkdown == "" { + return nil + } + filePath := filepath.Join(os.Getenv(coreutils.SummaryOutputDirPathEnv), commandsummary.OutputDirName, markdownFileName) + return saveFile(finalMarkdown, filePath) +} + +func saveFile(content, filePath string) (err error) { + if content == "" { + return nil + } + file, err := os.Create(filePath) + if err != nil { + return err + } + defer func() { + err = errors.Join(err, file.Close()) + }() + if _, err = file.WriteString(content); err != nil { + return err + } + return nil +} + +func getSectionMarkdownContent(section MarkdownSection) (string, error) { + sectionFilepath := filepath.Join(os.Getenv(coreutils.SummaryOutputDirPathEnv), commandsummary.OutputDirName, string(section), markdownFileName) + if _, err := os.Stat(sectionFilepath); os.IsNotExist(err) { + return "", nil + } + + contentBytes, err := os.ReadFile(sectionFilepath) + if err != nil { + return "", fmt.Errorf("error reading markdown file for section %s: %w", section, err) + } + if len(contentBytes) == 0 { + return "", nil + } + return string(contentBytes), nil +} + +func getSarifFiles() (files []string, err error) { + indexedFiles, err := commandsummary.GetIndexedDataFilesPaths() + if err != nil { + return + } + sarifsMap := indexedFiles[commandsummary.SarifReport] + for i := range sarifsMap { + files = append(files, sarifsMap[i]) + } + return +} + +func saveFinalSarifToFileSystem(finalSarif string) (err error) { + filePath := filepath.Join(getSarifReportsDir(), finalSarifFileName) + return saveFile(finalSarif, filePath) +} + +// Initiate the desired command summary implementation and invoke its Markdown generation. +func invokeSectionMarkdownGeneration(section MarkdownSection) error { + switch section { + case Security: + return generateSecurityMarkdown() + case BuildInfo: + return generateBuildInfoMarkdown() + case Upload: + return generateUploadMarkdown() + default: + return fmt.Errorf("unknown section: %s", section) + } +} + +func generateSecurityMarkdown() error { + securitySummary, err := output.NewSecurityJobSummary() + if err != nil { + return fmt.Errorf("error generating security markdown: %w", err) + } + return securitySummary.GenerateMarkdown() +} + +func generateBuildInfoMarkdown() error { + buildInfoSummary, err := commandsummary.NewBuildInfoSummary() + if err != nil { + return fmt.Errorf("error generating build-info markdown: %w", err) + } + if err = mapScanResults(); err != nil { + return fmt.Errorf("error mapping scan results: %w", err) + } + return buildInfoSummary.GenerateMarkdown() +} + +func generateUploadMarkdown() error { + if should, err := shouldGenerateUploadSummary(); err != nil || !should { + log.Debug("Skipping upload summary generation due build-info data to avoid duplications...") + return err + } + uploadSummary, err := commandsummary.NewUploadSummary() + if err != nil { + return fmt.Errorf("error generating upload markdown: %w", err) + } + return uploadSummary.GenerateMarkdown() +} + +// mapScanResults maps the scan results saved during runtime into scan components. +func mapScanResults() (err error) { + // Gets the saved scan results file paths. + indexedFiles, err := commandsummary.GetIndexedDataFilesPaths() + if err != nil { + return err + } + securityJobSummary := &output.SecurityJobSummary{} + // Init scan result map + scanResultsMap := make(map[string]commandsummary.ScanResult) + // Set default not scanned component view + scanResultsMap[commandsummary.NonScannedResult] = securityJobSummary.GetNonScannedResult() + commandsummary.StaticMarkdownConfig.SetScanResultsMapping(scanResultsMap) + // Process each scan result file by its type and append to map + for index, keyValue := range indexedFiles { + for scannedEntityName, scanResultDataFilePath := range keyValue { + scanResultsMap, err = processScan(index, scanResultDataFilePath, scannedEntityName, securityJobSummary, scanResultsMap) + if err != nil { + return + } + } + } + return +} + +// Each scan result should be processed according to its index. +// To generate custom view for each scan type. +func processScan(index commandsummary.Index, filePath string, scannedName string, sec *output.SecurityJobSummary, scanResultsMap map[string]commandsummary.ScanResult) (map[string]commandsummary.ScanResult, error) { + var res commandsummary.ScanResult + var err error + switch index { + case commandsummary.DockerScan: + res, err = sec.DockerScan([]string{filePath}) + case commandsummary.BuildScan: + res, err = sec.BuildScan([]string{filePath}) + case commandsummary.BinariesScan: + res, err = sec.BinaryScan([]string{filePath}) + } + scanResultsMap[scannedName] = res + if err != nil { + return nil, err + } + return scanResultsMap, nil +} + +// shouldGenerateUploadSummary checks if upload summary should be generated. +func shouldGenerateUploadSummary() (bool, error) { + buildInfoPath := filepath.Join(os.Getenv(coreutils.SummaryOutputDirPathEnv), commandsummary.OutputDirName, string(BuildInfo)) + if _, err := os.Stat(buildInfoPath); os.IsNotExist(err) { + return true, nil + } + dirEntries, err := os.ReadDir(buildInfoPath) + if err != nil { + return false, fmt.Errorf("error reading directory: %w", err) + } + return len(dirEntries) == 0, nil +} + +func createPlatformDetailsByFlags(c *cli.Context) (*coreConfig.ServerDetails, error) { + platformDetails, err := cliutils.CreateServerDetailsWithConfigOffer(c, true, commonCliUtils.Platform) + if err != nil { + return nil, fmt.Errorf("error creating JFrog Platform details: %w", err) + } + if platformDetails.Url == "" { + return nil, errors.New("no JFrog Platform URL specified, either via the --url flag or as part of the server configuration") + } + return platformDetails, nil +} + +func extractServerUrlAndVersion(c *cli.Context) (platformUrl string, platformMajorVersion int, err error) { + serverDetails, err := createPlatformDetailsByFlags(c) + if err != nil { + return "", 0, fmt.Errorf("error extracting server details: %w", err) + } + platformUrl = serverDetails.Url + + servicesManager, err := utils.CreateServiceManager(serverDetails, -1, 0, false) + if err != nil { + return "", 0, fmt.Errorf("error creating services manager: %w", err) + } + if platformMajorVersion, err = utils.GetRtMajorVersion(servicesManager); err != nil { + return "", 0, fmt.Errorf("error getting Artifactory major platformMajorVersion: %w", err) + } + return +} + +// shouldGenerateSummary checks if the summary should be generated. +func shouldGenerateSummary() bool { + return os.Getenv(coreutils.SummaryOutputDirPathEnv) != "" +} diff --git a/general/token/cli.go b/general/token/cli.go index bb35fd983..b13f3f188 100644 --- a/general/token/cli.go +++ b/general/token/cli.go @@ -73,7 +73,7 @@ func createPlatformDetailsByFlags(c *cli.Context) (*coreConfig.ServerDetails, er return nil, err } if platformDetails.Url == "" { - return nil, errors.New("platform URL is mandatory for access token creation") + return nil, errors.New("no JFrog Platform URL specified, either via the --url flag or as part of the server configuration") } return platformDetails, nil } diff --git a/go.mod b/go.mod index b9b1b1a5d..bbf8dcbaa 100644 --- a/go.mod +++ b/go.mod @@ -1,52 +1,61 @@ module github.com/jfrog/jfrog-cli -go 1.22.3 +go 1.23.3 + +replace ( + // Should not be updated to 0.2.6 due to a bug (https://github.com/jfrog/jfrog-cli-core/pull/372) + github.com/c-bata/go-prompt => github.com/c-bata/go-prompt v0.2.5 + + // Should not be updated to 0.2.0-beta.2 due to a bug (https://github.com/jfrog/jfrog-cli-core/pull/372) + github.com/pkg/term => github.com/pkg/term v1.1.0 +) require ( - github.com/agnivade/levenshtein v1.1.1 + github.com/agnivade/levenshtein v1.2.0 github.com/buger/jsonparser v1.1.1 - github.com/go-git/go-git/v5 v5.12.0 + github.com/docker/docker v27.3.1+incompatible github.com/gocarina/gocsv v0.0.0-20240520201108-78e41c74b4b1 - github.com/jfrog/archiver/v3 v3.6.0 - github.com/jfrog/build-info-go v1.9.26 - github.com/jfrog/gofrog v1.7.1 - github.com/jfrog/jfrog-cli-core/v2 v2.52.0 - github.com/jfrog/jfrog-cli-platform-services v1.3.0 - github.com/jfrog/jfrog-cli-security v1.1.0 - github.com/jfrog/jfrog-client-go v1.40.2 + github.com/jfrog/archiver/v3 v3.6.1 + github.com/jfrog/build-info-go v1.10.5 + github.com/jfrog/gofrog v1.7.6 + github.com/jfrog/jfrog-cli-artifactory v0.1.6 + github.com/jfrog/jfrog-cli-core/v2 v2.56.7 + github.com/jfrog/jfrog-cli-platform-services v1.4.0 + github.com/jfrog/jfrog-cli-security v1.12.5 + github.com/jfrog/jfrog-client-go v1.47.6 github.com/jszwec/csvutil v1.10.0 + github.com/manifoldco/promptui v0.9.0 github.com/stretchr/testify v1.9.0 - github.com/testcontainers/testcontainers-go v0.23.0 - github.com/urfave/cli v1.22.15 + github.com/testcontainers/testcontainers-go v0.34.0 + github.com/urfave/cli v1.22.16 github.com/xeipuuv/gojsonschema v1.2.0 - golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 - golang.org/x/term v0.20.0 + golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c gopkg.in/yaml.v2 v2.4.0 ) require ( dario.cat/mergo v1.0.0 // indirect + github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 // indirect github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect - github.com/BurntSushi/toml v1.3.2 // indirect - github.com/CycloneDX/cyclonedx-go v0.8.0 // indirect - github.com/Microsoft/go-winio v0.6.1 // indirect - github.com/Microsoft/hcsshim v0.11.4 // indirect + github.com/BurntSushi/toml v1.4.0 // indirect + github.com/CycloneDX/cyclonedx-go v0.9.0 // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect github.com/ProtonMail/go-crypto v1.0.0 // indirect github.com/VividCortex/ewma v1.2.0 // indirect github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d // indirect github.com/andybalholm/brotli v1.1.0 // indirect + github.com/beevik/etree v1.4.0 // indirect github.com/c-bata/go-prompt v0.2.6 // indirect - github.com/cenkalti/backoff/v4 v4.2.1 // indirect + github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/chzyer/readline v1.5.1 // indirect - github.com/cloudflare/circl v1.3.7 // indirect - github.com/containerd/containerd v1.7.15 // indirect + github.com/cloudflare/circl v1.4.0 // indirect github.com/containerd/log v0.1.0 // indirect - github.com/cpuguy83/dockercfg v0.3.1 // indirect - github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect + github.com/containerd/platforms v0.2.1 // indirect + github.com/cpuguy83/dockercfg v0.3.2 // indirect + github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect github.com/cyphar/filepath-securejoin v0.2.4 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect - github.com/distribution/reference v0.5.0 // indirect - github.com/docker/docker v25.0.5+incompatible // indirect + github.com/distribution/reference v0.6.0 // indirect github.com/docker/go-connections v0.5.0 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 // indirect @@ -54,95 +63,116 @@ require ( github.com/felixge/httpsnoop v1.0.4 // indirect github.com/forPelevin/gomoji v1.2.0 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect + github.com/gfleury/go-bitbucket-v1 v0.0.0-20230825095122-9bc1711434ab // indirect github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect github.com/go-git/go-billy/v5 v5.5.0 // indirect - github.com/go-logr/logr v1.4.1 // indirect + github.com/go-git/go-git/v5 v5.12.0 // indirect + github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-ole/go-ole v1.2.6 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang-jwt/jwt/v4 v4.5.0 // indirect + github.com/golang-jwt/jwt/v4 v4.5.1 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect - github.com/golang/protobuf v1.5.4 // indirect github.com/golang/snappy v0.0.4 // indirect + github.com/google/go-github/v56 v56.0.0 // indirect + github.com/google/go-querystring v1.1.0 // indirect github.com/google/uuid v1.6.0 // indirect github.com/gookit/color v1.5.4 // indirect + github.com/grokify/mogo v0.64.12 // indirect + github.com/hashicorp/go-cleanhttp v0.5.2 // indirect + github.com/hashicorp/go-retryablehttp v0.7.7 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect - github.com/jedib0t/go-pretty/v6 v6.5.9 // indirect + github.com/jedib0t/go-pretty/v6 v6.6.1 // indirect + github.com/jfrog/froggit-go v1.16.2 // indirect github.com/jfrog/jfrog-apps-config v1.0.1 // indirect github.com/kevinburke/ssh_config v1.2.0 // indirect - github.com/klauspost/compress v1.17.7 // indirect + github.com/klauspost/compress v1.17.9 // indirect github.com/klauspost/cpuid/v2 v2.2.7 // indirect github.com/klauspost/pgzip v1.2.6 // indirect + github.com/ktrysmt/go-bitbucket v0.9.80 // indirect + github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect github.com/magiconair/properties v1.8.7 // indirect - github.com/manifoldco/promptui v0.9.0 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - github.com/mattn/go-runewidth v0.0.15 // indirect + github.com/mattn/go-runewidth v0.0.16 // indirect github.com/mattn/go-tty v0.0.5 // indirect + github.com/microsoft/azure-devops-go-api/azuredevops/v7 v7.1.0 // indirect github.com/minio/sha256-simd v1.0.1 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/moby/docker-image-spec v1.3.1 // indirect github.com/moby/patternmatcher v0.6.0 // indirect github.com/moby/sys/sequential v0.5.0 // indirect github.com/moby/sys/user v0.1.0 // indirect + github.com/moby/sys/userns v0.1.0 // indirect github.com/moby/term v0.5.0 // indirect github.com/morikuni/aec v1.0.0 // indirect github.com/nwaples/rardecode v1.1.3 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0 // indirect github.com/owenrumney/go-sarif/v2 v2.3.0 // indirect - github.com/pelletier/go-toml/v2 v2.2.0 // indirect + github.com/pelletier/go-toml/v2 v2.2.2 // indirect github.com/pierrec/lz4/v4 v4.1.21 // indirect github.com/pjbgf/sha1cd v0.3.0 // indirect github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pkg/term v1.2.0-beta.2 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect github.com/rivo/uniseg v0.4.7 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/sagikazarmark/locafero v0.4.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect + github.com/secure-systems-lab/go-securesystemslib v0.8.0 // indirect github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect + github.com/shirou/gopsutil/v3 v3.23.12 // indirect + github.com/shoenig/go-m1cpu v0.1.6 // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/skeema/knownhosts v1.2.2 // indirect github.com/sourcegraph/conc v0.3.0 // indirect github.com/spf13/afero v1.11.0 // indirect github.com/spf13/cast v1.6.0 // indirect github.com/spf13/pflag v1.0.5 // indirect - github.com/spf13/viper v1.18.2 // indirect + github.com/spf13/viper v1.19.0 // indirect github.com/subosito/gotenv v1.6.0 // indirect + github.com/tklauser/go-sysconf v0.3.12 // indirect + github.com/tklauser/numcpus v0.6.1 // indirect github.com/ulikunitz/xz v0.5.12 // indirect - github.com/vbauerster/mpb/v7 v7.5.3 // indirect + github.com/vbauerster/mpb/v8 v8.8.3 // indirect + github.com/xanzy/go-gitlab v0.110.0 // indirect github.com/xanzy/ssh-agent v0.3.3 // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect + github.com/yusufpapurcu/wmi v1.2.3 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect - go.opentelemetry.io/otel v1.24.0 // indirect - go.opentelemetry.io/otel/metric v1.24.0 // indirect - go.opentelemetry.io/otel/trace v1.24.0 // indirect + go.opentelemetry.io/otel v1.31.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.31.0 // indirect + go.opentelemetry.io/otel/metric v1.31.0 // indirect + go.opentelemetry.io/otel/sdk v1.31.0 // indirect + go.opentelemetry.io/otel/trace v1.31.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/crypto v0.23.0 // indirect - golang.org/x/mod v0.17.0 // indirect - golang.org/x/net v0.25.0 // indirect - golang.org/x/sync v0.7.0 // indirect - golang.org/x/sys v0.20.0 // indirect - golang.org/x/text v0.15.0 // indirect - golang.org/x/tools v0.21.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f // indirect - google.golang.org/grpc v1.59.0 // indirect - google.golang.org/protobuf v1.33.0 // indirect + golang.org/x/crypto v0.28.0 // indirect + golang.org/x/mod v0.21.0 // indirect + golang.org/x/net v0.30.0 // indirect + golang.org/x/oauth2 v0.20.0 // indirect + golang.org/x/sync v0.8.0 // indirect + golang.org/x/sys v0.26.0 // indirect + golang.org/x/term v0.25.0 // indirect + golang.org/x/text v0.19.0 // indirect + golang.org/x/time v0.5.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) -// replace github.com/jfrog/jfrog-cli-core/v2 => github.com/jfrog/jfrog-cli-core/v2 v2.31.1-0.20240505160419-7173b506c6b7 +replace github.com/jfrog/jfrog-cli-core/v2 => github.com/jfrog/jfrog-cli-core/v2 v2.31.1-0.20241107130834-59ac9764f8b9 -// replace github.com/jfrog/jfrog-client-go => github.com/jfrog/jfrog-client-go v1.28.1-0.20240505164307-d12abb9f140e +// replace github.com/jfrog/jfrog-cli-security => github.com/jfrog/jfrog-cli-security v1.12.5-0.20241107141149-42cf964808a1 -// replace github.com/jfrog/jfrog-cli-security => github.com/jfrog/jfrog-cli-security v1.0.6-0.20240408061620-c9b84da33d5e +// replace github.com/jfrog/jfrog-client-go => github.com/jfrog/jfrog-client-go v1.28.1-0.20240918081224-1c584cc334c7 -// replace github.com/jfrog/build-info-go => github.com/jfrog/build-info-go dev +// replace github.com/jfrog/build-info-go => github.com/jfrog/build-info-go v1.8.9-0.20240918150101-ad5b10435a12 // replace github.com/jfrog/gofrog => github.com/jfrog/gofrog dev diff --git a/go.sum b/go.sum index 443210ade..cd64f9acc 100644 --- a/go.sum +++ b/go.sum @@ -1,26 +1,26 @@ +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= -github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9vkmnHYOMsOr4WLk+Vo07yKIzd94sVoIqshQ4bU= -github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= -github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= -github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= -github.com/CycloneDX/cyclonedx-go v0.8.0 h1:FyWVj6x6hoJrui5uRQdYZcSievw3Z32Z88uYzG/0D6M= -github.com/CycloneDX/cyclonedx-go v0.8.0/go.mod h1:K2bA+324+Og0X84fA8HhN2X066K7Bxz4rpMQ4ZhjtSk= +github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0= +github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= +github.com/CycloneDX/cyclonedx-go v0.9.0 h1:inaif7qD8bivyxp7XLgxUYtOXWtDez7+j72qKTMQTb8= +github.com/CycloneDX/cyclonedx-go v0.9.0/go.mod h1:NE/EWvzELOFlG6+ljX/QeMlVt9VKcTwu8u0ccsACEsw= github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= -github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= -github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= -github.com/Microsoft/hcsshim v0.11.4 h1:68vKo2VN8DE9AdN4tnkWnmdhqdbpUFM8OF3Airm7fz8= -github.com/Microsoft/hcsshim v0.11.4/go.mod h1:smjE4dvqPX9Zldna+t5FG3rnoHhaB7QYxPRqGcpAD9w= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/ProtonMail/go-crypto v1.0.0 h1:LRuvITjQWX+WIfr930YHG2HNfjR1uOfyf5vE0kC2U78= github.com/ProtonMail/go-crypto v1.0.0/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= github.com/VividCortex/ewma v1.2.0 h1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1ow= github.com/VividCortex/ewma v1.2.0/go.mod h1:nz4BbCtbLyFDeC9SUHbtcT5644juEuWfUAUnGx7j5l4= github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8= github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo= -github.com/agnivade/levenshtein v1.1.1 h1:QY8M92nrzkmr798gCo3kmMyqXFzdQVpxLlGPRBij0P8= -github.com/agnivade/levenshtein v1.1.1/go.mod h1:veldBMzWxcCG2ZvUTKD2kJNRdCk5hVbJomOvKkmgYbo= +github.com/agnivade/levenshtein v1.2.0 h1:U9L4IOT0Y3i0TIlUIDJ7rVUziKi/zPbrJGaFrtYH3SY= +github.com/agnivade/levenshtein v1.2.0/go.mod h1:QVVI16kDrtSuwcpd0p1+xMC6Z/VfhtCyDIjcwga4/DU= github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M= github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= @@ -30,15 +30,17 @@ github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= +github.com/beevik/etree v1.4.0 h1:oz1UedHRepuY3p4N5OjE0nK1WLCqtzHf25bxplKOHLs= +github.com/beevik/etree v1.4.0/go.mod h1:cyWiXwGoasx60gHvtnEh5x8+uIjUVnjWqBvEnhnqKDA= github.com/bradleyjkemp/cupaloy/v2 v2.8.0 h1:any4BmKE+jGIaMpnU8YgH/I2LPiLBufr6oMMlVBbn9M= github.com/bradleyjkemp/cupaloy/v2 v2.8.0/go.mod h1:bm7JXdkRd4BHJk9HpwqAI8BoAY1lps46Enkdqw6aRX0= github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= -github.com/c-bata/go-prompt v0.2.6 h1:POP+nrHE+DfLYx370bedwNhsqmpCUynWPxuHi0C5vZI= -github.com/c-bata/go-prompt v0.2.6/go.mod h1:/LMAke8wD2FsNu9EXNdHxNLbd9MedkPnCdfpU9wwHfY= -github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= -github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/c-bata/go-prompt v0.2.5 h1:3zg6PecEywxNn0xiqcXHD96fkbxghD+gdB2tbsYfl+Y= +github.com/c-bata/go-prompt v0.2.5/go.mod h1:vFnjEGDIIA/Lib7giyE4E9c50Lvl8j0S+7FVlAwDAVw= +github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/logex v1.2.1 h1:XHDu3E6q+gdHgsdTPH6ImJMIp436vR6MPtH8gP05QzM= github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ= @@ -49,16 +51,17 @@ github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMn github.com/chzyer/test v1.0.0 h1:p3BQDXSxOhOG0P9z6/hGnII4LGiEPOYBhs8asl/fC04= github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8= github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= -github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= -github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= -github.com/containerd/containerd v1.7.15 h1:afEHXdil9iAm03BmhjzKyXnnEBtjaLJefdU7DV0IFes= -github.com/containerd/containerd v1.7.15/go.mod h1:ISzRRTMF8EXNpJlTzyr2XMhN+j9K302C21/+cr3kUnY= +github.com/cloudflare/circl v1.4.0 h1:BV7h5MgrktNzytKmWjpOtdYrf0lkkbF8YMlBGPhJQrY= +github.com/cloudflare/circl v1.4.0/go.mod h1:PDRU+oXvdD7KCtgKxW95M5Z8BpSCJXQORiZFnBQS5QU= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= -github.com/cpuguy83/dockercfg v0.3.1 h1:/FpZ+JaygUR/lZP2NlFI2DVfrOEMAIKP5wWEJdoYe9E= -github.com/cpuguy83/dockercfg v0.3.1/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= -github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4= -github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A= +github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw= +github.com/cpuguy83/dockercfg v0.3.2 h1:DlJTyZGBDlXqUZ2Dk2Q3xHs/FtnooJJVaad2S9GKorA= +github.com/cpuguy83/dockercfg v0.3.2/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= +github.com/cpuguy83/go-md2man/v2 v2.0.5 h1:ZtcqGrnekaHpVLArFSe4HK5DoKx1T0rq2DwVB0alcyc= +github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg= @@ -67,12 +70,12 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48 h1:fRzb/w+pyskVMQ+UbP35JkH8yB7MYb4q/qhBarqZE6g= -github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA= -github.com/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0= -github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/docker/docker v25.0.5+incompatible h1:UmQydMduGkrD5nQde1mecF/YnSbTOaPeFIeP5C4W+DE= -github.com/docker/docker v25.0.5+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/dgryski/trifles v0.0.0-20230903005119-f50d829f2e54 h1:SG7nF6SRlWhcT7cNTs5R6Hk4V2lcmLz2NsG2VnInyNo= +github.com/dgryski/trifles v0.0.0-20230903005119-f50d829f2e54/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA= +github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= +github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= +github.com/docker/docker v27.3.1+incompatible h1:KttF0XoteNTicmUtBO0L2tP+J7FGRFTjaEF4k6WdhfI= +github.com/docker/docker v27.3.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= @@ -84,6 +87,8 @@ github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a h1:mATvB/9r/3gvcej github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= +github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= +github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/forPelevin/gomoji v1.2.0 h1:9k4WVSSkE1ARO/BWywxgEUBvR/jMnao6EZzrql5nxJ8= @@ -92,6 +97,8 @@ github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHk github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= +github.com/gfleury/go-bitbucket-v1 v0.0.0-20230825095122-9bc1711434ab h1:+7KwW/yy/ThnRXW9khailFFncxJiiFpxyk5BI9GK9pI= +github.com/gfleury/go-bitbucket-v1 v0.0.0-20230825095122-9bc1711434ab/go.mod h1:IqOZzks2wlWCIai0esXnZPdPwxF2yOz0HcCYw5I4pCg= github.com/gliderlabs/ssh v0.3.7 h1:iV3Bqi942d9huXnzEF2Mt+CY9gLu8DNM4Obd+8bODRE= github.com/gliderlabs/ssh v0.3.7/go.mod h1:zpHEXBstFnQYtGnB8k8kQLol82umzn/2/snG7alWVD8= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= @@ -103,81 +110,111 @@ github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod github.com/go-git/go-git/v5 v5.12.0 h1:7Md+ndsjrzZxbddRDZjF14qK+NN56sy6wkqaVrjZtys= github.com/go-git/go-git/v5 v5.12.0/go.mod h1:FTM9VKtnI2m65hNI/TenDDDnUf2Q9FHnXYjuz9i5OEY= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= -github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= +github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/gocarina/gocsv v0.0.0-20240520201108-78e41c74b4b1 h1:FWNFq4fM1wPfcK40yHE5UO3RUdSNPaBC+j3PokzA6OQ= github.com/gocarina/gocsv v0.0.0-20240520201108-78e41c74b4b1/go.mod h1:5YoVOkjYAQumqlV356Hj3xeYh4BdZuLE0/nRkf2NKkI= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= -github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/golang-jwt/jwt/v4 v4.5.1 h1:JdqV9zKUdtaa9gdPlywC3aeoEsR681PlKC+4F5gQgeo= +github.com/golang-jwt/jwt/v4 v4.5.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= -github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-github/v56 v56.0.0 h1:TysL7dMa/r7wsQi44BjqlwaHvwlFlqkK8CtBWCX3gb4= +github.com/google/go-github/v56 v56.0.0/go.mod h1:D8cdcX98YWJvi7TLo7zM4/h8ZTx6u6fwGEkCdisopo0= +github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= +github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gookit/color v1.5.4 h1:FZmqs7XOyGgCAxmWyPslpiok1k05wmY3SJTytgvYFs0= github.com/gookit/color v1.5.4/go.mod h1:pZJOeOS8DM43rXbp4AZo1n9zCU2qjpcRko0b6/QJi9w= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg= +github.com/grokify/mogo v0.64.12 h1:BNrZ1qBFuX4qu5722CW6qtqu/mrrsZ3bhKu/w1KowKg= +github.com/grokify/mogo v0.64.12/go.mod h1:lDhfYIiOhJo7C2U3aL00PlUU9gLvmTONi4MdIWoGmGM= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 h1:asbCHRVmodnJTuQ3qamDwqVOIjwqUPTYmYuemVOx+Ys= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0/go.mod h1:ggCgvZ2r7uOoQjOyu2Y1NhHmEPPzzuhWgcza5M1Ji1I= +github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= +github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= +github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= +github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= +github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU= +github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= -github.com/jedib0t/go-pretty/v6 v6.5.9 h1:ACteMBRrrmm1gMsXe9PSTOClQ63IXDUt03H5U+UV8OU= -github.com/jedib0t/go-pretty/v6 v6.5.9/go.mod h1:zbn98qrYlh95FIhwwsbIip0LYpwSG8SUOScs+v9/t0E= -github.com/jfrog/archiver/v3 v3.6.0 h1:OVZ50vudkIQmKMgA8mmFF9S0gA47lcag22N13iV3F1w= -github.com/jfrog/archiver/v3 v3.6.0/go.mod h1:fCAof46C3rAXgZurS8kNRNdSVMKBbZs+bNNhPYxLldI= -github.com/jfrog/build-info-go v1.9.26 h1:1Ddc6+Ecvhc+UMnKhRVG1jGM6fYNwA49207azTBGBc8= -github.com/jfrog/build-info-go v1.9.26/go.mod h1:8T7/ajM9aGshvgpwCtXwIFpyF/R6CEn4W+/FLryNXWw= -github.com/jfrog/gofrog v1.7.1 h1:ME1Meg4hukAT/7X6HUQCVSe4DNjMZACCP8aCY37EW/w= -github.com/jfrog/gofrog v1.7.1/go.mod h1:X7bjfWoQDN0Z4FQGbE91j3gbPP7Urwzm4Z8tkvrlbRI= +github.com/jedib0t/go-pretty/v6 v6.6.1 h1:iJ65Xjb680rHcikRj6DSIbzCex2huitmc7bDtxYVWyc= +github.com/jedib0t/go-pretty/v6 v6.6.1/go.mod h1:zbn98qrYlh95FIhwwsbIip0LYpwSG8SUOScs+v9/t0E= +github.com/jfrog/archiver/v3 v3.6.1 h1:LOxnkw9pOn45DzCbZNFV6K0+6dCsQ0L8mR3ZcujO5eI= +github.com/jfrog/archiver/v3 v3.6.1/go.mod h1:VgR+3WZS4N+i9FaDwLZbq+jeU4B4zctXL+gL4EMzfLw= +github.com/jfrog/build-info-go v1.10.5 h1:cW03JlPlKv7RMUU896uLUxyLWXAmCgR5Y5QX0fwgz0Q= +github.com/jfrog/build-info-go v1.10.5/go.mod h1:JcISnovFXKx3wWf3p1fcMmlPdt6adxScXvoJN4WXqIE= +github.com/jfrog/froggit-go v1.16.2 h1:F//S83iXH14qsCwYzv0zB2JtjS2pJVEsUoEmYA+37dQ= +github.com/jfrog/froggit-go v1.16.2/go.mod h1:5VpdQfAcbuyFl9x/x8HGm7kVk719kEtW/8YJFvKcHPA= +github.com/jfrog/gofrog v1.7.6 h1:QmfAiRzVyaI7JYGsB7cxfAJePAZTzFz0gRWZSE27c6s= +github.com/jfrog/gofrog v1.7.6/go.mod h1:ntr1txqNOZtHplmaNd7rS4f8jpA5Apx8em70oYEe7+4= github.com/jfrog/jfrog-apps-config v1.0.1 h1:mtv6k7g8A8BVhlHGlSveapqf4mJfonwvXYLipdsOFMY= github.com/jfrog/jfrog-apps-config v1.0.1/go.mod h1:8AIIr1oY9JuH5dylz2S6f8Ym2MaadPLR6noCBO4C22w= -github.com/jfrog/jfrog-cli-core/v2 v2.52.0 h1:0+kgk9FIJCIMLlJWC2SfMU9vb9Sa0rBtH+CqxW3bsxU= -github.com/jfrog/jfrog-cli-core/v2 v2.52.0/go.mod h1:hB5R+BgmCbOoz5HZyyqEdv408rL26ej7ZSHfXGpLqmw= -github.com/jfrog/jfrog-cli-platform-services v1.3.0 h1:IblSDZFBjL7WLRi37Ni2DmHrXJJ6ysSMxx7t41AvyDA= -github.com/jfrog/jfrog-cli-platform-services v1.3.0/go.mod h1:Ky4SDXuMeaiNP/5zMT1YSzIuXG+cNYYOl8BaEA7Awbc= -github.com/jfrog/jfrog-cli-security v1.1.0 h1:ifCjFJSa1D1pWyW/ADYPqnMkOddzkAT/WY4vHAufn1g= -github.com/jfrog/jfrog-cli-security v1.1.0/go.mod h1:086t7e/einVAGfBXxRdEGDKovWt67I6SqUb1rcpdiZc= -github.com/jfrog/jfrog-client-go v1.40.2 h1:zdCWPPT11r0bMGnAXGhZPb3RrIINhiTFCceQABhguZ4= -github.com/jfrog/jfrog-client-go v1.40.2/go.mod h1:m3hIn12eFWk5nJH1swPRtFrjXbiiCscOpX+v/vCdmNI= +github.com/jfrog/jfrog-cli-artifactory v0.1.6 h1:bMfJsrLQJw0dZp4nqUf1xOmtY0rpCatW/I5q88x+fhQ= +github.com/jfrog/jfrog-cli-artifactory v0.1.6/go.mod h1:jbNb22ebtupcjdhrdGq0VBew2vWG6VUK04xxGNDfynE= +github.com/jfrog/jfrog-cli-core/v2 v2.31.1-0.20241107130834-59ac9764f8b9 h1:zAlXZrJRbThdMtA5UDjC0RouJ/OVY/zv9+VI54NTbFo= +github.com/jfrog/jfrog-cli-core/v2 v2.31.1-0.20241107130834-59ac9764f8b9/go.mod h1:2E9LLSLDYNwp2585LyMuDaNMuq+ohvgZYg7K5EY933o= +github.com/jfrog/jfrog-cli-platform-services v1.4.0 h1:g6A30+tOfXd1h6VASeNwH+5mhs5bPQJ0MFzZs/4nlvs= +github.com/jfrog/jfrog-cli-platform-services v1.4.0/go.mod h1:Ky4SDXuMeaiNP/5zMT1YSzIuXG+cNYYOl8BaEA7Awbc= +github.com/jfrog/jfrog-cli-security v1.12.5 h1:2JHPyapXuHQw/qEaElGxBUGrJCZlVFLXDdxkqhf10vE= +github.com/jfrog/jfrog-cli-security v1.12.5/go.mod h1:5LBGwth7TXkEH8MO0JJXvpoRktMAV2BK7Q5nQePNrv4= +github.com/jfrog/jfrog-client-go v1.47.6 h1:nEMwJvjsuuY6LpOV3e33P4c4irPHkG8Qxw27bgeCl/Y= +github.com/jfrog/jfrog-client-go v1.47.6/go.mod h1:jCpvS83DZHAin2aSG7VroTsILJsyq7AOcFfx++P241E= github.com/jszwec/csvutil v1.10.0 h1:upMDUxhQKqZ5ZDCs/wy+8Kib8rZR8I8lOR34yJkdqhI= github.com/jszwec/csvutil v1.10.0/go.mod h1:/E4ONrmGkwmWsk9ae9jpXnv9QT8pLHEPcCirMFhxG9I= +github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k= +github.com/k0kubun/pp v3.0.1+incompatible/go.mod h1:GWse8YhT0p8pT4ir3ZgBbfZild3tgzSScAn6HmfYukg= github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= -github.com/klauspost/compress v1.17.7 h1:ehO88t2UGzQK66LMdE8tibEd1ErmzZjNEqWkjLAKQQg= -github.com/klauspost/compress v1.17.7/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= +github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU= github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/ktrysmt/go-bitbucket v0.9.80 h1:S+vZTXKx/VG5yCaX4I3Bmwo8lxWr4ifvuHdTboHTMMc= +github.com/ktrysmt/go-bitbucket v0.9.80/go.mod h1:b8ogWEGxQMWoeFnT1ZE4aHIPGindI+9z/zAW/OVFjk0= +github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= +github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA= github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= @@ -191,22 +228,28 @@ github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D github.com/mattn/go-runewidth v0.0.6/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= -github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= -github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= -github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= +github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-tty v0.0.3/go.mod h1:ihxohKRERHTVzN+aSVRwACLCeqIoZAWpoICkkvrWyR0= github.com/mattn/go-tty v0.0.5 h1:s09uXI7yDbXzzTTfw3zonKFzwGkyYlgU3OMjqA0ddz4= github.com/mattn/go-tty v0.0.5/go.mod h1:u5GGXBtZU6RQoKV8gY5W6UhMudbR5vXnUe7j3pxse28= +github.com/microsoft/azure-devops-go-api/azuredevops/v7 v7.1.0 h1:mmJCWLe63QvybxhW1iBmQWEaCKdc4SKgALfTNZ+OphU= +github.com/microsoft/azure-devops-go-api/azuredevops/v7 v7.1.0/go.mod h1:mDunUZ1IUJdJIRHvFb+LPBUtxe3AYB5MI6BMXNg8194= github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM= github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= +github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk= github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= github.com/moby/sys/sequential v0.5.0/go.mod h1:tH2cOOs5V9MlPiXcQzRC+eEyab644PWKGRYaaV5ZZlo= github.com/moby/sys/user v0.1.0 h1:WmZ93f5Ux6het5iituh9x2zAG7NFY9Aqi49jjE1PaQg= github.com/moby/sys/user v0.1.0/go.mod h1:fKJhFOnsCN6xZ5gSfbM6zaHGgDJMrqt9/reuj4T7MmU= +github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g= +github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28= github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= @@ -222,24 +265,29 @@ github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2sz github.com/owenrumney/go-sarif v1.1.1/go.mod h1:dNDiPlF04ESR/6fHlPyq7gHKmrM0sHUvAGjsoh8ZH0U= github.com/owenrumney/go-sarif/v2 v2.3.0 h1:wP5yEpI53zr0v5cBmagXzLbHZp9Oylyo3AJDpfLBITs= github.com/owenrumney/go-sarif/v2 v2.3.0/go.mod h1:MSqMMx9WqlBSY7pXoOZWgEsVB4FDNfhcaXDA1j6Sr+w= -github.com/pelletier/go-toml/v2 v2.2.0 h1:QLgLl2yMN7N+ruc31VynXs1vhMZa7CeHHejIeBAsoHo= -github.com/pelletier/go-toml/v2 v2.2.0/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= +github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= +github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ= github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4= github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/term v1.2.0-beta.2 h1:L3y/h2jkuBVFdWiJvNfYfKmzcCnILw7mJWm2JQuMppw= -github.com/pkg/term v1.2.0-beta.2/go.mod h1:E25nymQcrSllhX42Ok8MRm1+hyBdHY0dCeiKZ9jpNGw= +github.com/pkg/term v1.1.0 h1:xIAAdCMh3QIAy+5FrE8Ad8XoDhEU4ufwbaSozViP9kk= +github.com/pkg/term v1.1.0/go.mod h1:E25nymQcrSllhX42Ok8MRm1+hyBdHY0dCeiKZ9jpNGw= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= +github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= @@ -248,8 +296,16 @@ github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6ke github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= +github.com/secure-systems-lab/go-securesystemslib v0.8.0 h1:mr5An6X45Kb2nddcFlbmfHkLguCE9laoZCUzEEpIZXA= +github.com/secure-systems-lab/go-securesystemslib v0.8.0/go.mod h1:UH2VZVuJfCYR8WgMlCU1uFsOUU+KeyrTWcSS73NBOzU= github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= +github.com/shirou/gopsutil/v3 v3.23.12 h1:z90NtUkp3bMtmICZKpC4+WaknU1eXtp5vtbQ11DgpE4= +github.com/shirou/gopsutil/v3 v3.23.12/go.mod h1:1FrWgea594Jp7qmjHUUPlJDTPgcsb9mGnXDxavtikzM= +github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= +github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= +github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU= +github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= @@ -263,11 +319,12 @@ github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ= -github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk= +github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI= +github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= @@ -282,17 +339,23 @@ github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8 github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/terminalstatic/go-xsd-validate v0.1.5 h1:RqpJnf6HGE2CB/lZB1A8BYguk8uRtcvYAPLCF15qguo= github.com/terminalstatic/go-xsd-validate v0.1.5/go.mod h1:18lsvYFofBflqCrvo1umpABZ99+GneNTw2kEEc8UPJw= -github.com/testcontainers/testcontainers-go v0.23.0 h1:ERYTSikX01QczBLPZpqsETTBO7lInqEP349phDOVJVs= -github.com/testcontainers/testcontainers-go v0.23.0/go.mod h1:3gzuZfb7T9qfcH2pHpV4RLlWrPjeWNQah6XlYQ32c4I= +github.com/testcontainers/testcontainers-go v0.34.0 h1:5fbgF0vIN5u+nD3IWabQwRybuB4GY8G2HHgCkbMzMHo= +github.com/testcontainers/testcontainers-go v0.34.0/go.mod h1:6P/kMkQe8yqPHfPWNulFGdFHTD8HB2vLq/231xY2iPQ= +github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= +github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= +github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= +github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc= github.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= -github.com/urfave/cli v1.22.15 h1:nuqt+pdC/KqswQKhETJjo7pvn/k4xMUxgW6liI7XpnM= -github.com/urfave/cli v1.22.15/go.mod h1:wSan1hmo5zeyLGBjRJbzRTNk8gwoYa2B9n4q9dmRIc0= -github.com/vbauerster/mpb/v7 v7.5.3 h1:BkGfmb6nMrrBQDFECR/Q7RkKCw7ylMetCb4079CGs4w= -github.com/vbauerster/mpb/v7 v7.5.3/go.mod h1:i+h4QY6lmLvBNK2ah1fSreiw3ajskRlBp9AhY/PnuOE= +github.com/urfave/cli v1.22.16 h1:MH0k6uJxdwdeWQTwhSO42Pwr4YLrNLwBtg1MRgTqPdQ= +github.com/urfave/cli v1.22.16/go.mod h1:EeJR6BKodywf4zciqrdw6hpCPk68JO9z5LazXZMn5Po= +github.com/vbauerster/mpb/v8 v8.8.3 h1:dTOByGoqwaTJYPubhVz3lO5O6MK553XVgUo33LdnNsQ= +github.com/vbauerster/mpb/v8 v8.8.3/go.mod h1:JfCCrtcMsJwP6ZwMn9e5LMnNyp3TVNpUWWkN+nd4EWk= github.com/vmihailenco/msgpack/v4 v4.3.12/go.mod h1:gborTTJjAo/GWTqqRjrLCn9pgNN+NXzzngzBKDPIqw4= github.com/vmihailenco/tagparser v0.1.1/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI= +github.com/xanzy/go-gitlab v0.110.0 h1:hsFIFp01v/0D0sdUXoZfRk6CROzZbHQplk6NzKSFKhc= +github.com/xanzy/go-gitlab v0.110.0/go.mod h1:wKNKh3GkYDMOsGmnfuX+ITCmDuSDWFO0G+C4AygL9RY= github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= @@ -309,23 +372,27 @@ github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJu github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw= +github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= github.com/zclconf/go-cty v1.10.0/go.mod h1:vVKLxnk3puL4qRAv72AO+W99LUD4da90g3uUAzyuvAk= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw= -go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo= -go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0 h1:Mne5On7VWdx7omSrSSZvM4Kw7cS7NQkOOmLcgscI51U= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0/go.mod h1:IPtUMKL4O3tH5y+iXVyAXqpAwMuzC1IrxVS81rummfE= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0 h1:IeMeyr1aBvBiPVYihXIaeIZba6b8E1bYp7lbdxK8CQg= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0/go.mod h1:oVdCUtjq9MK9BlS7TtucsQwUcXcymNiEDjgDD2jMtZU= -go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI= -go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco= -go.opentelemetry.io/otel/sdk v1.19.0 h1:6USY6zH+L8uMH8L3t1enZPR3WFEmSTADlqldyHtJi3o= -go.opentelemetry.io/otel/sdk v1.19.0/go.mod h1:NedEbbS4w3C6zElbLdPJKOpJQOrGUJ+GfzpjUvI0v1A= -go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI= -go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= -go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I= -go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM= +go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY= +go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.31.0 h1:K0XaT3DwHAcV4nKLzcQvwAgSyisUghWoY20I7huthMk= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.31.0/go.mod h1:B5Ki776z/MBnVha1Nzwp5arlzBbE3+1jk+pGmaP5HME= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.31.0 h1:lUsI2TYsQw2r1IASwoROaCnjdj2cvC2+Jbxvk6nHnWU= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.31.0/go.mod h1:2HpZxxQurfGxJlJDblybejHB6RX6pmExPNe517hREw4= +go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE= +go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY= +go.opentelemetry.io/otel/sdk v1.31.0 h1:xLY3abVHYZ5HSfOg3l2E5LUj2Cwva5Y7yGxnSW9H5Gk= +go.opentelemetry.io/otel/sdk v1.31.0/go.mod h1:TfRbMdhvxIIr/B2N2LQW2S5v9m3gOQ/08KsbbO5BPT0= +go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys= +go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A= +go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= +go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= +go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU= +go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -335,16 +402,20 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= -golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= -golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= -golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM= -golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= +golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c h1:7dEasQXItcW1xKJ2+gg5VOiBnqWrJc+rq0DPKyvvdbY= +golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c/go.mod h1:NQtJDoLvd6faHhE7m4T/1IY708gDefGGjR/iUW8yQQ8= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= -golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0= +golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -356,20 +427,29 @@ golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= -golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= -golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= +golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= +golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.20.0 h1:4mQdhULixXKP1rwYBW0vAijoXnkTG0BLCDRzfe1idMo= +golang.org/x/oauth2 v0.20.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -379,6 +459,7 @@ golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200918174421-af09f7315aff/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -388,21 +469,28 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220909162455-aba9fc2a8ff2/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= -golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= -golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw= -golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= +golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= +golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= +golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= @@ -412,8 +500,10 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= -golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -422,27 +512,29 @@ golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roY golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.21.0 h1:qc0xYgIbsSDt9EyWz05J5wfa7LOVW0YTLOXrqdLAWIw= -golang.org/x/tools v0.21.0/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= +golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ= +golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17 h1:wpZ8pe2x1Q3f2KyT5f8oP/fa9rHAKgFPr/HZdNuS+PQ= -google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17 h1:JpwMPBpFN3uKhdaekDpiNlImDdkUAyiJ6ez/uxGaUSo= -google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:0xJLfVdJqpAPl8tDg1ujOCGzx6LFLttXT5NhllGOXY4= -google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f h1:ultW7fxlIvee4HYrtnaRPon9HpEgFk5zYpmfMgtKB5I= -google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f/go.mod h1:L9KNLi232K1/xB6f7AlSX692koaRnKaWSR0stBki0Yc= -google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk= -google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98= -google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= -google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/genproto v0.0.0-20240930140551-af27646dc61f h1:mCJ6SGikSxVlt9scCayUl2dMq0msUgmBArqRY6umieI= +google.golang.org/genproto/googleapis/api v0.0.0-20241007155032-5fefd90f89a9 h1:T6rh4haD3GVYsgEfWExoCZA2o2FmbNyKpTuAxbEFPTg= +google.golang.org/genproto/googleapis/api v0.0.0-20241007155032-5fefd90f89a9/go.mod h1:wp2WsuBYj6j8wUdo3ToZsdxxixbvQNAHqVJrTgi5E5M= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241007155032-5fefd90f89a9 h1:QCqS/PdaHTSWGvupk2F/ehwHtGc0/GYkT+3GAcR1CCc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241007155032-5fefd90f89a9/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI= +google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E= +google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= +google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= +google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= @@ -453,5 +545,5 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gotest.tools/v3 v3.5.0 h1:Ljk6PdHdOhAb5aDMWXjDLMMhph+BpztA4v1QdqEW2eY= -gotest.tools/v3 v3.5.0/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= +gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= +gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= diff --git a/lifecycle/cli.go b/lifecycle/cli.go index bb8133a1e..b47731934 100644 --- a/lifecycle/cli.go +++ b/lifecycle/cli.go @@ -165,23 +165,13 @@ func create(c *cli.Context) (err error) { return } - sync := syncDefaultValueTrue(c) - createCmd := lifecycle.NewReleaseBundleCreateCommand().SetServerDetails(lcDetails).SetReleaseBundleName(c.Args().Get(0)). - SetReleaseBundleVersion(c.Args().Get(1)).SetSigningKeyName(c.String(cliutils.SigningKey)).SetSync(sync). + SetReleaseBundleVersion(c.Args().Get(1)).SetSigningKeyName(c.String(cliutils.SigningKey)).SetSync(c.Bool(cliutils.Sync)). SetReleaseBundleProject(cliutils.GetProject(c)).SetSpec(creationSpec). SetBuildsSpecPath(c.String(cliutils.Builds)).SetReleaseBundlesSpecPath(c.String(cliutils.ReleaseBundles)) return commands.Exec(createCmd) } -func syncDefaultValueTrue(c *cli.Context) bool { - sync := true - if c.IsSet(cliutils.Sync) { - sync = c.Bool(cliutils.Sync) - } - return sync -} - func getReleaseBundleCreationSpec(c *cli.Context) (*spec.SpecFiles, error) { if c.IsSet("spec") { return cliutils.GetSpec(c, true, false) diff --git a/main.go b/main.go index a487e8a37..dab9625c5 100644 --- a/main.go +++ b/main.go @@ -4,17 +4,9 @@ import ( "crypto/rand" "encoding/hex" "fmt" - "github.com/jfrog/jfrog-cli/general/ai" - "github.com/jfrog/jfrog-client-go/http/httpclient" - "github.com/jfrog/jfrog-client-go/utils/errorutils" - "os" - "runtime" - "sort" - "strings" - "github.com/agnivade/levenshtein" + artifactoryCLI "github.com/jfrog/jfrog-cli-artifactory/evidence/cli" corecommon "github.com/jfrog/jfrog-cli-core/v2/docs/common" - setupcore "github.com/jfrog/jfrog-cli-core/v2/general/envsetup" "github.com/jfrog/jfrog-cli-core/v2/plugins/components" coreconfig "github.com/jfrog/jfrog-cli-core/v2/utils/config" "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" @@ -28,13 +20,12 @@ import ( "github.com/jfrog/jfrog-cli/distribution" "github.com/jfrog/jfrog-cli/docs/common" aiDocs "github.com/jfrog/jfrog-cli/docs/general/ai" - "github.com/jfrog/jfrog-cli/docs/general/cisetup" loginDocs "github.com/jfrog/jfrog-cli/docs/general/login" + summaryDocs "github.com/jfrog/jfrog-cli/docs/general/summary" tokenDocs "github.com/jfrog/jfrog-cli/docs/general/token" - cisetupcommand "github.com/jfrog/jfrog-cli/general/cisetup" - "github.com/jfrog/jfrog-cli/general/envsetup" + "github.com/jfrog/jfrog-cli/general/ai" "github.com/jfrog/jfrog-cli/general/login" - "github.com/jfrog/jfrog-cli/general/project" + "github.com/jfrog/jfrog-cli/general/summary" "github.com/jfrog/jfrog-cli/general/token" "github.com/jfrog/jfrog-cli/lifecycle" "github.com/jfrog/jfrog-cli/missioncontrol" @@ -42,11 +33,17 @@ import ( "github.com/jfrog/jfrog-cli/plugins" "github.com/jfrog/jfrog-cli/plugins/utils" "github.com/jfrog/jfrog-cli/utils/cliutils" + "github.com/jfrog/jfrog-client-go/http/httpclient" clientutils "github.com/jfrog/jfrog-client-go/utils" + "github.com/jfrog/jfrog-client-go/utils/errorutils" "github.com/jfrog/jfrog-client-go/utils/io/fileutils" clientlog "github.com/jfrog/jfrog-client-go/utils/log" "github.com/urfave/cli" "golang.org/x/exp/slices" + "os" + "runtime" + "sort" + "strings" ) const commandHelpTemplate string = `{{.HelpName}}{{if .UsageText}} @@ -73,7 +70,7 @@ func main() { log.SetDefaultLogger() err := execMain() if cleanupErr := fileutils.CleanOldDirs(); cleanupErr != nil { - clientlog.Warn(cleanupErr) + clientlog.Warn("failed while attempting to cleanup old CLI temp directories:", cleanupErr) } coreutils.ExitOnErr(err) } @@ -254,31 +251,6 @@ func getCommands() ([]cli.Command, error) { Subcommands: config.GetCommands(), Category: commandNamespacesCategory, }, - { - Name: cliutils.CmdProject, - Hidden: true, - Usage: "Project commands.", - Subcommands: project.GetCommands(), - Category: otherCategory, - }, - { - Name: "ci-setup", - Hidden: true, - Usage: cisetup.GetDescription(), - HelpName: corecommon.CreateUsage("ci-setup", cisetup.GetDescription(), cisetup.Usage), - ArgsUsage: common.CreateEnvVars(), - BashComplete: corecommon.CreateBashCompletionFunc(), - Category: otherCategory, - Action: func(c *cli.Context) error { - return cisetupcommand.RunCiSetupCmd() - }, - }, - { - Name: "setup", - Hidden: true, - Flags: cliutils.GetCommandFlags(cliutils.Setup), - Action: SetupCmd, - }, { Name: "intro", Hidden: true, @@ -302,12 +274,10 @@ func getCommands() ([]cli.Command, error) { Action: login.LoginCmd, }, { - Hidden: true, Name: "how", Usage: aiDocs.GetDescription(), HelpName: corecommon.CreateUsage("how", aiDocs.GetDescription(), aiDocs.Usage), BashComplete: corecommon.CreateBashCompletionFunc(), - Category: otherCategory, Action: ai.HowCmd, }, { @@ -322,17 +292,30 @@ func getCommands() ([]cli.Command, error) { Category: otherCategory, Action: token.AccessTokenCreateCmd, }, + { + Name: "generate-summary-markdown", + Aliases: []string{"gsm"}, + Usage: summaryDocs.GetDescription(), + HelpName: corecommon.CreateUsage("gsm", summaryDocs.GetDescription(), summaryDocs.Usage), + Category: otherCategory, + Action: summary.FinalizeCommandSummaries, + }, } securityCmds, err := ConvertEmbeddedPlugin(securityCLI.GetJfrogCliSecurityApp()) if err != nil { return nil, err } + artifactoryCmds, err := ConvertEmbeddedPlugin(artifactoryCLI.GetJfrogCliArtifactoryApp()) + if err != nil { + return nil, err + } platformServicesCmds, err := ConvertEmbeddedPlugin(platformServicesCLI.GetPlatformServicesApp()) if err != nil { return nil, err } allCommands := append(slices.Clone(cliNameSpaces), securityCmds...) + allCommands = append(allCommands, artifactoryCmds...) allCommands = append(allCommands, platformServicesCmds...) allCommands = append(allCommands, utils.GetPlugins()...) allCommands = append(allCommands, buildtools.GetCommands()...) @@ -380,15 +363,6 @@ GLOBAL OPTIONS: ` } -func SetupCmd(c *cli.Context) error { - format := setupcore.Human - formatFlag := c.String("format") - if formatFlag == string(setupcore.Machine) { - format = setupcore.Machine - } - return envsetup.RunEnvSetupCmd(c, format) -} - func IntroCmd(_ *cli.Context) error { ci, err := clientutils.GetBoolEnvValue(coreutils.CI, false) if ci || err != nil { diff --git a/main_test.go b/main_test.go index fa593db57..f8715470e 100644 --- a/main_test.go +++ b/main_test.go @@ -150,7 +150,7 @@ func cleanTestsHomeEnv() { } func validateBuildInfo(buildInfo buildinfo.BuildInfo, t *testing.T, expectedDependencies int, expectedArtifacts int, moduleName string, moduleType buildinfo.ModuleType) { - if buildInfo.Modules == nil || len(buildInfo.Modules) == 0 { + if len(buildInfo.Modules) == 0 { assert.Fail(t, "build info was not generated correctly, no modules were created.") return } diff --git a/maven_test.go b/maven_test.go index e993ac8e7..5281f7385 100644 --- a/maven_test.go +++ b/maven_test.go @@ -54,6 +54,7 @@ func TestMavenBuildWithServerID(t *testing.T) { func TestMavenBuildWithNoProxy(t *testing.T) { initMavenTest(t, false) + // jfrog-ignore - not a real password setEnvCallBack := clientTestUtils.SetEnvWithCallbackAndAssert(t, buildUtils.HttpProxyEnvKey, "http://login:pass@proxy.mydomain:8888") defer setEnvCallBack() // Set noProxy to match all to skip http proxy configuration @@ -69,6 +70,7 @@ func TestMavenBuildWithNoProxy(t *testing.T) { func TestMavenBuildWithNoProxyHttps(t *testing.T) { initMavenTest(t, false) + // jfrog-ignore - not a real password setHttpsEnvCallBack := clientTestUtils.SetEnvWithCallbackAndAssert(t, buildUtils.HttpsProxyEnvKey, "https://logins:passw@proxys.mydomains:8889") defer setHttpsEnvCallBack() // Set noProxy to match all to skip https proxy configuration diff --git a/npm_test.go b/npm_test.go index 71f170421..8698fb0a0 100644 --- a/npm_test.go +++ b/npm_test.go @@ -84,6 +84,7 @@ func testNpm(t *testing.T, isLegacy bool) { assert.NoError(t, err) return } + log.Info("npm version:", npmVersion.GetVersion()) isNpm7 := isNpm7(npmVersion) // Temporarily change the cache folder to a temporary folder - to make sure the cache is clean and dependencies will be downloaded from Artifactory @@ -374,7 +375,7 @@ func validateNpmCommonPublish(t *testing.T, npmTestParams npmTestParams, isNpm7, } buildInfo := publishedBuildInfo.BuildInfo expectedArtifactName := tests.GetNpmArtifactName(isNpm7, isScoped) - if buildInfo.Modules == nil || len(buildInfo.Modules) == 0 { + if len(buildInfo.Modules) == 0 { // Case no module was created assert.Fail(t, "npm publish test failed", "params: \n%v \nexpected to have module with the following artifact: \n%v \nbut has no modules: \n%v", npmTestParams, expectedArtifactName, buildInfo) diff --git a/pip_test.go b/pip_test.go index 34267df70..c4199eded 100644 --- a/pip_test.go +++ b/pip_test.go @@ -166,7 +166,11 @@ func assertDependencyChecksums(t *testing.T, checksum buildinfo.Checksum) { } func createPipProject(t *testing.T, outFolder, projectName string) string { - projectSrc := filepath.Join(filepath.FromSlash(tests.GetTestResourcesPath()), "pip", projectName) + return createPypiProject(t, outFolder, projectName, "pip") +} + +func createPypiProject(t *testing.T, outFolder, projectName, projectSrcDir string) string { + projectSrc := filepath.Join(filepath.FromSlash(tests.GetTestResourcesPath()), projectSrcDir, projectName) projectTarget := filepath.Join(tests.Out, outFolder+"-"+projectName) err := fileutils.CreateDirIfNotExist(projectTarget) assert.NoError(t, err) @@ -176,7 +180,7 @@ func createPipProject(t *testing.T, outFolder, projectName string) string { assert.NoError(t, err) // Copy pip-config file. - configSrc := filepath.Join(filepath.FromSlash(tests.GetTestResourcesPath()), "pip", "pip.yaml") + configSrc := filepath.Join(filepath.FromSlash(tests.GetTestResourcesPath()), projectSrcDir, "pip.yaml") configTarget := filepath.Join(projectTarget, ".jfrog", "projects") _, err = tests.ReplaceTemplateVariables(configSrc, configTarget) assert.NoError(t, err) @@ -187,6 +191,80 @@ func initPipTest(t *testing.T) { if !*tests.TestPip { t.Skip("Skipping Pip test. To run Pip test add the '-test.pip=true' option.") } + require.True(t, isRepoExist(tests.PypiLocalRepo), "Pypi test local repository doesn't exist.") require.True(t, isRepoExist(tests.PypiRemoteRepo), "Pypi test remote repository doesn't exist.") require.True(t, isRepoExist(tests.PypiVirtualRepo), "Pypi test virtual repository doesn't exist.") } + +func TestTwine(t *testing.T) { + // Init pip. + initPipTest(t) + + // Populate cli config with 'default' server. + oldHomeDir, newHomeDir := prepareHomeDir(t) + defer func() { + clientTestUtils.SetEnvAndAssert(t, coreutils.HomeDir, oldHomeDir) + clientTestUtils.RemoveAllAndAssert(t, newHomeDir) + }() + + // Create test cases. + allTests := []struct { + name string + project string + outputFolder string + expectedModuleId string + args []string + expectedArtifacts int + }{ + {"twine", "pyproject", "twine", "jfrog-python-example:1.0", []string{}, 2}, + {"twine-with-module", "pyproject", "twine-with-module", "twine-with-module", []string{"--module=twine-with-module"}, 2}, + } + + // Run test cases. + for testNumber, test := range allTests { + t.Run(test.name, func(t *testing.T) { + cleanVirtualEnv, err := prepareVirtualEnv(t) + assert.NoError(t, err) + + buildNumber := strconv.Itoa(100 + testNumber) + test.args = append([]string{"twine", "upload", "dist/*", "--build-name=" + tests.PipBuildName, "--build-number=" + buildNumber}, test.args...) + testTwineCmd(t, createPypiProject(t, test.outputFolder, test.project, "twine"), buildNumber, test.expectedModuleId, test.expectedArtifacts, test.args) + + // cleanup + cleanVirtualEnv() + inttestutils.DeleteBuild(serverDetails.ArtifactoryUrl, tests.PipBuildName, artHttpDetails) + }) + } +} + +func testTwineCmd(t *testing.T, projectPath, buildNumber, expectedModuleId string, expectedArtifacts int, args []string) { + wd, err := os.Getwd() + assert.NoError(t, err, "Failed to get current dir") + chdirCallback := clientTestUtils.ChangeDirWithCallback(t, wd, projectPath) + defer chdirCallback() + + jfrogCli := coretests.NewJfrogCli(execMain, "jfrog", "") + err = jfrogCli.Exec(args...) + if err != nil { + assert.Fail(t, "Failed executing twine upload command", err.Error()) + return + } + + assert.NoError(t, artifactoryCli.Exec("bp", tests.PipBuildName, buildNumber)) + + publishedBuildInfo, found, err := tests.GetBuildInfo(serverDetails, tests.PipBuildName, buildNumber) + if err != nil { + assert.NoError(t, err) + return + } + if !found { + assert.True(t, found, "build info was expected to be found") + return + } + buildInfo := publishedBuildInfo.BuildInfo + require.Len(t, buildInfo.Modules, 1) + twineModule := buildInfo.Modules[0] + assert.Equal(t, buildinfo.Python, twineModule.Type) + assert.Len(t, twineModule.Artifacts, expectedArtifacts) + assert.Equal(t, expectedModuleId, twineModule.Id) +} diff --git a/pipelines/cli.go b/pipelines/cli.go index a053b9f98..347508672 100644 --- a/pipelines/cli.go +++ b/pipelines/cli.go @@ -1,7 +1,7 @@ package pipelines import ( - "fmt" + "errors" "github.com/jfrog/jfrog-cli-core/v2/common/commands" corecommon "github.com/jfrog/jfrog-cli-core/v2/docs/common" pipelines "github.com/jfrog/jfrog-cli-core/v2/pipelines/commands" @@ -87,7 +87,7 @@ func createPipelinesDetailsByFlags(c *cli.Context) (*coreConfig.ServerDetails, e return nil, err } if plDetails.PipelinesUrl == "" { - return nil, fmt.Errorf("the --pipelines-url option is mandatory") + return nil, errors.New("no JFrog Pipelines URL specified as part of the server configuration") } return plDetails, nil } diff --git a/plugins/commands/install.go b/plugins/commands/install.go index a45f3138d..7b5a55f94 100644 --- a/plugins/commands/install.go +++ b/plugins/commands/install.go @@ -170,6 +170,7 @@ func downloadPlugin(pluginsDir, pluginName, downloadUrl string, httpDetails http return } if progressMgr != nil { + progressMgr.SetHeadlineMsg("Downloading") progressMgr.InitProgressReaders() progressMgr.IncGeneralProgressTotalBy(1) defer func() { @@ -268,6 +269,6 @@ func downloadFromArtifactory(downloadDetails *httpclient.DownloadFileDetails, ht if err != nil { return } - log.Info("Downloading: " + downloadDetails.FileName) + log.Info("Downloading:", downloadDetails.FileName) return client.DownloadFileWithProgress(downloadDetails, "", httpDetails, false, false, progressMgr) } diff --git a/testdata/docker/artifactory/Dockerfile b/testdata/docker/artifactory/Dockerfile index c47054915..90f394d54 100644 --- a/testdata/docker/artifactory/Dockerfile +++ b/testdata/docker/artifactory/Dockerfile @@ -1,10 +1,14 @@ # Wrap jfrog-testing-infra in a contaner for the docker tests. # We run a container with Artifactory since docker tests may need to spin up a contaner to build images inside. +FROM golang:bookworm -FROM golang:buster RUN go install github.com/jfrog/jfrog-testing-infra/local-rt-setup@latest + ENV JFROG_HOME=/jfrog_home WORKDIR /jfrog_home + EXPOSE 8082 EXPOSE 8081 -CMD ["sh","-c","local-rt-setup; sleep infinity"] \ No newline at end of file + +# Temporarily use version 7.84.17 due to an issue with the automatic token generation mechanism in 7.90 +CMD ["sh","-c","local-rt-setup --rt-version 7.84.17; sleep infinity"] diff --git a/testdata/pypi_local_repository_config.json b/testdata/pypi_local_repository_config.json new file mode 100644 index 000000000..ab4952816 --- /dev/null +++ b/testdata/pypi_local_repository_config.json @@ -0,0 +1,5 @@ +{ + "key": "${PYPI_LOCAL_REPO}", + "rclass": "local", + "packageType": "pypi" +} diff --git a/testdata/pypi_virtual_repository_config.json b/testdata/pypi_virtual_repository_config.json index 574fad282..0c8a751d0 100644 --- a/testdata/pypi_virtual_repository_config.json +++ b/testdata/pypi_virtual_repository_config.json @@ -2,5 +2,6 @@ "key": "${PYPI_VIRTUAL_REPO}", "rclass": "virtual", "packageType": "pypi", - "repositories": ["${PYPI_REMOTE_REPO}"] + "repositories": ["${PYPI_REMOTE_REPO}", "${PYPI_LOCAL_REPO}"], + "defaultDeploymentRepo": "${PYPI_LOCAL_REPO}" } diff --git a/testdata/twine/pip.yaml b/testdata/twine/pip.yaml new file mode 100644 index 000000000..226862f66 --- /dev/null +++ b/testdata/twine/pip.yaml @@ -0,0 +1,8 @@ +version: 1 +type: pip +resolver: + repo: ${PYPI_VIRTUAL_REPO} + serverId: default +deployer: + repo: ${PYPI_VIRTUAL_REPO} + serverId: default \ No newline at end of file diff --git a/testdata/twine/pyproject/dist/jfrog_python_example-1.0-py3-none-any.whl b/testdata/twine/pyproject/dist/jfrog_python_example-1.0-py3-none-any.whl new file mode 100644 index 000000000..be3eea646 Binary files /dev/null and b/testdata/twine/pyproject/dist/jfrog_python_example-1.0-py3-none-any.whl differ diff --git a/testdata/twine/pyproject/dist/jfrog_python_example-1.0.tar.gz b/testdata/twine/pyproject/dist/jfrog_python_example-1.0.tar.gz new file mode 100644 index 000000000..657cf23e5 Binary files /dev/null and b/testdata/twine/pyproject/dist/jfrog_python_example-1.0.tar.gz differ diff --git a/testdata/twine/pyproject/pyproject.toml b/testdata/twine/pyproject/pyproject.toml new file mode 100644 index 000000000..792971df4 --- /dev/null +++ b/testdata/twine/pyproject/pyproject.toml @@ -0,0 +1,15 @@ +[build-system] +requires = ["setuptools>=42", "wheel"] +build-backend = "setuptools.build_meta" + +[project] +name = "jfrog-python-example" +version = "1.0" +description = "Project example for building Python project with JFrog products" +authors = [ + { name="JFrog", email="jfrog@jfrog.com" } +] +dependencies = [ + "PyYAML>3.11", + "nltk" +] diff --git a/transfer_test.go b/transfer_test.go index 592dc9ea3..2fe91bd22 100644 --- a/transfer_test.go +++ b/transfer_test.go @@ -369,6 +369,7 @@ func generateTestRepoSnapshotFile(t *testing.T, repoKey, repoSnapshotFilePath st func addChildWithFiles(t *testing.T, parent *reposnapshot.Node, dirName string, explored, checkCompleted bool, filesCount int) *reposnapshot.Node { childNode := reposnapshot.CreateNewNode(dirName, nil) for i := 0; i < filesCount; i++ { + //#nosec G115 assert.NoError(t, childNode.IncrementFilesCount(uint64(i))) } diff --git a/utils/cliutils/cli_consts.go b/utils/cliutils/cli_consts.go index a3ccc577f..3488905f2 100644 --- a/utils/cliutils/cli_consts.go +++ b/utils/cliutils/cli_consts.go @@ -4,7 +4,7 @@ import "time" const ( // General CLI constants - CliVersion = "2.56.1" + CliVersion = "2.71.3" ClientAgent = "jfrog-cli-go" // CLI base commands constants: diff --git a/utils/cliutils/commandsflags.go b/utils/cliutils/commandsflags.go index ca818004f..446f84eff 100644 --- a/utils/cliutils/commandsflags.go +++ b/utils/cliutils/commandsflags.go @@ -37,6 +37,8 @@ const ( GitLfsClean = "git-lfs-clean" Mvn = "mvn" MvnConfig = "mvn-config" + CocoapodsConfig = "cocoapods-config" + SwiftConfig = "swift-config" Gradle = "gradle" GradleConfig = "gradle-config" DockerPromote = "docker-promote" @@ -66,6 +68,7 @@ const ( PipConfig = "pip-config" TerraformConfig = "terraform-config" Terraform = "terraform" + Twine = "twine" Pipenv = "pipenv" PipenvConfig = "pipenv-config" PipenvInstall = "pipenv-install" @@ -531,9 +534,6 @@ const ( Completion = "completion" Install = "install" - // Setup flags - setupFormat = "setup-format" - // *** TransferFiles Commands' flags *** transferFilesPrefix = "transfer-files-" Filestore = "filestore" @@ -730,7 +730,7 @@ var flagsMap = map[string]cli.Flag{ }, archiveEntries: cli.StringFlag{ Name: archiveEntries, - Usage: "[Optional] If specified, only archive artifacts containing entries matching this pattern are matched. You can use wildcards to specify multiple artifacts.` `", + Usage: "[Optional] This option is no longer supported since version 7.90.5 of Artifactory. If specified, only archive artifacts containing entries matching this pattern are matched. You can use wildcards to specify multiple artifacts.` `", }, detailedSummary: cli.BoolFlag{ Name: detailedSummary, @@ -1247,7 +1247,7 @@ var flagsMap = map[string]cli.Flag{ // Distribution's commands Flags distUrl: cli.StringFlag{ Name: url, - Usage: "[Optional] JFrog Distribution URL.` `", + Usage: "[Optional] JFrog Distribution URL. (example: https://acme.jfrog.io/distribution)` `", }, rbDryRun: cli.BoolFlag{ Name: dryRun, @@ -1322,7 +1322,7 @@ var flagsMap = map[string]cli.Flag{ // Xray's commands Flags xrUrl: cli.StringFlag{ Name: url, - Usage: "[Optional] JFrog Xray URL.` `", + Usage: "[Optional] JFrog Xray URL. (example: https://acme.jfrog.io/xray)` `", }, xrayScan: cli.StringFlag{ Name: xrayScan, @@ -1485,7 +1485,7 @@ var flagsMap = map[string]cli.Flag{ // Mission Control's commands Flags mcUrl: cli.StringFlag{ Name: url, - Usage: "[Optional] JFrog Mission Control URL.` `", + Usage: "[Optional] JFrog Mission Control URL. (example: https://acme.jfrog.io/mc)` `", }, mcAccessToken: cli.StringFlag{ Name: accessToken, @@ -1554,10 +1554,6 @@ var flagsMap = map[string]cli.Flag{ Name: Install, Usage: "[Default: false] Set to true to install the completion script instead of printing it to the standard output.` `", }, - setupFormat: cli.StringFlag{ - Name: "format", - Hidden: true, - }, CreateRepo: cli.BoolFlag{ Name: CreateRepo, Usage: "[Default: false] Set to true to create the repository on the edge if it does not exist.` `", @@ -1634,9 +1630,9 @@ var flagsMap = map[string]cli.Flag{ Name: PreChecks, Usage: "[Default: false] Set to true to run pre-transfer checks.` `", }, - lcSync: cli.BoolFlag{ + lcSync: cli.BoolTFlag{ Name: Sync, - Usage: "[Default: false] Set to true to run synchronously.` `", + Usage: "[Default: true] Set to false to run asynchronously.` `", }, lcProject: cli.StringFlag{ Name: Project, @@ -1821,6 +1817,12 @@ var commandFlags = map[string][]string{ url, user, password, accessToken, sshPassphrase, sshKeyPath, serverId, refs, glcRepo, glcDryRun, glcQuiet, InsecureTls, retries, retryWaitTime, }, + CocoapodsConfig: { + global, serverIdResolve, repoResolve, + }, + SwiftConfig: { + global, serverIdResolve, repoResolve, + }, MvnConfig: { global, serverIdResolve, serverIdDeploy, repoResolveReleases, repoResolveSnapshots, repoDeployReleases, repoDeploySnapshots, includePatterns, excludePatterns, UseWrapper, }, @@ -1836,7 +1838,7 @@ var commandFlags = map[string][]string{ }, Docker: { buildName, buildNumber, module, Project, - serverId, skipLogin, threads, detailedSummary, watches, repoPath, licenses, xrOutput, fail, ExtendedTable, BypassArchiveLimits, MinSeverity, FixableOnly, + serverId, skipLogin, threads, detailedSummary, watches, repoPath, licenses, xrOutput, fail, ExtendedTable, BypassArchiveLimits, MinSeverity, FixableOnly, vuln, }, DockerPush: { buildName, buildNumber, module, Project, @@ -1904,6 +1906,9 @@ var commandFlags = map[string][]string{ namespace, provider, tag, exclusions, buildName, buildNumber, module, Project, }, + Twine: { + buildName, buildNumber, module, Project, + }, TransferConfig: { Force, Verbose, IncludeRepos, ExcludeRepos, SourceWorkingDir, TargetWorkingDir, PreChecks, }, @@ -1918,13 +1923,13 @@ var commandFlags = map[string][]string{ serverId, }, PipConfig: { - global, serverIdResolve, repoResolve, + global, serverIdResolve, serverIdDeploy, repoResolve, repoDeploy, }, PipInstall: { buildName, buildNumber, module, Project, }, PipenvConfig: { - global, serverIdResolve, repoResolve, + global, serverIdResolve, serverIdDeploy, repoResolve, repoDeploy, }, PipenvInstall: { buildName, buildNumber, module, Project, @@ -2061,9 +2066,6 @@ var commandFlags = map[string][]string{ Install, }, // CLI base commands - Setup: { - setupFormat, - }, Intro: {}, // Pipelines commands Status: { diff --git a/utils/cliutils/persistence.go b/utils/cliutils/persistence.go new file mode 100644 index 000000000..ef7747c97 --- /dev/null +++ b/utils/cliutils/persistence.go @@ -0,0 +1,136 @@ +package cliutils + +import ( + "encoding/json" + "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" + "github.com/jfrog/jfrog-client-go/utils/errorutils" + "github.com/jfrog/jfrog-client-go/utils/io/fileutils" + "os" + "path/filepath" + gosync "sync" +) + +const persistenceFileName = "persistence.json" + +// PersistenceFile holds various indicators that need to be persisted between CLI runs +type PersistenceFile struct { + LatestCliVersionCheckTime *int64 `json:"latestCliVersionCheckTime,omitempty"` + LatestAiTermsRevision *int `json:"latestAiTermsRevision,omitempty"` +} + +var ( + persistenceFilePath string + fileLock gosync.Mutex +) + +// getPersistenceFilePath ensures that the persistence file path is initialized +func getPersistenceFilePath() error { + if persistenceFilePath == "" { + homeDir, err := coreutils.GetJfrogHomeDir() + if err != nil { + return errorutils.CheckErrorf("failed to get JFrog home directory: " + err.Error()) + } + persistenceFilePath = filepath.Join(homeDir, persistenceFileName) + } + return nil +} + +// setCliLatestVersionCheckTime updates the latest version check time in the persistence file +func setCliLatestVersionCheckTime(timestamp int64) error { + fileLock.Lock() + defer fileLock.Unlock() + + info, err := getPersistenceInfo() + if err != nil { + return err + } + + info.LatestCliVersionCheckTime = ×tamp + return setPersistenceInfo(info) +} + +// getLatestCliVersionCheckTime retrieves the latest version check time from the persistence file +func getLatestCliVersionCheckTime() (*int64, error) { + fileLock.Lock() + defer fileLock.Unlock() + + info, err := getPersistenceInfo() + if err != nil { + return nil, err + } + + return info.LatestCliVersionCheckTime, nil +} + +// SetLatestAiTermsRevision updates the AI terms version in the persistence file +func SetLatestAiTermsRevision(version int) error { + fileLock.Lock() + defer fileLock.Unlock() + + info, err := getPersistenceInfo() + if err != nil { + return err + } + + info.LatestAiTermsRevision = &version + return setPersistenceInfo(info) +} + +// GetLatestAiTermsRevision retrieves the AI terms version from the persistence file +func GetLatestAiTermsRevision() (*int, error) { + fileLock.Lock() + defer fileLock.Unlock() + + info, err := getPersistenceInfo() + if err != nil { + return nil, err + } + + return info.LatestAiTermsRevision, nil +} + +// getPersistenceInfo reads the persistence file, creates it if it doesn't exist, and returns the persisted info +func getPersistenceInfo() (*PersistenceFile, error) { + if err := getPersistenceFilePath(); err != nil { + return nil, err + } + if exists, err := fileutils.IsFileExists(persistenceFilePath, false); err != nil || !exists { + if err != nil { + return nil, err + } + // Create an empty persistence file if it doesn't exist + pFile := &PersistenceFile{} + if err = setPersistenceInfo(pFile); err != nil { + return nil, errorutils.CheckErrorf("failed while attempting to initialize persistence file: " + err.Error()) + } + return pFile, nil + } + + data, err := os.ReadFile(persistenceFilePath) + if err != nil { + return nil, errorutils.CheckErrorf("failed while attempting to read persistence file: " + err.Error()) + } + + var info PersistenceFile + if err = json.Unmarshal(data, &info); err != nil { + return nil, errorutils.CheckErrorf("failed while attempting to parse persistence file: " + err.Error()) + } + + return &info, nil +} + +// setPersistenceInfo writes the given info to the persistence file +func setPersistenceInfo(info *PersistenceFile) error { + if err := getPersistenceFilePath(); err != nil { + return err + } + data, err := json.MarshalIndent(info, "", " ") + if err != nil { + return errorutils.CheckErrorf("failed while attempting to create persistence file: " + err.Error()) + } + + if err = os.WriteFile(persistenceFilePath, data, 0644); err != nil { + return errorutils.CheckErrorf("failed while attempting to write persistence file: " + err.Error()) + } + return nil +} diff --git a/utils/cliutils/persistence_test.go b/utils/cliutils/persistence_test.go new file mode 100644 index 000000000..cdf5826e7 --- /dev/null +++ b/utils/cliutils/persistence_test.go @@ -0,0 +1,65 @@ +package cliutils + +import ( + "os" + "path/filepath" + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +// TestSetAndGetLatestVersionCheckTime tests setting and getting the LatestCliVersionCheckTime +func TestSetAndGetLatestVersionCheckTime(t *testing.T) { + // Setup temporary directory + persistenceFilePath = filepath.Join(t.TempDir(), persistenceFileName) + + // Set the timestamp + timestamp := time.Now().UnixMilli() + err := setCliLatestVersionCheckTime(timestamp) + assert.NoError(t, err, "Failed to set LatestCliVersionCheckTime") + + // Get the timestamp + storedTimestamp, err := getLatestCliVersionCheckTime() + assert.NoError(t, err, "Failed to get LatestCliVersionCheckTime") + + // Assert equality + assert.Equal(t, timestamp, *storedTimestamp, "Stored timestamp does not match the set timestamp") +} + +// TestSetAndGetAiTermsVersion tests setting and getting the LatestAiTermsRevision +func TestSetAndGetAiTermsVersion(t *testing.T) { + // Setup temporary directory + persistenceFilePath = filepath.Join(t.TempDir(), persistenceFileName) + + // Set the AI terms version + version := 42 + err := SetLatestAiTermsRevision(version) + assert.NoError(t, err, "Failed to set LatestAiTermsRevision") + + // Get the AI terms version + storedVersion, err := GetLatestAiTermsRevision() + assert.NoError(t, err, "Failed to get LatestAiTermsRevision") + + // Assert equality + assert.Equal(t, version, *storedVersion, "Stored AI terms version does not match the set version") +} + +// TestPersistenceFileCreation tests if the persistence file is created when it doesn't exist +func TestPersistenceFileCreation(t *testing.T) { + // Setup temporary directory + persistenceFilePath = filepath.Join(t.TempDir(), persistenceFileName) + + // Ensure the persistence file doesn't exist + _, err := os.Stat(persistenceFilePath) + assert.ErrorIs(t, err, os.ErrNotExist, "Expected error to be os.ErrNotExist") + + // Trigger file creation by setting version check time + timestamp := time.Now().UnixMilli() + err = setCliLatestVersionCheckTime(timestamp) + assert.NoError(t, err, "Failed to set LatestCliVersionCheckTime") + + // Verify the persistence file was created + _, err = os.Stat(persistenceFilePath) + assert.False(t, os.IsNotExist(err), "Expected file to exist, but it does not") +} diff --git a/utils/cliutils/utils.go b/utils/cliutils/utils.go index 47fcd3ad1..17781fe50 100644 --- a/utils/cliutils/utils.go +++ b/utils/cliutils/utils.go @@ -4,10 +4,10 @@ import ( "encoding/json" "errors" "fmt" + "github.com/jfrog/jfrog-cli-core/v2/artifactory/utils/commandsummary" "io" "net/http" "os" - "path" "path/filepath" "strconv" "strings" @@ -125,7 +125,7 @@ func PrintBriefSummaryReport(success, failed int, failNoOp bool, originalErr err func PrintDeploymentView(reader *content.ContentReader) error { tree := artifactoryUtils.NewFileTree() for transferDetails := new(clientutils.FileTransferDetails); reader.NextRecord(transferDetails) == nil; transferDetails = new(clientutils.FileTransferDetails) { - tree.AddFile(transferDetails.TargetPath) + tree.AddFile(transferDetails.TargetPath, "") } if err := reader.GetError(); err != nil { return err @@ -635,7 +635,7 @@ func CreateArtifactoryDetailsByFlags(c *cli.Context) (*coreConfig.ServerDetails, return nil, err } if artDetails.ArtifactoryUrl == "" { - return nil, errors.New("the --url option is mandatory") + return nil, errors.New("no JFrog Artifactory URL specified, either via the --url flag or as part of the server configuration") } return artDetails, nil } @@ -690,25 +690,31 @@ func CheckNewCliVersionAvailable(currentVersion string) (warningMessage string, return } +func GetDetailedSummary(c *cli.Context) bool { + return c.Bool("detailed-summary") || commandsummary.ShouldRecordSummary() +} + +// Check if the latest CLI version should be checked via the GitHub API to let the user know if a newer version is available. +// To avoid checking this for every run, we check only if the last check was more than 6 hours ago. +// Also, if the user set the JFROG_CLI_AVOID_NEW_VERSION_WARNING environment variable to true, we won't check. func shouldCheckLatestCliVersion() (shouldCheck bool, err error) { if strings.ToLower(os.Getenv(JfrogCliAvoidNewVersionWarning)) == "true" { return } - homeDir, err := coreutils.GetJfrogHomeDir() + latestVersionCheckTime, err := getLatestCliVersionCheckTime() if err != nil { return } - indicatorFile := path.Join(homeDir, "Latest_Cli_Version_Check_Indicator") - fileInfo, err := os.Stat(indicatorFile) - if err != nil && !os.IsNotExist(err) { - err = fmt.Errorf("couldn't get indicator file %s info: %s", indicatorFile, err.Error()) + timeNow := time.Now().UnixMilli() + if latestVersionCheckTime != nil && + (timeNow-*latestVersionCheckTime) < LatestCliVersionCheckInterval.Milliseconds() { + // Timestamp file exists and updated less than 6 hours ago, therefor no need to check version again return } - if err == nil && (time.Now().UnixMilli()-fileInfo.ModTime().UnixMilli()) < LatestCliVersionCheckInterval.Milliseconds() { - // Timestamp file exists and updated less than 6 hours ago, therefor no need to check version again + if err = setCliLatestVersionCheckTime(timeNow); err != nil { return } - return true, os.WriteFile(indicatorFile, []byte{}, 0666) + return true, nil } func getLatestCliVersionFromGithubAPI() (githubVersionInfo githubResponse, err error) { diff --git a/utils/cliutils/utils_test.go b/utils/cliutils/utils_test.go index 0d257bb5f..f9bf32b2e 100644 --- a/utils/cliutils/utils_test.go +++ b/utils/cliutils/utils_test.go @@ -9,6 +9,7 @@ import ( "path/filepath" "strings" "testing" + "time" coretests "github.com/jfrog/jfrog-cli-core/v2/utils/tests" "github.com/jfrog/jfrog-cli/utils/tests" @@ -114,9 +115,7 @@ func testCheckNewCliVersionAvailable(t *testing.T, version string, shouldWarn bo } func TestShouldCheckLatestCliVersion(t *testing.T) { - // Create temp JFROG_HOME - cleanUpTempEnv := configtests.CreateTempEnv(t, false) - defer cleanUpTempEnv() + persistenceFilePath = filepath.Join(t.TempDir(), persistenceFileName) // Validate that avoiding the version check using an environment variable is working setEnvCallback := clientTestUtils.SetEnvWithCallbackAndAssert(t, JfrogCliAvoidNewVersionWarning, "true") @@ -134,4 +133,10 @@ func TestShouldCheckLatestCliVersion(t *testing.T) { shouldCheck, err = shouldCheckLatestCliVersion() assert.NoError(t, err) assert.False(t, shouldCheck) + + assert.NoError(t, setCliLatestVersionCheckTime(time.Now().UnixMilli()-LatestCliVersionCheckInterval.Milliseconds())) + // Third run, more than 6 hours between runs, so should return true + shouldCheck, err = shouldCheckLatestCliVersion() + assert.NoError(t, err) + assert.True(t, shouldCheck) } diff --git a/utils/tests/consts.go b/utils/tests/consts.go index de4152d01..2efa5b889 100644 --- a/utils/tests/consts.go +++ b/utils/tests/consts.go @@ -1,11 +1,12 @@ package tests import ( + "path/filepath" + "github.com/jfrog/jfrog-cli-core/v2/artifactory/utils" "github.com/jfrog/jfrog-client-go/artifactory/services" servicesUtils "github.com/jfrog/jfrog-client-go/artifactory/services/utils" clientUtils "github.com/jfrog/jfrog-client-go/utils" - "path/filepath" ) const ( @@ -94,6 +95,7 @@ const ( PipenvVirtualRepositoryConfig = "pipenv_virtual_repository_config.json" ProdRepo1RepositoryConfig = "prod_repo1_repository_config.json" ProdRepo2RepositoryConfig = "prod_repo2_repository_config.json" + PypiLocalRepositoryConfig = "pypi_local_repository_config.json" PypiRemoteRepositoryConfig = "pypi_remote_repository_config.json" PypiVirtualRepositoryConfig = "pypi_virtual_repository_config.json" ReplicationTempCreate = "replication_push_create.json" @@ -182,6 +184,7 @@ var ( NpmRemoteRepo = "cli-npm-remote" NugetRemoteRepo = "cli-nuget-remote" YarnRemoteRepo = "cli-yarn-remote" + PypiLocalRepo = "cli-pypi-local" PypiRemoteRepo = "cli-pypi-remote" PypiVirtualRepo = "cli-pypi-virtual" PipenvRemoteRepo = "cli-pipenv-pypi-remote" @@ -1795,12 +1798,13 @@ func GetGradleDeployedArtifacts() []string { } func GetNpmDeployedScopedArtifacts(isNpm7 bool) []string { - path := NpmRepo + "/@jscope/jfrog-cli-tests/-/" + path := NpmRepo + "/@jscope/jfrog-cli-tests/-/@jscope/" path += GetNpmArtifactName(isNpm7, true) return []string{ path, } } + func GetNpmDeployedArtifacts(isNpm7 bool) []string { path := NpmRepo + "/jfrog-cli-tests/-/" path += GetNpmArtifactName(isNpm7, false) diff --git a/utils/tests/container.go b/utils/tests/container.go index 05024b0f1..b94f3d682 100644 --- a/utils/tests/container.go +++ b/utils/tests/container.go @@ -3,6 +3,8 @@ package tests import ( "context" "fmt" + "github.com/docker/docker/api/types/container" + "github.com/docker/docker/api/types/mount" "github.com/jfrog/jfrog-client-go/utils/log" "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/wait" @@ -51,7 +53,7 @@ func NewContainerRequest() *ContainerRequest { // FromDockerfile represents the parameters needed to build an image from a Dockerfile // rather than using a pre-built image. -// This setter cannot be used with 'SetImage' to run a container.. +// This setter cannot be used with 'SetImage' to run a container. // // context - The path to the context of the docker build // file - The path from the context to the Dockerfile for the image, defaults to "Dockerfile" @@ -100,10 +102,13 @@ func (c *ContainerRequest) Remove() *ContainerRequest { } // Mounts the 'hostPath' working directory from localhost into the container. -// source - it is the path where the mount should be mounted within the localhost -// Target - It's the path where the mount should be mounted within the container -func (c *ContainerRequest) Mount(hostPath, target string, readOnly bool) *ContainerRequest { - c.request.Mounts = append(c.request.Mounts, testcontainers.ContainerMount{Source: testcontainers.GenericBindMountSource{HostPath: hostPath}, Target: testcontainers.ContainerMountTarget(target), ReadOnly: readOnly}) +func (c *ContainerRequest) Mount(mounts ...mount.Mount) *ContainerRequest { + c.request.HostConfigModifier = func(cfg *container.HostConfig) { + if cfg.Mounts == nil { + cfg.Mounts = make([]mount.Mount, 0) + } + cfg.Mounts = append(cfg.Mounts, mounts...) + } return c } diff --git a/utils/tests/utils.go b/utils/tests/utils.go index e1e6b6396..91f3cdd6b 100644 --- a/utils/tests/utils.go +++ b/utils/tests/utils.go @@ -255,6 +255,7 @@ var reposConfigMap = map[*string]string{ &NpmRemoteRepo: NpmRemoteRepositoryConfig, &NugetRemoteRepo: NugetRemoteRepositoryConfig, &YarnRemoteRepo: YarnRemoteRepositoryConfig, + &PypiLocalRepo: PypiLocalRepositoryConfig, &PypiRemoteRepo: PypiRemoteRepositoryConfig, &PypiVirtualRepo: PypiVirtualRepositoryConfig, &PipenvRemoteRepo: PipenvRemoteRepositoryConfig, @@ -316,7 +317,7 @@ func GetNonVirtualRepositories() map[*string]string { TestMaven: {&MvnRepo1, &MvnRepo2, &MvnRemoteRepo}, TestNpm: {&NpmRepo, &NpmRemoteRepo}, TestNuget: {&NugetRemoteRepo}, - TestPip: {&PypiRemoteRepo}, + TestPip: {&PypiLocalRepo, &PypiRemoteRepo}, TestPipenv: {&PipenvRemoteRepo}, TestPlugins: {&RtRepo1}, TestXray: {&NpmRemoteRepo, &NugetRemoteRepo, &YarnRemoteRepo, &GradleRemoteRepo, &MvnRemoteRepo, &GoRepo, &GoRemoteRepo, &PypiRemoteRepo}, @@ -421,6 +422,7 @@ func getSubstitutionMap() map[string]string { "${PASSWORD}": *JfrogPassword, "${RT_CREDENTIALS_BASIC_AUTH}": base64.StdEncoding.EncodeToString([]byte(*JfrogUser + ":" + *JfrogPassword)), "${ACCESS_TOKEN}": *JfrogAccessToken, + "${PYPI_LOCAL_REPO}": PypiLocalRepo, "${PYPI_REMOTE_REPO}": PypiRemoteRepo, "${PYPI_VIRTUAL_REPO}": PypiVirtualRepo, "${PIPENV_REMOTE_REPO}": PipenvRemoteRepo, @@ -479,6 +481,7 @@ func AddTimestampToGlobalVars() { NpmRemoteRepo += uniqueSuffix NugetRemoteRepo += uniqueSuffix YarnRemoteRepo += uniqueSuffix + PypiLocalRepo += uniqueSuffix PypiRemoteRepo += uniqueSuffix PypiVirtualRepo += uniqueSuffix PipenvRemoteRepo += uniqueSuffix