diff --git a/.dockerignore b/.dockerignore index 9414382..b4a86d3 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1 +1,4 @@ Dockerfile +target +.git +.github diff --git a/.github/marathon-cloud.rb.liquid b/.github/marathon-cloud.rb.liquid new file mode 100644 index 0000000..6f7b6cb --- /dev/null +++ b/.github/marathon-cloud.rb.liquid @@ -0,0 +1,109 @@ +# typed: false +# frozen_string_literal: true + +class MarathonCloud < Formula + desc "Command-line interface for Marathon Cloud" + homepage "https://github.com/MarathonLabs/marathon-cloud-cli" + version "{{ version }}" + license "MIT" + + on_macos do + url "{{ darwin.url }}" + sha256 "{{ darwin.sha256 }}" + + def install + bin.install "marathon-cloud" + generate_completions_from_executable(bin/"marathon-cloud", "completions", base_name: "marathon-cloud") + man1.install Dir["*.1"] + end + + def caveats; <<~EOS + ⚠️ To have superior experience, enable autocompletion on Brew. ⚠️ + You need to enable autocompletion just once for Brew. If it is already enabled, you can skip this part. + ▪ For Zsh users: + Follow https://docs.brew.sh/Shell-Completion#configuring-completions-in-zsh + ▪ For Bash users: + 1) `brew install bash-completion` and follow the printed \"Caveats\" section. Example \"Caveats\" instructions: + Add the following line to your ~/.bash_profile: + # !! Note that paths may differ depending on your installation, so you should follow the Caveats section on your system. + [[ -r \"/Users/anton/.brew/etc/profile.d/bash_completion.sh\" ]] && . \"/home/anton/.brew/etc/profile.d/bash_completion.sh\" + 2) `source ~/.bash_profile` + EOS + end + end + + on_linux do + if Hardware::CPU.arm? && Hardware::CPU.is_64_bit? + url "{{ linux.arm64.url }}" + sha256 "{{ linux.arm64.sha256 }}" + + def install + bin.install "marathon-cloud" + generate_completions_from_executable(bin/"marathon-cloud", "completions", base_name: "marathon-cloud") + man1.install Dir["*.1"] + end + + def caveats; <<~EOS + ⚠️ To have superior experience, enable autocompletion on Brew. ⚠️ + You need to enable autocompletion just once for Brew. If it is already enabled, you can skip this part. + ▪ For Zsh users: + Follow https://docs.brew.sh/Shell-Completion#configuring-completions-in-zsh + ▪ For Bash users: + 1) `brew install bash-completion` and follow the printed \"Caveats\" section. Example \"Caveats\" instructions: + Add the following line to your ~/.bash_profile: + # !! Note that paths may differ depending on your installation, so you should follow the Caveats section on your system. + [[ -r \"/home/ubuntu/.linuxbrew/etc/profile.d/bash_completion.sh\" ]] && . \"/home/ubuntu/.linuxbrew/etc/profile.d/bash_completion.sh\" + 2) `source ~/.bash_profile` + EOS + end + end + if Hardware::CPU.intel? + url "{{ linux.amd64.url }}" + sha256 "{{ linux.amd64.sha256 }}" + + def install + bin.install "marathon-cloud" + generate_completions_from_executable(bin/"marathon-cloud", "completions", base_name: "marathon-cloud") + man1.install Dir["*.1"] + end + + def caveats; <<~EOS + ⚠️ To have superior experience, enable autocompletion on Brew. ⚠️ + You need to enable autocompletion just once for Brew. If it is already enabled, you can skip this part. + ▪ For Zsh users: + Follow https://docs.brew.sh/Shell-Completion#configuring-completions-in-zsh + ▪ For Bash users: + 1) `brew install bash-completion` and follow the printed \"Caveats\" section. Example \"Caveats\" instructions: + Add the following line to your ~/.bash_profile: + # !! Note that paths may differ depending on your installation, so you should follow the Caveats section on your system. + [[ -r \"/home/ubuntu/.linuxbrew/etc/profile.d/bash_completion.sh\" ]] && . \"/home/ubuntu/.linuxbrew/etc/profile.d/bash_completion.sh\" + 2) `source ~/.bash_profile` + EOS + end + end + if Hardware::CPU.arm? && !Hardware::CPU.is_64_bit? + url "{{ linux.arm.url }}" + sha256 "{{ linux.arm.sha256 }}" + + def install + bin.install "marathon-cloud" + generate_completions_from_executable(bin/"marathon-cloud", "completions", base_name: "marathon-cloud") + man1.install Dir["*.1"] + end + + def caveats; <<~EOS + ⚠️ To have superior experience, enable autocompletion on Brew. ⚠️ + You need to enable autocompletion just once for Brew. If it is already enabled, you can skip this part. + ▪ For Zsh users: + Follow https://docs.brew.sh/Shell-Completion#configuring-completions-in-zsh + ▪ For Bash users: + 1) `brew install bash-completion` and follow the printed \"Caveats\" section. Example \"Caveats\" instructions: + Add the following line to your ~/.bash_profile: + # !! Note that paths may differ depending on your installation, so you should follow the Caveats section on your system. + [[ -r \"/home/ubuntu/.linuxbrew/etc/profile.d/bash_completion.sh\" ]] && . \"/home/ubuntu/.linuxbrew/etc/profile.d/bash_completion.sh\" + 2) `source ~/.bash_profile` + EOS + end + end + end +end diff --git a/.github/marathon-cloud.sample.json b/.github/marathon-cloud.sample.json new file mode 100644 index 0000000..fb9b149 --- /dev/null +++ b/.github/marathon-cloud.sample.json @@ -0,0 +1,21 @@ +{ + "version": "1.0.0", + "darwin": { + "url": "A", + "sha256": "B" + }, + "linux": { + "amd64": { + "url": "C", + "sha256": "D" + }, + "arm64": { + "url": "E", + "sha256": "F" + }, + "arm": { + "url": "G", + "sha256": "H" + } + } +} diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index c320377..36fa3f9 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -1,18 +1,526 @@ -on: [push, pull_request] +name: cicd +env: + CICD_INTERMEDIATES_DIR: "_cicd-intermediates" + MSRV_FEATURES: --no-default-features -name: Build +on: + workflow_dispatch: + pull_request: + push: + branches: + - main + tags: + - '*' jobs: - check: - name: Check + all-jobs: + if: always() # Otherwise this job is skipped if the matrix job fails + name: all-jobs runs-on: ubuntu-latest + needs: + - crate_metadata + - ensure_cargo_fmt + - min_version + - documentation + - cargo-audit + - build steps: - - uses: actions/checkout@v3 - - name: Set up Go - uses: actions/setup-go@v4 + - run: jq --exit-status 'all(.result == "success")' <<< '${{ toJson(needs) }}' + + crate_metadata: + name: Extract crate metadata + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Extract crate information + id: crate_metadata + run: | + cargo metadata --no-deps --format-version 1 | jq -r '"name=" + .packages[0].name' | tee -a $GITHUB_OUTPUT + cargo metadata --no-deps --format-version 1 | jq -r '"maintainer=" + .packages[0].authors[0]' | tee -a $GITHUB_OUTPUT + cargo metadata --no-deps --format-version 1 | jq -r '"homepage=" + .packages[0].homepage' | tee -a $GITHUB_OUTPUT + cargo metadata --no-deps --format-version 1 | jq -r '"msrv=" + .packages[0].rust_version' | tee -a $GITHUB_OUTPUT + + if [[ $GITHUB_REF =~ ^refs/tags/[0-9].* ]]; then + VERSION=$(echo $GITHUB_REF | cut -d / -f 3) + echo "version=${VERSION}" >> $GITHUB_OUTPUT + else + cargo metadata --no-deps --format-version 1 | jq -r '"version=" + .packages[0].version' | tee -a $GITHUB_OUTPUT + fi + + outputs: + name: ${{ steps.crate_metadata.outputs.name }} + version: ${{ steps.crate_metadata.outputs.version }} + maintainer: ${{ steps.crate_metadata.outputs.maintainer }} + homepage: ${{ steps.crate_metadata.outputs.homepage }} + msrv: ${{ steps.crate_metadata.outputs.msrv }} + + ensure_cargo_fmt: + name: Ensure 'cargo fmt' has been run + runs-on: ubuntu-22.04 + steps: + - uses: dtolnay/rust-toolchain@stable + with: + components: rustfmt + - uses: actions/checkout@v4 + - run: cargo fmt -- --check + + min_version: + name: Minimum supported rust version + runs-on: ubuntu-22.04 + needs: crate_metadata + steps: + - name: Checkout source code + uses: actions/checkout@v4 + + - name: Install rust toolchain (v${{ needs.crate_metadata.outputs.msrv }}) + uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ needs.crate_metadata.outputs.msrv }} + components: clippy + - name: Run clippy (on minimum supported rust version to prevent warnings we can't fix) + run: cargo clippy --locked --all-targets ${{ env.MSRV_FEATURES }} + - name: Run tests + run: cargo test --locked ${{ env.MSRV_FEATURES }} + + documentation: + name: Documentation + runs-on: ubuntu-22.04 + steps: + - name: Git checkout + uses: actions/checkout@v4 + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@stable + - name: Generate documentation + run: mkdir -p target/release/man && OUT_DIR=target/release/man/ cargo run --bin marathon-cloud-mangen + - name: Show man page + run: man target/release/man/marathon-cloud.1 + + cargo-audit: + name: cargo audit + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - run: cargo audit + + build: + name: ${{ matrix.job.target }} (${{ matrix.job.os }}) + runs-on: ${{ matrix.job.os }} + needs: crate_metadata + strategy: + fail-fast: false + matrix: + job: + - { target: aarch64-unknown-linux-gnu , os: ubuntu-22.04, dpkg_arch: arm64, use-cross: true } + - { target: aarch64-unknown-linux-musl , os: ubuntu-22.04, dpkg_arch: arm64, use-cross: true } + - { target: arm-unknown-linux-gnueabihf , os: ubuntu-22.04, dpkg_arch: armhf, use-cross: true } + - { target: arm-unknown-linux-musleabihf , os: ubuntu-22.04, dpkg_arch: musl-linux-armhf, use-cross: true } + - { target: i686-pc-windows-msvc , os: windows-2019, } + - { target: i686-unknown-linux-gnu , os: ubuntu-22.04, dpkg_arch: i686, use-cross: true } + - { target: i686-unknown-linux-musl , os: ubuntu-22.04, dpkg_arch: musl-linux-i686, use-cross: true } + - { target: 'x86_64-apple-darwin,aarch64-apple-darwin', os: macos-13, } + - { target: x86_64-pc-windows-gnu , os: windows-2019, } + - { target: x86_64-pc-windows-msvc , os: windows-2019, } + - { target: x86_64-unknown-linux-gnu , os: ubuntu-22.04, dpkg_arch: amd64, use-cross: true } + - { target: x86_64-unknown-linux-musl , os: ubuntu-22.04, dpkg_arch: musl-linux-amd64, use-cross: true } + env: + BUILD_CMD: cargo + CARGO_EDIT_VERSION: 0.12.2 + steps: + - name: Checkout source code + uses: actions/checkout@v4 + + - name: Check for release + id: is-release + shell: bash + run: | + unset IS_RELEASE ; if [[ $GITHUB_REF =~ ^refs/tags/[0-9].* ]]; then IS_RELEASE='true' ; fi + echo "IS_RELEASE=${IS_RELEASE}" >> $GITHUB_OUTPUT + unset VERSION + VERSION=$(echo $GITHUB_REF | cut -d / -f 3) + echo "VERSION=${VERSION}" >> $GITHUB_OUTPUT + + - name: Install prerequisites + shell: bash + run: | + case ${{ matrix.job.target }} in + arm-unknown-linux-*) sudo apt-get -y update ; sudo apt-get -y install gcc-arm-linux-gnueabihf ;; + aarch64-unknown-linux-gnu) sudo apt-get -y update ; sudo apt-get -y install gcc-aarch64-linux-gnu ;; + esac + + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@stable + with: + targets: ${{ matrix.job.target }} + + - run: | + cargo install --version ${{ env.CARGO_EDIT_VERSION }} cargo-edit + cargo set-version ${{ steps.is-release.outputs.VERSION }} + if: steps.is-release.outputs.IS_RELEASE + + - name: Install cross + if: matrix.job.use-cross + uses: taiki-e/install-action@v2 + with: + tool: cross + + - name: Overwrite build command env variable + if: matrix.job.use-cross + shell: bash + run: echo "BUILD_CMD=cross" >> $GITHUB_ENV + + - name: Show version information (Rust, cargo, GCC) + shell: bash + run: | + gcc --version || true + rustup -V + rustup toolchain list + rustup default + cargo -V + rustc -V + + - name: Set binary name & path + id: bin + shell: bash + run: | + # Figure out suffix of binary + EXE_suffix="" + case ${{ matrix.job.target }} in + *-pc-windows-*) EXE_suffix=".exe" ;; + esac; + + # Setup paths + BIN_NAME="${{ needs.crate_metadata.outputs.name }}${EXE_suffix}" + # For macos we use universal binary + if [[ ${{ matrix.job.os }} = macos-* ]]; then + BIN_PATH="target/universal-apple-darwin/release/${BIN_NAME}" + else + BIN_PATH="target/${{ matrix.job.target }}/release/${BIN_NAME}" + fi + + # Let subsequent steps know where to find the binary + + echo "BIN_PATH=${BIN_PATH}" >> $GITHUB_OUTPUT + echo "BIN_NAME=${BIN_NAME}" >> $GITHUB_OUTPUT + + - name: Build + shell: bash + run: | + if [[ ${{ matrix.job.os }} = macos-* ]] + then + # For macos we build universal binary + for i in $(echo "${{ matrix.job.target}}" | sed "s/,/ /g") + do + $BUILD_CMD build --locked --release --target=$i + done + + BIN_INTEL_PATH="target/x86_64-apple-darwin/release/${{ steps.bin.outputs.BIN_NAME}}" + BIN_APPLE_PATH="target/aarch64-apple-darwin/release/${{ steps.bin.outputs.BIN_NAME}}" + + mkdir -p target/universal-apple-darwin/release + lipo -create -output target/universal-apple-darwin/release/${{ steps.bin.outputs.BIN_NAME}} ${BIN_INTEL_PATH} ${BIN_APPLE_PATH} + else + $BUILD_CMD build --locked --release --target=${{ matrix.job.target }} + fi + + - name: Set testing options + id: test-options + shell: bash + run: | + # test only library unit tests and binary for arm-type targets + unset CARGO_TEST_OPTIONS + unset CARGO_TEST_OPTIONS ; case ${{ matrix.job.target }} in arm-* | aarch64-*) CARGO_TEST_OPTIONS="--lib --bin ${{ needs.crate_metadata.outputs.name }}" ;; esac; + echo "CARGO_TEST_OPTIONS=${CARGO_TEST_OPTIONS}" >> $GITHUB_OUTPUT + + - name: Run tests + shell: bash + run: | + if [[ ${{ matrix.job.os }} = windows-* ]]; then + powershell.exe -command "$BUILD_CMD test --locked --target=${{ matrix.job.target }} ${{ steps.test-options.outputs.CARGO_TEST_OPTIONS}}" + elif [[ ${{ matrix.job.os }} = macos-* ]]; then + # For macos universal binary we test using x86 since GitHub Actions do not yet support arm execution environment + $BUILD_CMD test --locked --target=x86_64-apple-darwin ${{ steps.test-options.outputs.CARGO_TEST_OPTIONS}} + else + $BUILD_CMD test --locked --target=${{ matrix.job.target }} ${{ steps.test-options.outputs.CARGO_TEST_OPTIONS}} + fi + + - name: Generate manpage + shell: bash + run: | + if [[ ${{ matrix.job.os }} = windows-* ]] + then + mkdir -p target/release/man + powershell.exe -command "$BUILD_CMD run --target=${{ matrix.job.target }} --bin marathon-cloud-mangen target\\release\\man" + elif [[ ${{ matrix.job.os }} = macos-* ]]; then + # For macos universal binary we test using x86 since GitHub Actions do not yet support arm execution environment + mkdir -p target/release/man + OUT_DIR=target/release/man/ $BUILD_CMD run --target=x86_64-apple-darwin --bin marathon-cloud-mangen + else + mkdir -p target/release/man + OUT_DIR=target/release/man/ $BUILD_CMD run --target=${{ matrix.job.target }} --bin marathon-cloud-mangen + fi + + - name: Generate completions + shell: bash + run: | + if [[ ${{ matrix.job.os }} = windows-* ]] + then + mkdir -p target/release/autocomplete + powershell.exe -command "$BUILD_CMD -q run --target=${{ matrix.job.target }} -- completions bash" > target/release/autocomplete/marathon-cloud.bash + powershell.exe -command "$BUILD_CMD -q run --target=${{ matrix.job.target }} -- completions fish" > target/release/autocomplete/marathon-cloud.fish + powershell.exe -command "$BUILD_CMD -q run --target=${{ matrix.job.target }} -- completions powershell" > target/release/autocomplete/_marathon-cloud.ps1 + powershell.exe -command "$BUILD_CMD -q run --target=${{ matrix.job.target }} -- completions zsh" > target/release/autocomplete/marathon-cloud.zsh + elif [[ ${{ matrix.job.os }} = macos-* ]]; then + # For macos universal binary we test using x86 since GitHub Actions do not yet support arm execution environment + mkdir -p target/release/autocomplete + $BUILD_CMD -q run --target=x86_64-apple-darwin -- completions bash > target/release/autocomplete/marathon-cloud.bash + $BUILD_CMD -q run --target=x86_64-apple-darwin -- completions fish > target/release/autocomplete/marathon-cloud.fish + $BUILD_CMD -q run --target=x86_64-apple-darwin -- completions powershell > target/release/autocomplete/_marathon-cloud.ps1 + $BUILD_CMD -q run --target=x86_64-apple-darwin -- completions zsh > target/release/autocomplete/marathon-cloud.zsh + else + mkdir -p target/release/autocomplete + $BUILD_CMD -q run --target=${{ matrix.job.target }} -- completions bash > target/release/autocomplete/marathon-cloud.bash + $BUILD_CMD -q run --target=${{ matrix.job.target }} -- completions fish > target/release/autocomplete/marathon-cloud.fish + $BUILD_CMD -q run --target=${{ matrix.job.target }} -- completions powershell > target/release/autocomplete/_marathon-cloud.ps1 + $BUILD_CMD -q run --target=${{ matrix.job.target }} -- completions zsh > target/release/autocomplete/marathon-cloud.zsh + fi + + - name: Create tarball + id: package + shell: bash + run: | + PKG_suffix=".tar.gz" ; case ${{ matrix.job.target }} in *-pc-windows-*) PKG_suffix=".zip" ;; esac; + if [[ ${{ matrix.job.os }} = macos-* ]]; then + PKG_BASENAME=${{ needs.crate_metadata.outputs.name }}-v${{ needs.crate_metadata.outputs.version }}-universal-apple-darwin + else + PKG_BASENAME=${{ needs.crate_metadata.outputs.name }}-v${{ needs.crate_metadata.outputs.version }}-${{ matrix.job.target }} + fi + PKG_NAME=${PKG_BASENAME}${PKG_suffix} + echo "PKG_NAME=${PKG_NAME}" >> $GITHUB_OUTPUT + + PKG_STAGING="${{ env.CICD_INTERMEDIATES_DIR }}/package" + ARCHIVE_DIR="${PKG_STAGING}/${PKG_BASENAME}/" + mkdir -p "${ARCHIVE_DIR}" + mkdir -p "${ARCHIVE_DIR}/autocomplete" + + # Binary + cp "${{ steps.bin.outputs.BIN_PATH }}" "$ARCHIVE_DIR" + + # README, LICENSE and CHANGELOG files + cp "README.md" "LICENSE" "$ARCHIVE_DIR" + + # Man page + cp 'target/release/man/marathon-cloud.1' "$ARCHIVE_DIR" + + # Autocompletion files + cp 'target/release/autocomplete/marathon-cloud.bash' "$ARCHIVE_DIR/autocomplete/${{ needs.crate_metadata.outputs.name }}.bash" + cp 'target/release/autocomplete/marathon-cloud.fish' "$ARCHIVE_DIR/autocomplete/${{ needs.crate_metadata.outputs.name }}.fish" + cp 'target/release/autocomplete/_marathon-cloud.ps1' "$ARCHIVE_DIR/autocomplete/_${{ needs.crate_metadata.outputs.name }}.ps1" + cp 'target/release/autocomplete/marathon-cloud.zsh' "$ARCHIVE_DIR/autocomplete/${{ needs.crate_metadata.outputs.name }}.zsh" + + # base compressed package + pushd "${PKG_STAGING}/" >/dev/null + case ${{ matrix.job.target }} in + *-pc-windows-*) 7z -y a "${PKG_NAME}" "${PKG_BASENAME}"/* | tail -2 ;; + *) tar czf "${PKG_NAME}" "${PKG_BASENAME}"/* ;; + esac; + popd >/dev/null + + # Let subsequent steps know where to find the compressed package + echo "PKG_PATH=${PKG_STAGING}/${PKG_NAME}" >> $GITHUB_OUTPUT + + - name: Create Debian package + id: debian-package + shell: bash + if: startsWith(matrix.job.os, 'ubuntu') + run: | + COPYRIGHT_YEARS="2023 - "$(date "+%Y") + DPKG_STAGING="${{ env.CICD_INTERMEDIATES_DIR }}/debian-package" + DPKG_DIR="${DPKG_STAGING}/dpkg" + mkdir -p "${DPKG_DIR}" + + DPKG_BASENAME=${{ needs.crate_metadata.outputs.name }} + DPKG_CONFLICTS=${{ needs.crate_metadata.outputs.name }}-musl + case ${{ matrix.job.target }} in *-musl) DPKG_BASENAME=${{ needs.crate_metadata.outputs.name }}-musl ; DPKG_CONFLICTS=${{ needs.crate_metadata.outputs.name }} ;; esac; + DPKG_VERSION=${{ needs.crate_metadata.outputs.version }} + DPKG_ARCH="${{ matrix.job.dpkg_arch }}" + DPKG_NAME="${DPKG_BASENAME}_v${DPKG_VERSION}_${DPKG_ARCH}.deb" + echo "DPKG_NAME=${DPKG_NAME}" >> $GITHUB_OUTPUT + + # Binary + install -Dm755 "${{ steps.bin.outputs.BIN_PATH }}" "${DPKG_DIR}/usr/bin/${{ steps.bin.outputs.BIN_NAME }}" + + # Man page + install -Dm644 'target/release/man/marathon-cloud.1' "${DPKG_DIR}/usr/share/man/man1/${{ needs.crate_metadata.outputs.name }}.1" + gzip -n --best "${DPKG_DIR}/usr/share/man/man1/${{ needs.crate_metadata.outputs.name }}.1" + + # Autocompletion files + install -Dm644 'target/release/autocomplete/marathon-cloud.bash' "${DPKG_DIR}/usr/share/bash-completion/completions/${{ needs.crate_metadata.outputs.name }}" + install -Dm644 'target/release/autocomplete/marathon-cloud.fish' "${DPKG_DIR}/usr/share/fish/vendor_completions.d/${{ needs.crate_metadata.outputs.name }}.fish" + install -Dm644 'target/release/autocomplete/marathon-cloud.zsh' "${DPKG_DIR}/usr/share/zsh/vendor-completions/_${{ needs.crate_metadata.outputs.name }}" + + # README and LICENSE + install -Dm644 "README.md" "${DPKG_DIR}/usr/share/doc/${DPKG_BASENAME}/README.md" + install -Dm644 "LICENSE" "${DPKG_DIR}/usr/share/doc/${DPKG_BASENAME}/LICENSE" + + cat > "${DPKG_DIR}/usr/share/doc/${DPKG_BASENAME}/copyright" < "${DPKG_DIR}/DEBIAN/control" <> $GITHUB_OUTPUT + + # build dpkg + fakeroot dpkg-deb --build "${DPKG_DIR}" "${DPKG_PATH}" + + - name: "Artifact upload: tarball" + uses: actions/upload-artifact@master + with: + name: ${{ steps.package.outputs.PKG_NAME }} + path: ${{ steps.package.outputs.PKG_PATH }} + + - name: "Artifact upload: Debian package" + uses: actions/upload-artifact@master + if: steps.debian-package.outputs.DPKG_NAME + with: + name: ${{ steps.debian-package.outputs.DPKG_NAME }} + path: ${{ steps.debian-package.outputs.DPKG_PATH }} + + - name: Publish archives and packages + uses: softprops/action-gh-release@v1 + if: steps.is-release.outputs.IS_RELEASE + with: + files: | + ${{ steps.package.outputs.PKG_PATH }} + ${{ steps.debian-package.outputs.DPKG_PATH }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + homebrew: + runs-on: ubuntu-latest + needs: + - build + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Check for release + id: is-release + shell: bash + run: | + unset IS_RELEASE ; if [[ $GITHUB_REF =~ ^refs/tags/[0-9].* ]]; then IS_RELEASE='true' ; fi + echo "IS_RELEASE=${IS_RELEASE}" >> $GITHUB_OUTPUT + unset VERSION + VERSION=$(echo $GITHUB_REF | cut -d / -f 3) + echo "VERSION=${VERSION}" >> $GITHUB_OUTPUT + - uses: actions/checkout@v4 + with: + repository: 'Malinskiy/homebrew-tap' + ref: 'master' + path: 'homebrew-tap' + token: ${{ secrets.HOMEBREW_TAP_GITHUB_TOKEN }} + - uses: ruby/setup-ruby@v1 + with: + ruby-version: 3.3 + - name: Render new formulae + if: steps.is-release.outputs.IS_RELEASE + run: | + gem install liquid-cli + DARWIN_URL="https://github.com/$GITHUB_REPOSITORY/releases/download/${{ steps.is-release.outputs.VERSION }}/marathon-cloud-v${{ steps.is-release.outputs.VERSION }}-universal-apple-darwin.tar.gz" + LINUX_AMD64_URL="https://github.com/$GITHUB_REPOSITORY/releases/download/${{ steps.is-release.outputs.VERSION }}/marathon-cloud-v${{ steps.is-release.outputs.VERSION }}-x86_64-unknown-linux-gnu.tar.gz" + LINUX_ARM64_URL="https://github.com/$GITHUB_REPOSITORY/releases/download/${{ steps.is-release.outputs.VERSION }}/marathon-cloud-v${{ steps.is-release.outputs.VERSION }}-aarch64-unknown-linux-gnu.tar.gz" + LINUX_ARM_URL="https://github.com/$GITHUB_REPOSITORY/releases/download/${{ steps.is-release.outputs.VERSION }}/marathon-cloud-v${{ steps.is-release.outputs.VERSION }}-arm-unknown-linux-gnueabihf.tar.gz" + DARWIN_SHA256=$(curl -L --retry 5 --retry-max-time 120 $DARWIN_URL | sha256sum | sed 's/ -//') + LINUX_AMD64_SHA256=$(curl -L --retry 5 --retry-max-time 120 $LINUX_AMD64_URL | sha256sum | sed 's/ -//') + LINUX_ARM64_SHA256=$(curl -L --retry 5 --retry-max-time 120 $LINUX_ARM64_URL | sha256sum | sed 's/ -//') + LINUX_ARM_SHA256=$(curl -L --retry 5 --retry-max-time 120 $LINUX_ARM_URL | sha256sum | sed 's/ -//') + echo "{\"version\":\"${{ steps.is-release.outputs.VERSION }}\",\"darwin\":{\"url\":\"$DARWIN_URL\",\"sha256\":\"$DARWIN_SHA256\"},\"linux\":{\"amd64\":{\"url\":\"$LINUX_AMD64_URL\",\"sha256\":\"$LINUX_AMD64_SHA256\"},\"arm64\":{\"url\":\"$LINUX_ARM64_URL\",\"sha256\":\"$LINUX_ARM64_SHA256\"},\"arm\":{\"url\":\"$LINUX_ARM_URL\",\"sha256\":\"$LINUX_ARM_SHA256\"}}}" > .github/marathon-cloud.json + cat .github/marathon-cloud.json | jq . + cat .github/marathon-cloud.rb.liquid | liquid "$(< .github/marathon-cloud.json)" > homebrew-tap/Formula/marathon-cloud.rb + ls homebrew-tap/Formula + cat homebrew-tap/Formula/marathon-cloud.rb + cd homebrew-tap + git diff + git config --global user.name 'Anton Malinskiy' + git config --global user.email 'malinskiy@users.noreply.github.com' + git commit -am "Brew formula update for marathon version ${{ steps.tag.outputs.tag }}" + git push + checksums: + runs-on: ubuntu-latest + needs: + - build + steps: + - name: Check for release + id: is-release + shell: bash + run: | + unset IS_RELEASE ; if [[ $GITHUB_REF =~ ^refs/tags/[0-9].* ]]; then IS_RELEASE='true' ; fi + echo "IS_RELEASE=${IS_RELEASE}" >> $GITHUB_OUTPUT + unset VERSION + VERSION=$(echo $GITHUB_REF | cut -d / -f 3) + echo "VERSION=${VERSION}" >> $GITHUB_OUTPUT + - uses: robinraju/release-downloader@v1.9 + if: steps.is-release.outputs.IS_RELEASE + with: + tag: "${{ steps.is-release.outputs.VERSION }}" + fileName: "*" + - name: Generate checksum + uses: jmgilman/actions-generate-checksum@v1 + if: steps.is-release.outputs.IS_RELEASE + with: + output: checksums.txt + patterns: | + * + - name: Upload checksums + uses: softprops/action-gh-release@v1 + if: steps.is-release.outputs.IS_RELEASE + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: - go-version: '1.18' - - name: Build - run: go build -v ./... - - name: Test - run: go test -v ./... + files: | + checksums.txt diff --git a/.github/workflows/docker.yaml b/.github/workflows/docker.yaml new file mode 100644 index 0000000..4590eb2 --- /dev/null +++ b/.github/workflows/docker.yaml @@ -0,0 +1,46 @@ +name: deploy-docker-image + +on: + push: + branches: + - main + tags: + - '*' + +jobs: + docker: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Determine version + id: determine-version + shell: bash + run: | + git fetch --force --tags + VERSION=$(git describe --tags --always) + echo "VERSION=${VERSION}" >> $GITHUB_OUTPUT + - name: Enable KVM + run: | + echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules + sudo udevadm control --reload-rules + sudo udevadm trigger --name-match=kvm + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + - name: Docker Hub Login + run: echo "$HUB_PASSWORD" | docker login --username $HUB_LOGIN --password-stdin + env: + HUB_LOGIN: ${{ secrets.HUB_LOGIN }} + HUB_PASSWORD: ${{ secrets.HUB_PASSWORD }} + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + - run: | + docker buildx build \ + --push \ + --platform linux/arm64/v8,linux/amd64 \ + --build-arg=VERSION=3.18 \ + --build-arg=RUST_VERSION=1.75.0 \ + --tag marathonlabs/marathon-cloud:${{ steps.determine-version.outputs.VERSION }} \ + --tag marathonlabs/marathon-cloud:latest \ + . diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml deleted file mode 100644 index b577f57..0000000 --- a/.github/workflows/release.yaml +++ /dev/null @@ -1,38 +0,0 @@ -name: release - -on: - push: - # run only against tags - tags: - - '*' - -permissions: - contents: write - # packages: write - # issues: write - -jobs: - goreleaser: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - run: git fetch --force --tags - - name: Set up Go - uses: actions/setup-go@v4 - with: - go-version: '1.18' - - name: Docker Hub Login - run: echo "$HUB_PASSWORD" | docker login --username $HUB_LOGIN --password-stdin - env: - HUB_LOGIN: ${{ secrets.HUB_LOGIN }} - HUB_PASSWORD: ${{ secrets.HUB_PASSWORD }} - - uses: goreleaser/goreleaser-action@v4 - with: - distribution: goreleaser - version: latest - args: release --clean - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - HOMEBREW_TAP_GITHUB_TOKEN: ${{ secrets.HOMEBREW_TAP_GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore index be222a1..5d02e30 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ cli dist +target diff --git a/.goreleaser.yaml b/.goreleaser.yaml deleted file mode 100644 index ae5fd7b..0000000 --- a/.goreleaser.yaml +++ /dev/null @@ -1,107 +0,0 @@ -project_name: marathon-cloud -before: - hooks: - - go mod tidy -builds: - - id: marathon-cloud - binary: marathon-cloud - main: main.go - env: - - CGO_ENABLED=0 - goarch: - - amd64 - - arm - - arm64 - goarm: - - "6" - - "7" -universal_binaries: -- replace: true -archives: - - name_template: "{{ .ProjectName }}_{{ .Os }}_{{ .Arch }}{{- if .Arm }}_{{ .Arm }}{{ end }}-{{ .Summary }}" - builds: - - marathon-cloud - format_overrides: - - goos: windows - format: zip - files: - - README.md - - LICENSE -dockers: -- image_templates: - - "marathonlabs/{{ .ProjectName }}:{{ .Version }}-amd64" - - "marathonlabs/{{ .ProjectName }}:latest-amd64" - use: buildx - goarch: amd64 - dockerfile: Dockerfile - build_flag_templates: - - "--platform=linux/amd64" - - "--label=org.opencontainers.image.created={{.Date}}" - - "--label=org.opencontainers.image.title={{.ProjectName}}" - - "--label=org.opencontainers.image.revision={{.FullCommit}}" - - "--label=org.opencontainers.image.version={{.Version}}" -- image_templates: - - "marathonlabs/{{ .ProjectName }}:{{ .Version }}-arm64v8" - - "marathonlabs/{{ .ProjectName }}:latest-arm64v8" - use: buildx - goarch: arm64 - dockerfile: Dockerfile - build_flag_templates: - - "--platform=linux/arm64/v8" - - "--label=org.opencontainers.image.created={{.Date}}" - - "--label=org.opencontainers.image.title={{.ProjectName}}" - - "--label=org.opencontainers.image.revision={{.FullCommit}}" - - "--label=org.opencontainers.image.version={{.Version}}" -- image_templates: - - "marathonlabs/{{ .ProjectName }}:{{ .Version }}-armv6" - - "marathonlabs/{{ .ProjectName }}:latest-armv6" - use: buildx - goarch: arm - goarm: 6 - dockerfile: Dockerfile - build_flag_templates: - - "--platform=linux/arm/v6" - - "--label=org.opencontainers.image.created={{.Date}}" - - "--label=org.opencontainers.image.title={{.ProjectName}}" - - "--label=org.opencontainers.image.revision={{.FullCommit}}" - - "--label=org.opencontainers.image.version={{.Version}}" -- image_templates: - - "marathonlabs/{{ .ProjectName }}:{{ .Version }}-armv7" - - "marathonlabs/{{ .ProjectName }}:latest-armv7" - use: buildx - goarch: arm - goarm: 7 - dockerfile: Dockerfile - build_flag_templates: - - "--platform=linux/arm/v7" - - "--label=org.opencontainers.image.created={{.Date}}" - - "--label=org.opencontainers.image.title={{.ProjectName}}" - - "--label=org.opencontainers.image.revision={{.FullCommit}}" - - "--label=org.opencontainers.image.version={{.Version}}" -docker_manifests: -- name_template: "marathonlabs/{{ .ProjectName }}:{{ .Version }}" - image_templates: - - "marathonlabs/{{ .ProjectName }}:{{ .Version }}-amd64" - - "marathonlabs/{{ .ProjectName }}:{{ .Version }}-arm64v8" - - "marathonlabs/{{ .ProjectName }}:{{ .Version }}-armv6" - - "marathonlabs/{{ .ProjectName }}:{{ .Version }}-armv7" -- name_template: "marathonlabs/{{ .ProjectName }}:latest" - image_templates: - - "marathonlabs/{{ .ProjectName }}:latest-amd64" - - "marathonlabs/{{ .ProjectName }}:latest-arm64v8" - - "marathonlabs/{{ .ProjectName }}:latest-armv6" - - "marathonlabs/{{ .ProjectName }}:latest-armv7" -checksum: - name_template: 'checksums.txt' -release: - disable: false -brews: - - tap: - owner: Malinskiy - name: homebrew-tap - branch: master - token: "{{ .Env.HOMEBREW_TAP_GITHUB_TOKEN }}" - homepage: "https://github.com/MarathonLabs/marathon-cloud-cli" - description: "Command-line interface for Marathon Cloud" - license: "MIT" - folder: Formula diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..2e2e579 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,2077 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "addr2line" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "aho-corasick" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +dependencies = [ + "memchr", +] + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anstream" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e2e1ebcb11de5c03c67de28a7df593d32191b44939c482e97702baaaa6ab6a5" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" + +[[package]] +name = "anstyle-parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "317b9a89c1868f5ea6ff1d9539a69f45dffc21ce321ac1fd1160dfa48c8e2140" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" +dependencies = [ + "windows-sys 0.48.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0699d10d2f4d628a98ee7b57b289abbc98ff3bad977cb3152709d4bf2330628" +dependencies = [ + "anstyle", + "windows-sys 0.48.0", +] + +[[package]] +name = "anyhow" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca" + +[[package]] +name = "async-stream" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd56dd203fef61ac097dd65721a419ddccb106b2d2b70ba60a6b529f03961a51" +dependencies = [ + "async-stream-impl", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "async-trait" +version = "0.1.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a66537f1bb974b254c98ed142ff995236e81b9d0fe4db0575f46612cb15eb0f9" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "backtrace" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "base64" +version = "0.21.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" + +[[package]] +name = "bumpalo" +version = "3.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" + +[[package]] +name = "bytes" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" + +[[package]] +name = "cc" +version = "1.0.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +dependencies = [ + "libc", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chrono" +version = "0.4.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f13690e35a5e4ace198e7beea2895d29f3a9cc55015fcebe6336bd2010af9eb" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "num-traits", + "serde", + "windows-targets 0.52.0", +] + +[[package]] +name = "clap" +version = "4.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e578d6ec4194633722ccf9544794b71b1385c3c027efe0c55db226fc880865c" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap-verbosity-flag" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5fdbb015d790cfb378aca82caf9cc52a38be96a7eecdb92f31b4366a8afc019" +dependencies = [ + "clap", + "log", +] + +[[package]] +name = "clap_builder" +version = "4.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4df4df40ec50c46000231c914968278b1eb05098cf8f1b3a518a95030e71d1c7" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_complete" +version = "4.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df631ae429f6613fcd3a7c1adbdb65f637271e561b03680adaa6573015dfb106" +dependencies = [ + "clap", +] + +[[package]] +name = "clap_derive" +version = "4.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "clap_lex" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" + +[[package]] +name = "clap_mangen" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43144ab702c764b0a3ecda5ecd2aba2e6874d8de4b9f56930bbb1e88fcecd84a" +dependencies = [ + "clap", + "roff", +] + +[[package]] +name = "colorchoice" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" + +[[package]] +name = "colored" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2674ec482fbc38012cf31e6c42ba0177b431a0cb6f15fe40efa5aab1bda516f6" +dependencies = [ + "is-terminal", + "lazy_static", + "windows-sys 0.48.0", +] + +[[package]] +name = "console" +version = "0.15.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c926e00cc70edefdc64d3a5ff31cc65bb97a3460097762bd23afb4d8145fccf8" +dependencies = [ + "encode_unicode", + "lazy_static", + "libc", + "unicode-width", + "windows-sys 0.45.0", +] + +[[package]] +name = "core-foundation" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" + +[[package]] +name = "darling" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b750cb3417fd1b327431a470f388520309479ab0bf5e323505daf0290cd3850" +dependencies = [ + "darling_core 0.14.4", + "darling_macro 0.14.4", +] + +[[package]] +name = "darling" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0209d94da627ab5605dcccf08bb18afa5009cfbef48d8a8b7d7bdbc79be25c5e" +dependencies = [ + "darling_core 0.20.3", + "darling_macro 0.20.3", +] + +[[package]] +name = "darling_core" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "109c1ca6e6b7f82cc233a97004ea8ed7ca123a9af07a8230878fcfda9b158bf0" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "darling_core" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "177e3443818124b357d8e76f53be906d60937f0d3a90773a664fa63fa253e621" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.48", +] + +[[package]] +name = "darling_macro" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4aab4dbc9f7611d8b55048a3a16d2d010c2c8334e46304b40ac1cc14bf3b48e" +dependencies = [ + "darling_core 0.14.4", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "darling_macro" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5" +dependencies = [ + "darling_core 0.20.3", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "deranged" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f32d04922c60427da6f9fef14d042d9edddef64cb9d4ce0d64d0685fbeb1fd3" +dependencies = [ + "powerfmt", + "serde", +] + +[[package]] +name = "encode_unicode" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" + +[[package]] +name = "encoding_rs" +version = "0.8.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "errno" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "fastrand" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0290714b38af9b4a7b094b8a37086d1b4e61f2df9122c3cad2577669145335" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff4dd66668b557604244583e3e1e1eada8c5c2e96a6d0d6653ede395b78bbacb" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb1d22c66e66d9d72e1758f0bd7d4fd0bee04cad842ee34587d68c07e45d088c" + +[[package]] +name = "futures-executor" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f4fb8693db0cf099eadcca0efe2a5a22e4550f98ed16aba6c48700da29597bc" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bf34a163b5c4c52d0478a4d757da8fb65cabef42ba90515efee0f6f9fa45aaa" + +[[package]] +name = "futures-macro" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "futures-sink" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e36d3378ee38c2a36ad710c5d30c2911d752cb941c00c72dbabfb786a7970817" + +[[package]] +name = "futures-task" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efd193069b0ddadc69c46389b740bbccdd97203899b48d09c5f7969591d6bae2" + +[[package]] +name = "futures-timer" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c" + +[[package]] +name = "futures-util" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a19526d624e703a3179b3d322efec918b6246ea0fa51d41124525f00f1cc8104" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "getrandom" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "gimli" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" + +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + +[[package]] +name = "h2" +version = "0.3.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb2c4422095b67ee78da96fbb51a4cc413b3b25883c7717ff7ca1ab31022c9c9" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap 2.2.2", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hashbrown" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "hermit-abi" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "http" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hyper" +version = "0.14.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffb1cfd654a8219eaef89881fdb3bb3b1cdc5fa75ded05d6933b2b382e395468" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2 0.4.10", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" +dependencies = [ + "futures-util", + "http", + "hyper", + "rustls", + "tokio", + "tokio-rustls", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.59" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6a67363e2aa4443928ce15e57ebae94fd8949958fd1223c4cfc0cd473ad7539" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "idna" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", +] + +[[package]] +name = "indexmap" +version = "2.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "824b2ae422412366ba479e8111fd301f7b5faece8149317bb81925979a53f520" +dependencies = [ + "equivalent", + "hashbrown 0.14.3", + "serde", +] + +[[package]] +name = "indicatif" +version = "0.17.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb28741c9db9a713d93deb3bb9515c20788cef5815265bee4980e87bde7e0f25" +dependencies = [ + "console", + "instant", + "number_prefix", + "portable-atomic", + "unicode-width", +] + +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "ipnet" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" + +[[package]] +name = "is-terminal" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" +dependencies = [ + "hermit-abi", + "rustix", + "windows-sys 0.48.0", +] + +[[package]] +name = "itoa" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" + +[[package]] +name = "js-sys" +version = "0.3.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.153" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" + +[[package]] +name = "linux-raw-sys" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" + +[[package]] +name = "lock_api" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" + +[[package]] +name = "marathon-cloud" +version = "0.0.1" +dependencies = [ + "anyhow", + "async-stream", + "async-trait", + "clap", + "clap-verbosity-flag", + "clap_complete", + "clap_mangen", + "console", + "futures", + "h2", + "indicatif", + "log", + "num_cpus", + "reqwest", + "rstest", + "serde", + "serde-enum-str", + "serde_json", + "serde_with", + "serde_yaml", + "simple_logger", + "tempfile", + "thiserror", + "time", + "tokio", + "tokio-util", + "url", +] + +[[package]] +name = "memchr" +version = "2.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "mime_guess" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef" +dependencies = [ + "mime", + "unicase", +] + +[[package]] +name = "miniz_oxide" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +dependencies = [ + "adler", +] + +[[package]] +name = "mio" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dce281c5e46beae905d4de1870d8b1509a9142b62eedf18b443b011ca8343d0" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.48.0", +] + +[[package]] +name = "num-traits" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "num_threads" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44" +dependencies = [ + "libc", +] + +[[package]] +name = "number_prefix" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" + +[[package]] +name = "object" +version = "0.32.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets 0.48.5", +] + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "pin-project-lite" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "portable-atomic" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bccab0e7fd7cc19f820a1c8c91720af652d0c88dc9664dd72aef2614f04af3b" + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "proc-macro2" +version = "1.0.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "redox_syscall" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "regex" +version = "1.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" + +[[package]] +name = "relative-path" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c707298afce11da2efef2f600116fa93ffa7a032b5d7b628aa17711ec81383ca" + +[[package]] +name = "reqwest" +version = "0.11.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6920094eb85afde5e4a138be3f2de8bbdf28000f0029e72c45025a56b042251" +dependencies = [ + "base64", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "hyper", + "hyper-rustls", + "ipnet", + "js-sys", + "log", + "mime", + "mime_guess", + "once_cell", + "percent-encoding", + "pin-project-lite", + "rustls", + "rustls-pemfile", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "system-configuration", + "tokio", + "tokio-rustls", + "tokio-util", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams", + "web-sys", + "webpki-roots", + "winreg", +] + +[[package]] +name = "ring" +version = "0.17.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb0205304757e5d899b9c2e448b867ffd03ae7f988002e47cd24954391394d0b" +dependencies = [ + "cc", + "getrandom", + "libc", + "spin", + "untrusted", + "windows-sys 0.48.0", +] + +[[package]] +name = "roff" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b833d8d034ea094b1ea68aa6d5c740e0d04bad9d16568d08ba6f76823a114316" + +[[package]] +name = "rstest" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97eeab2f3c0a199bc4be135c36c924b6590b88c377d416494288c14f2db30199" +dependencies = [ + "futures", + "futures-timer", + "rstest_macros", + "rustc_version", +] + +[[package]] +name = "rstest_macros" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d428f8247852f894ee1be110b375111b586d4fa431f6c46e64ba5a0dcccbe605" +dependencies = [ + "cfg-if", + "glob", + "proc-macro2", + "quote", + "regex", + "relative-path", + "rustc_version", + "syn 2.0.48", + "unicode-ident", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" + +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "0.38.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "322394588aaf33c24007e8bb3238ee3e4c5c09c084ab32bc73890b99ff326bca" +dependencies = [ + "bitflags 2.4.1", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustls" +version = "0.21.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "446e14c5cda4f3f30fe71863c34ec70f5ac79d6087097ad0bb433e1be5edf04c" +dependencies = [ + "log", + "ring", + "rustls-webpki", + "sct", +] + +[[package]] +name = "rustls-pemfile" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" +dependencies = [ + "base64", +] + +[[package]] +name = "rustls-webpki" +version = "0.101.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "ryu" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "sct" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "semver" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090" + +[[package]] +name = "serde" +version = "1.0.196" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "870026e60fa08c69f064aa766c10f10b1d62db9ccd4d0abb206472bee0ce3b32" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde-attributes" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6eb8ec7724e4e524b2492b510e66957fe1a2c76c26a6975ec80823f2439da685" +dependencies = [ + "darling_core 0.14.4", + "serde-rename-rule", + "syn 1.0.109", +] + +[[package]] +name = "serde-enum-str" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26416dc95fcd46b0e4b12a3758043a229a6914050aaec2e8191949753ed4e9aa" +dependencies = [ + "darling 0.14.4", + "proc-macro2", + "quote", + "serde-attributes", + "syn 1.0.109", +] + +[[package]] +name = "serde-rename-rule" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "794e44574226fc701e3be5c651feb7939038fc67fb73f6f4dd5c4ba90fd3be70" + +[[package]] +name = "serde_derive" +version = "1.0.196" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "serde_json" +version = "1.0.113" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69801b70b1c3dac963ecb03a364ba0ceda9cf60c71cfe475e99864759c8b8a79" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_with" +version = "3.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b0ed1662c5a68664f45b76d18deb0e234aff37207086803165c961eb695e981" +dependencies = [ + "base64", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.2.2", + "serde", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "568577ff0ef47b879f736cd66740e022f3672788cdf002a05a4e609ea5a6fb15" +dependencies = [ + "darling 0.20.3", + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "serde_yaml" +version = "0.9.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adf8a49373e98a4c5f0ceb5d05aa7c648d75f63774981ed95b7c7443bbd50c6e" +dependencies = [ + "indexmap 2.2.2", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +dependencies = [ + "libc", +] + +[[package]] +name = "simple_logger" +version = "4.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e7e46c8c90251d47d08b28b8a419ffb4aede0f87c2eea95e17d1d5bacbf3ef1" +dependencies = [ + "colored", + "log", + "time", + "windows-sys 0.48.0", +] + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "942b4a808e05215192e39f4ab80813e599068285906cc91aa64f923db842bd5a" + +[[package]] +name = "socket2" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "socket2" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" +dependencies = [ + "libc", + "windows-sys 0.48.0", +] + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + +[[package]] +name = "system-configuration" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "tempfile" +version = "3.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01ce4141aa927a6d1bd34a041795abd0db1cccba5d5f24b009f694bdf3a1f3fa" +dependencies = [ + "cfg-if", + "fastrand", + "redox_syscall", + "rustix", + "windows-sys 0.52.0", +] + +[[package]] +name = "thiserror" +version = "1.0.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "time" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4a34ab300f2dee6e562c10a046fc05e358b29f9bf92277f30c3c8d82275f6f5" +dependencies = [ + "deranged", + "itoa", + "libc", + "num_threads", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" + +[[package]] +name = "time-macros" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ad70d68dba9e1f8aceda7aa6711965dfec1cac869f311a51bd08b3a2ccbce20" +dependencies = [ + "time-core", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f38200e3ef7995e5ef13baec2f432a6da0aa9ac495b2c0e8f3b7eec2c92d653" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "num_cpus", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2 0.5.5", + "tokio-macros", + "windows-sys 0.48.0", +] + +[[package]] +name = "tokio-macros" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "tokio-rustls" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", + "tracing", +] + +[[package]] +name = "tower-service" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" + +[[package]] +name = "tracing" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +dependencies = [ + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +dependencies = [ + "once_cell", +] + +[[package]] +name = "try-lock" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" + +[[package]] +name = "unicase" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" +dependencies = [ + "version_check", +] + +[[package]] +name = "unicode-bidi" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "unicode-normalization" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-width" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" + +[[package]] +name = "unsafe-libyaml" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab4c90930b95a82d00dc9e9ac071b4991924390d46cbd0dfe566148667605e4b" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.48", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c02dbc21516f9f1f04f187958890d7e6026df8d16540b7ad9492bc34a67cea03" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" + +[[package]] +name = "wasm-streams" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b65dc4c90b63b118468cf747d8bf3566c1913ef60be765b5730ead9e0a3ba129" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "web-sys" +version = "0.3.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki-roots" +version = "0.25.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14247bb57be4f377dfb94c72830b8ce8fc6beac03cf4bf7b9732eadd414123fc" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets 0.52.0", +] + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.0", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +dependencies = [ + "windows_aarch64_gnullvm 0.52.0", + "windows_aarch64_msvc 0.52.0", + "windows_i686_gnu 0.52.0", + "windows_i686_msvc 0.52.0", + "windows_x86_64_gnu 0.52.0", + "windows_x86_64_gnullvm 0.52.0", + "windows_x86_64_msvc 0.52.0", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" + +[[package]] +name = "winreg" +version = "0.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..d3de5eb --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,50 @@ +[package] +authors = ["Anton Malinskiy "] +categories = ["command-line-utilities"] +description = "Command-line client for Marathon Cloud" +homepage = "https://github.com/MarathonLabs/marathon-cloud-cli" +license = "MIT" +name = "marathon-cloud" +repository = "https://github.com/MarathonLabs/marathon-cloud-cli" +version = "0.0.1" +edition = "2021" +rust-version = "1.70" +default-run = "marathon-cloud" + +[[bin]] +name = "marathon-cloud-mangen" +path = "src/bin/mangen.rs" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +anyhow = "1.0.79" +clap = { version = "4.4.18", features = ["derive", "env"] } +clap_complete = "4" +log = "0.4.20" +serde = { version = "1.0", features = ["derive"] } +serde-enum-str = "0.4.0" +serde_json = "1.0.113" +serde_yaml = "0.9.31" +serde_with = "3.6.0" +simple_logger = "4.3.3" +tempfile = "3.9.0" +# Reqwest pulls in dependency on openssl which we replace with rustls, hence disabling default features +reqwest = { version = "0.11.23", default-features = false, features = ["json", "multipart", "stream", "rustls-tls"] } +time = { version = "0.3", features = ["serde-well-known"]} +tokio = { version = "1", features = ["full"] } +tokio-util = "0.7" +futures = "0.3" +async-trait = "0.1" +num_cpus = "1" +clap-verbosity-flag = "2.1" +indicatif = "0.17" +console = "0.15" +thiserror = "1.0" +url = "2.5" +async-stream = "0.3" +clap_mangen = "0.2.18" +h2 = "0.3.24" + +[dev-dependencies] +rstest = "0.18.2" diff --git a/Cross.toml b/Cross.toml new file mode 100644 index 0000000..527369b --- /dev/null +++ b/Cross.toml @@ -0,0 +1,4 @@ +[build.env] +passthrough = [ + "OUT_DIR", +] diff --git a/Dockerfile b/Dockerfile index ead27f8..575dcfa 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,16 @@ -ARG VERSION=3.18 -FROM alpine:${VERSION} +ARG RUST_VERSION=1.75.0 +ARG ALPINE_VERSION=3.18 +FROM rust:${RUST_VERSION}-alpine AS build +ARG TARGETARCH +RUN apk update && apk add --no-cache musl-dev +WORKDIR /usr/src/ +COPY . . +RUN if [ "$TARGETARCH" = "amd64" ]; then TARGET="x86_64-unknown-linux-musl"; elif [ "$TARGETARCH" = "arm64" ]; then TARGET="aarch64-unknown-linux-musl"; else echo "$TARGETARCH"; exit 1; fi && \ + cargo install --target=$TARGET --path . && \ + /usr/local/cargo/bin/marathon-cloud --help + +FROM alpine:${ALPINE_VERSION} +ARG TARGETARCH +COPY --from=build /usr/local/cargo/bin/marathon-cloud /usr/local/bin/ ENTRYPOINT ["marathon-cloud"] WORKDIR "/work" -COPY marathon-cloud /usr/local/bin diff --git a/README.md b/README.md index cf3a8ea..a550d98 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,14 @@ # Marathon Cloud command-line interface +![](assets/marathon-cloud-cli.1280.gif) + ## Installation For homebrew users: ```bash brew tap malinskiy/tap brew install malinskiy/tap/marathon-cloud ``` +To have superior experience, [enable autocompletion for Brew](https://docs.brew.sh/Shell-Completion#configuring-completions-in-zsh) For docker users: ```bash @@ -15,35 +18,50 @@ alias marathon-cloud='docker run -v "$(pwd)":/work -it --rm marathonlabs/maratho ## Usage ```bash -Usage of marathon-cloud: - -app string - application filepath. Required - android example: /home/user/workspace/sample.apk - ios example: /home/user/workspace/sample.zip - -testapp string - test apk file path. Required - android example: /home/user/workspace/testSample.apk - ios example: /home/user/workspace/sampleUITests-Runner.zip - -platform string - testing platform. Required - possible values: "Android" or "iOS" - -api-key string - api-key for client. Required - -os-version string - Android or iOS OS version - -link string - link to commit - -name string - name for run, for example it could be description of commit - -o string - allure raw results output folder - -system-image string - OS-specific system image. For Android one of [default,google_apis]. For iOS only [default] - -isolated bool - Run each test using isolated execution. Default is false. - -filter-file string - File containing test filters in YAML format, following the schema described at https://docs.marathonlabs.io/runner/configuration/filtering/#filtering-logic. - For iOS see also https://docs.marathonlabs.io/runner/next/ios#test-plans. - -flavor string - Type of tests to run. Default: [native]. Possible values: [native, js-test-appium, python-robotframework-appium]. +Command-line client for Marathon Cloud + +Usage: marathon-cloud [OPTIONS] [COMMAND] + +Commands: + run Submit a test run + download Download artifacts from a previous test run + completions Output shell completion code for the specified shell (bash, zsh, fish) + help Print this message or the help of the given subcommand(s) + +Options: + -v, --verbose... Increase logging verbosity + -q, --quiet... Decrease logging verbosity + -h, --help Print help + -V, --version Print version +``` + +## Autocompletions +If you're using installation from homebrew then you should have working autocompletions upon installation assuming +you've done the [brew general setup](https://docs.brew.sh/Shell-Completion#configuring-completions-in-zsh). + +If you install the binary manually then you can easily generate autcompletions: + +### bash ``` +# set up autocomplete in bash into the current shell, bash-completion package should be installed first. +source <(marathon-cloud completions bash) +# add autocomplete permanently to your bash shell. +echo "source <(marathon-cloud completions bash)" >> ~/.bashrc +``` + +### zsh +``` +# set up autocomplete in zsh into the current shell +source <(marathon-cloud completions zsh) +# add autocomplete permanently to your zsh shell +echo '[[ $commands[marathon-cloud] ]] && source <(marathon-cloud completions zsh)' >> ~/.zshrc +``` + +### fish +``` +# add marathon-cloud autocompletion permanently to your fish shell +echo 'marathon-cloud completions fish | source' >> ~/.config/fish/config.fish +``` + +## License +marathon-cloud cli codebase is licensed under [MIT](LICENSE). diff --git a/allure/rawAllure.go b/allure/rawAllure.go deleted file mode 100644 index 91f4cb9..0000000 --- a/allure/rawAllure.go +++ /dev/null @@ -1,312 +0,0 @@ -package allure - -import ( - "cli/request" - "encoding/json" - "fmt" - "github.com/otiai10/copy" - "io" - "io/ioutil" - "os" - "path" - "path/filepath" - "strings" - "sync" - "time" -) - -type ArtifactTree struct { - ID string `json:"id"` - IsFile bool `json:"is_file"` - Name string `json:"name"` -} - -type FileNode struct { - ID string `json:"id"` - IsFile bool `json:"is_file"` - Name string `json:"name"` - Downloaded bool `json:"downloaded"` -} - -var maxConcurrentDownloads = 20 // Limit the number of concurrent downloads. - -func GetArtifacts(host string, token string, runId string, whereToSave string) { - fmt.Println("Start downloading artifacts") - - var fileTree []FileNode - var wg sync.WaitGroup - - // Semaphore and error channel - sem := make(chan struct{}, maxConcurrentDownloads) - errors := make(chan error) - - // Error handling goroutine - go func() { - for err := range errors { - if err != nil { - fmt.Println("Error during download:", err) - } - } - }() - - // Step 1: Traverse and store file tree - traverseAndStoreFileTree(host, token, runId, &fileTree, &wg, sem) - wg.Wait() // Wait for file tree traversal to complete - - // Steps 2-3: Check for new files and redownload failed ones - for { - if !downloadFilesAndCheckForNew(host, token, runId, &fileTree, whereToSave, sem, errors, 3) { // Assuming 3 retries - break - } - time.Sleep(time.Duration(10) * time.Second) - } - - close(errors) // Close the error channel after all operations are done - - // Post-processing: move contents from runId folder to root - relocateContents(whereToSave, runId) - // Update Allure json paths - updateJsonPaths(whereToSave) - - fmt.Println("Finish downloading artifacts ") -} - -func traverseAndStoreFileTree(host string, token string, folderID string, fileTree *[]FileNode, wg *sync.WaitGroup, sem chan struct{}) { - sem <- struct{}{} // Acquire semaphore - resp := request.SendGetRequest("https://"+host+"/api/v1/artifact/"+folderID, token) - <-sem // Release semaphore - - if resp == nil || resp.Body == nil { - return - } - defer resp.Body.Close() - - bodyBytes, err := io.ReadAll(resp.Body) - if err != nil { - fmt.Println("Error reading response:", err.Error()) - return - } - - var folders []ArtifactTree - err = json.Unmarshal(bodyBytes, &folders) - if err != nil { - fmt.Println("Failed to unmarshal response:", err.Error()) - return - } - - for _, folder := range folders { - node := FileNode{ - ID: folder.ID, - IsFile: folder.IsFile, - Name: folder.Name, - Downloaded: false, - } - *fileTree = append(*fileTree, node) - - if !folder.IsFile { - wg.Add(1) - go func(fID string) { - defer wg.Done() - traverseAndStoreFileTree(host, token, fID, fileTree, wg, sem) - }(folder.ID) - } - } -} - -func downloadFileWithRetry(host string, token string, fileNode *FileNode, whereToSave string, sem chan struct{}, errors chan<- error, maxRetries int) { - var err error - for i := 0; i < maxRetries; i++ { - sem <- struct{}{} // Acquire semaphore - err = downloadFile(host, token, fileNode.ID, whereToSave) - <-sem // Release semaphore - - if err == nil { - fileNode.Downloaded = true - return - } - - // Exponential backoff - time.Sleep(time.Duration(i) * time.Second) - } - errors <- err // Send error to error channel if all retries fail -} - -func downloadFilesAndCheckForNew(host string, token string, runId string, fileTree *[]FileNode, whereToSave string, sem chan struct{}, errors chan<- error, maxRetries int) bool { - newFilesAdded := false - var notDownloadedCount int - var wg sync.WaitGroup - - // Retry downloading for files that failed in previous attempts - for i := range *fileTree { - if (*fileTree)[i].IsFile && !(*fileTree)[i].Downloaded { - newFilesAdded = true - wg.Add(1) - go func(node *FileNode) { - defer wg.Done() - downloadFileWithRetry(host, token, node, whereToSave, sem, errors, maxRetries) - }(&(*fileTree)[i]) - } - } - wg.Wait() - - // Re-traverse the file tree to check for new files - var newFileTree []FileNode - traverseAndStoreFileTree(host, token, runId, &newFileTree, &wg, sem) - wg.Wait() // Wait for re-traversal to complete - - // Check for new files and add them to the fileTree - for _, newNode := range newFileTree { - found := false - for _, existingNode := range *fileTree { - if newNode.ID == existingNode.ID { - found = true - break - } - } - if !found { - newFilesAdded = true - notDownloadedCount++ - *fileTree = append(*fileTree, newNode) - } - } - - return newFilesAdded -} - -func downloadFile(host string, token string, fileID string, whereToSave string) error { - if fileID == "" { - return fmt.Errorf("empty fileID provided") - } - - // Split the fileID path to figure out the folder structure and file name. - keyArray := strings.Split(fileID, "/") - subFolder := "" - if len(keyArray) > 1 { - subFolder = strings.Join(keyArray[:len(keyArray)-1], "/") - } - fileName := keyArray[len(keyArray)-1] - fileFolder := path.Join(whereToSave, subFolder) - - // Ensure the directory structure exists. - err := os.MkdirAll(fileFolder, os.ModePerm) - if err != nil { - return fmt.Errorf("failed to create directory: %v", err) - } - - // Replace any '#' in the fileID with '%23' for the URL request. This is URL encoding. - validFileID := strings.ReplaceAll(fileID, "#", "%23") - resp := request.SendGetRequest("https://"+host+"/api/v1/artifact?key="+validFileID, token) - defer resp.Body.Close() - - // Create the file at the determined path. - filePath := path.Join(fileFolder, fileName) - out, err := os.Create(filePath) - if err != nil { - return fmt.Errorf("got error while os.Create: %v", err) - } - defer out.Close() - - // Copy the response body (the downloaded data) to our file. - _, err = io.Copy(out, resp.Body) - if err != nil { - return fmt.Errorf("error writing file: %v", err) - } - - return nil -} - -func relocateContents(whereToSave string, runId string) { - runIdDir := filepath.Join(whereToSave, runId) - if _, err := os.Stat(runIdDir); os.IsNotExist(err) { - fmt.Println(runId, "directory does not exist. Skipping relocation.") - return - } - if err := copy.Copy(runIdDir, whereToSave); err != nil { - fmt.Println("Error copying files:", err) - return - } - - // Remove the runId directory - if err := os.RemoveAll(runIdDir); err != nil { - fmt.Println("Error removing directory", runIdDir, ":", err) - } -} - -func updateJsonPaths(whereToSave string) { - // 1. Build the hashmap - fileMap := make(map[string]string) - - err := filepath.Walk(whereToSave, func(path string, info os.FileInfo, err error) error { - if err != nil { - return err - } - - if !info.IsDir() { - filename := filepath.Base(path) - fileMap[filename] = path - } - return nil - }) - - if err != nil { - fmt.Println("Error walking the path", whereToSave, ":", err) - return - } - - // 2. Go through each JSON file and update paths - allureResultsDir := filepath.Join(whereToSave, "report", "allure-results") - files, err := ioutil.ReadDir(allureResultsDir) - if err != nil { - fmt.Println("Error reading directory", allureResultsDir, ":", err) - return - } - - for _, file := range files { - if filepath.Ext(file.Name()) == ".json" { - filePath := filepath.Join(allureResultsDir, file.Name()) - - data, err := ioutil.ReadFile(filePath) - if err != nil { - fmt.Println("Error reading file", filePath, ":", err) - continue - } - - var jsonData map[string]interface{} - if err := json.Unmarshal(data, &jsonData); err != nil { - fmt.Println("Error unmarshaling JSON data from file", filePath, ":", err) - continue - } - - if attachments, ok := jsonData["attachments"].([]interface{}); ok { - for _, attachment := range attachments { - if attachMap, ok := attachment.(map[string]interface{}); ok { - if source, exists := attachMap["source"]; exists { - if sourceStr, ok := source.(string); ok { - filename := filepath.Base(sourceStr) - - if newPath, found := fileMap[filename]; found { - relativePath, err := filepath.Rel(allureResultsDir, newPath) - if err != nil { - fmt.Println("Error calculating relative path for", newPath, ":", err) - continue - } - attachMap["source"] = relativePath - } - } - } - } - } - - updatedData, err := json.MarshalIndent(jsonData, "", " ") - if err != nil { - fmt.Println("Error marshaling JSON data for file", filePath, ":", err) - continue - } - - if err := ioutil.WriteFile(filePath, updatedData, 0644); err != nil { - fmt.Println("Error writing updated data to file", filePath, ":", err) - } - } - } - } -} diff --git a/assets/marathon-cloud-cli.1280.gif b/assets/marathon-cloud-cli.1280.gif new file mode 100644 index 0000000..a133431 Binary files /dev/null and b/assets/marathon-cloud-cli.1280.gif differ diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..2e16af0 --- /dev/null +++ b/build.sh @@ -0,0 +1,41 @@ +#!/usr/bin/env zsh + +set -x + +declare -A BUILDS +BUILDS[darwin_amd64_v1]="x86_64-apple-darwin" +BUILDS[darwin_arm64]="aarch64-apple-darwin" + +BUILDS[windows_arm64]="aarch64-pc-windows-msvc" +BUILDS[windows_amd64_v1]="x86_64-pc-windows-msvc" + +BUILDS[linux_arm64]="aarch64-unknown-linux-gnu" +BUILDS[linux_amd64_v1]="x86_64-unknown-linux-gnu" + +if [ -z "$1" ] || [ -z "$2" ]; then + echo "Specify goeleaser arch name and binary name" + exit 1; +fi + +# The args come from goreleaser. +GO_ARCH=$1 +BIN=$2 +RUST_ARCH=${BUILDS[$GO_ARCH]} +GO_PATH=dist/${BIN}_${GO_ARCH} + +if [ -z "$RUST_ARCH" ]; then + echo "${GO_ARCH} not found in the build map" + exit 1; +fi + +echo "building $GO_ARCH => $RUST_ARCH" + +rm -rf $GO_PATH +rm -rf target + +# Build. +cargo build --release --target=$RUST_ARCH + +# Copy all results to goreleaser dist. +mkdir -p $GO_PATH +cp -R target/$RUST_ARCH/release/${BIN} $GO_PATH diff --git a/config/config.go b/config/config.go deleted file mode 100644 index 5e5efd3..0000000 --- a/config/config.go +++ /dev/null @@ -1,111 +0,0 @@ -package config - -import ( - "errors" - "flag" - "os" - "strings" - - "github.com/spf13/viper" -) - -var config *viper.Viper - -func ReadFlags() error { - config = viper.New() - - CONFIG_HOST := flag.String("host", "app.testwise.pro", "Marathon Cloud API host") - CONFIG_APP := flag.String( - "app", - "", - "application filepath, example: android => /home/user/workspace/sample.apk; iOS => /home/user/workspace/sample.zip. Required") - CONFIG_TEST_APP := flag.String( - "testapp", - "", - "test app filepath, example: android => /home/user/workspace/testSample.apk; iOS => /home/user/workspace/sampleUITests-Runner.zip. Required") - CONFIG_COMMIT_NAME := flag.String("name", "", "Name for run, for example it could be description of commit") - CONFIG_COMMIT_LINK := flag.String("link", "", "Link to commit") - CONFIG_ALLURE_OUTPUT := flag.String("o", "", "Allure raw results output folder") - CONFIG_API_KEY := flag.String("api-key", "", "Api-key for client. Required") - CONFIG_LOGIN := flag.String("e", "", "User email, example: user@domain.com. Deprecated") - CONFIG_PASSWORD := flag.String("p", "", "User password, example: 123456. Deprecated") - CONFIG_PLATFORM := flag.String("platform", "", "Testing platform (Android or iOS only)") - CONFIG_OS_VERSION := flag.String("os-version", "", "Android or iOS OS version") - CONFIG_ISOLATED := flag.String("isolated", "", "Run each test using isolated execution. Default is false.") - CONFIG_SYSTEM_IMAGE := flag.String("system-image", "", "OS-specific system image. For Android one of [default,google_apis]. For iOS only [default]") - CONFIG_FILTER_FILE := flag.String("filter-file", "", "File containing test filters in YAML format, following the schema described at https://docs.marathonlabs.io/runner/configuration/filtering/#filtering-logic. For iOS see also https://docs.marathonlabs.io/runner/next/ios#test-plans.") - CONFIG_FLAVOR := flag.String("flavor", "", "Type of tests to run. Default: [native]. Possible values: [native, js-test-appium, python-robotframework-appium]") - - args := os.Args - if len(args) > 1 && args[1] == "help" { - args[1] = "-help" - } - - flag.Parse() - - config.Set("HOST", *CONFIG_HOST) - - // app - if len(*CONFIG_APP) > 0 { - config.Set("APP", *CONFIG_APP) - } else { - return errors.New("app filepath must be specified") - } - - // test app - if len(*CONFIG_TEST_APP) > 0 { - config.Set("TEST_APP", *CONFIG_TEST_APP) - } else { - return errors.New("testapp filepath must be specified") - } - - // configPlatformLowerCase - if *CONFIG_PLATFORM == "" { - return errors.New("platform must be specified") - } - configPlatformLowerCase := strings.ToLower(*CONFIG_PLATFORM) - var platform string - if configPlatformLowerCase == "android" { - platform = "Android" - } else if configPlatformLowerCase == "ios" { - platform = "iOS" - } else { - return errors.New("platform must be 'Android' or 'iOS'") - } - config.Set("PLATFORM", platform) - - // login & password - if len(*CONFIG_LOGIN) > 0 { - config.Set("LOGIN", *CONFIG_LOGIN) - } - if len(*CONFIG_PASSWORD) > 0 { - config.Set("PASSWORD", *CONFIG_PASSWORD) - } - - // api key - if len(*CONFIG_API_KEY) > 0 { - config.Set("API_KEY", *CONFIG_API_KEY) - } - - if len(*CONFIG_ISOLATED) > 0 { - config.Set("ISOLATED", *CONFIG_ISOLATED) - } - - if len(*CONFIG_API_KEY) == 0 && (len(*CONFIG_LOGIN) == 0 || len(*CONFIG_PASSWORD) == 0) { - return errors.New("api-key or login with password must be specified") - } - - config.Set("NAME", *CONFIG_COMMIT_NAME) - config.Set("LINK", *CONFIG_COMMIT_LINK) - config.Set("ALLURE_OUTPUT", *CONFIG_ALLURE_OUTPUT) - config.Set("OS_VERSION", *CONFIG_OS_VERSION) - config.Set("SYSTEM_IMAGE", *CONFIG_SYSTEM_IMAGE) - config.Set("FILTER_FILE", *CONFIG_FILTER_FILE) - config.Set("FLAVOR", *CONFIG_FLAVOR) - - return nil -} - -func GetConfig() *viper.Viper { - return config -} diff --git a/filter/filter.go b/filter/filter.go deleted file mode 100644 index f26f47d..0000000 --- a/filter/filter.go +++ /dev/null @@ -1,113 +0,0 @@ -package filter - -import ( - "encoding/json" - "errors" - "io/ioutil" - - "gopkg.in/yaml.v2" -) - -type Configuration struct { - FilteringConfig FilteringConfiguration `json:"filteringConfiguration,omitempty" yaml:"filteringConfiguration"` -} - -type FilteringConfiguration struct { - Allowlist *[]Filter `json:"allowlist,omitempty" yaml:"allowlist"` - Blocklist *[]Filter `json:"blocklist,omitempty" yaml:"blocklist"` -} - -type Filter struct { - Type string `json:"type,omitempty" yaml:"type"` - Regex string `json:"regex,omitempty" yaml:"regex,omitempty"` - Values []string `json:"values,omitempty" yaml:"values,omitempty"` - Filters []Filter `json:"filters,omitempty" yaml:"filters,omitempty"` - Op string `json:"op,omitempty" yaml:"op,omitempty"` - File string `json:"file,omitempty" yaml:"file,omitempty"` -} - -func ValidateYAMLAndConvertToJSON(filePath string) (string, error) { - yamlData, err := ioutil.ReadFile(filePath) - if err != nil { - return "", err - } - - var config Configuration - err = yaml.Unmarshal(yamlData, &config) - if err != nil { - return "", err - } - - if err := validateFilters(config.FilteringConfig.Allowlist); err != nil { - return "", err - } - if err := validateFilters(config.FilteringConfig.Blocklist); err != nil { - return "", err - } - - jsonOutput, err := json.Marshal(config) - if err != nil { - return "", err - } - - return string(jsonOutput), nil -} - -func validateFilters(filters *[]Filter) error { - if filters == nil { - return nil - } - - validTypes := map[string]bool{ - "fully-qualified-class-name": true, - "fully-qualified-test-name": true, - "simple-class-name": true, - "package": true, - "method": true, - "annotation": true, - "allure": true, - "composition": true, - } - - for _, filter := range *filters { - if filter.Type == "fragmentation" { - return errors.New("the 'Fragmented execution of tests' feature (type: 'fragmentation') is not supported in the cloud") - } - if !validTypes[filter.Type] { - return errors.New("invalid filter type: " + filter.Type) - } - if filter.Type == "composition" { - if filter.Op == "" || len(filter.Filters) == 0 { - return errors.New("composition type must have 'op' and 'filters' fields initialized") - } - if err := validateFilters(&filter.Filters); err != nil { - return err - } - } else { - if err := validateNonCompositionFilter(filter); err != nil { - return err - } - } - } - return nil -} - -func validateNonCompositionFilter(filter Filter) error { - fieldsInitialized := 0 - if filter.Regex != "" { - fieldsInitialized++ - } - if len(filter.Values) > 0 { - fieldsInitialized++ - } - if filter.File != "" { - return errors.New("the 'file' field is not supported. Please include all values directly in the YAML") - } - if fieldsInitialized > 1 { - return errors.New("only one of [regex, values] can be specified for type: " + filter.Type) - } - if fieldsInitialized == 0 { - return errors.New("at least one of [regex, values] should be specified for type: " + filter.Type) - } - return nil -} diff --git a/filter/filter_test.go b/filter/filter_test.go deleted file mode 100644 index 783df13..0000000 --- a/filter/filter_test.go +++ /dev/null @@ -1,122 +0,0 @@ -package filter - -import ( - "encoding/json" - "io/ioutil" - "reflect" - "testing" - "strings" - - "gopkg.in/yaml.v2" -) - -func TestValidateYAMLAndConvertToJSONFragmentation(t *testing.T) { - _, err := ValidateYAMLAndConvertToJSON("./testdata/fragmentation.yaml") - if err == nil || err.Error() != "the 'Fragmented execution of tests' feature (type: 'fragmentation') is not supported in the cloud" { - t.Errorf("ValidateYAMLAndConvertToJSON failed to detect fragmentation feature") - } -} - -func TestValidateYAMLAndConvertToJSONFileType(t *testing.T) { - _, err := ValidateYAMLAndConvertToJSON("./testdata/filetype.yaml") - if err == nil || err.Error() != "the 'file' field is not supported. Please include all values directly in the YAML" { - t.Errorf("ValidateYAMLAndConvertToJSON failed to detect file filter parameter") - } -} - -func TestValidateYAMLAndConvertToJSONInvalidYAML(t *testing.T) { - _, err := ValidateYAMLAndConvertToJSON("./testdata/invalid.yaml") - if err == nil { - t.Errorf("ValidateYAMLAndConvertToJSON should have failed for invalid YAML format") - } -} - -func TestValidateYAMLAndConvertToJSONGrammarError(t *testing.T) { - _, err := ValidateYAMLAndConvertToJSON("./testdata/grammarError.yaml") - if err == nil { - t.Errorf("ValidateYAMLAndConvertToJSON should have failed for grammar errors in YAML") - } -} - -func TestValidateYAMLAndConvertToJSONUnknownType(t *testing.T) { - _, err := ValidateYAMLAndConvertToJSON("./testdata/unknownType.yaml") - if err == nil || !strings.Contains(err.Error(), "invalid filter type") { - t.Errorf("ValidateYAMLAndConvertToJSON should have failed for unknown filter type") - } -} - -func TestValidateYAMLAndConvertToJSONCorrectTypeNoFields(t *testing.T) { - _, err := ValidateYAMLAndConvertToJSON("./testdata/correctTypeNoFields.yaml") - if err == nil { - t.Errorf("ValidateYAMLAndConvertToJSON should have failed for correct type with no additional fields") - } -} - -func TestValidateYAMLAndConvertToJSONCorrectTypeTwoFields(t *testing.T) { - _, err := ValidateYAMLAndConvertToJSON("./testdata/correctTypeTwoFields.yaml") - if err == nil || !strings.Contains(err.Error(), "only one of [regex, values] can be specified") { - t.Errorf("ValidateYAMLAndConvertToJSON should have failed for correct type with two fields") - } -} - -func TestValidateYAMLAndConvertToJSONInvalidCompositionFields(t *testing.T) { - _, err := ValidateYAMLAndConvertToJSON("./testdata/invalidCompositionFields.yaml") - if err == nil || !strings.Contains(err.Error(), "composition type must have 'op' and 'filters' fields initialized") { - t.Errorf("ValidateYAMLAndConvertToJSON should have failed for invalid composition fields") - } -} - -func TestValidateYAMLAndConvertToJSONValid(t *testing.T) { - testValidateYAMLAndConvertToJSON(t, "./testdata/valid.yaml") -} - -func TestValidateYAMLAndConvertToJSONValidComplex(t *testing.T) { - testValidateYAMLAndConvertToJSON(t, "./testdata/validComplex.yaml") -} - -func testValidateYAMLAndConvertToJSON(t *testing.T, filePath string) { - jsonOutput, err := ValidateYAMLAndConvertToJSON(filePath) - if err != nil { - t.Errorf("ValidateYAMLAndConvertToJSON failed for %s: %v", filePath, err) - return - } - - if jsonOutput == "" { - t.Errorf("JSON output is empty for %s", filePath) - return - } - - // Convert JSON output directly back to YAML - var jsonData interface{} - err = json.Unmarshal([]byte(jsonOutput), &jsonData) - if err != nil { - t.Errorf("Failed to unmarshal JSON for %s: %v", filePath, err) - return - } - - yamlOutput, err := yaml.Marshal(jsonData) - if err != nil { - t.Errorf("Failed to marshal back to YAML for %s: %v", filePath, err) - return - } - - // Read the original YAML file for comparison - originalYaml, err := ioutil.ReadFile(filePath) - if err != nil { - t.Errorf("Failed to read original YAML file %s: %v", filePath, err) - return - } - - // Unmarshal both YAMLs into interfaces for structural comparison - var originalData, roundTripData interface{} - errOriginal := yaml.Unmarshal(originalYaml, &originalData) - errRoundTrip := yaml.Unmarshal(yamlOutput, &roundTripData) - if errOriginal != nil || errRoundTrip != nil { - t.Errorf("Failed to unmarshal YAMLs for comparison: %v, %v", errOriginal, errRoundTrip) - return - } - - if !reflect.DeepEqual(originalData, roundTripData) { - t.Errorf("Round-trip YAML does not match original for %s", filePath) - } -} diff --git a/filter/testdata/correctTypeNoFields.yaml b/fixture/filtering/correctTypeNoFields.yaml similarity index 100% rename from filter/testdata/correctTypeNoFields.yaml rename to fixture/filtering/correctTypeNoFields.yaml diff --git a/filter/testdata/correctTypeTwoFields.yaml b/fixture/filtering/correctTypeTwoFields.yaml similarity index 100% rename from filter/testdata/correctTypeTwoFields.yaml rename to fixture/filtering/correctTypeTwoFields.yaml diff --git a/filter/testdata/filetype.yaml b/fixture/filtering/filetype.yaml similarity index 99% rename from filter/testdata/filetype.yaml rename to fixture/filtering/filetype.yaml index 6d812ac..c01daeb 100644 --- a/filter/testdata/filetype.yaml +++ b/fixture/filtering/filetype.yaml @@ -2,4 +2,3 @@ filteringConfiguration: allowlist: - type: "fully-qualified-test-name" file: "tests.txt" - diff --git a/filter/testdata/fragmentation.yaml b/fixture/filtering/fragmentation.yaml similarity index 100% rename from filter/testdata/fragmentation.yaml rename to fixture/filtering/fragmentation.yaml diff --git a/filter/testdata/grammarError.yaml b/fixture/filtering/grammarError.yaml similarity index 100% rename from filter/testdata/grammarError.yaml rename to fixture/filtering/grammarError.yaml diff --git a/filter/testdata/invalid.yaml b/fixture/filtering/invalid.yaml similarity index 100% rename from filter/testdata/invalid.yaml rename to fixture/filtering/invalid.yaml diff --git a/filter/testdata/invalidCompositionFields.yaml b/fixture/filtering/invalidCompositionFields.yaml similarity index 100% rename from filter/testdata/invalidCompositionFields.yaml rename to fixture/filtering/invalidCompositionFields.yaml diff --git a/fixture/filtering/tests.txt b/fixture/filtering/tests.txt new file mode 100644 index 0000000..bd89f12 --- /dev/null +++ b/fixture/filtering/tests.txt @@ -0,0 +1,2 @@ +com.malinskiy.adam.SimpleTest#test1 +com.malinskiy.adam.SimpleTest#test2 diff --git a/filter/testdata/unknownType.yaml b/fixture/filtering/unknownType.yaml similarity index 100% rename from filter/testdata/unknownType.yaml rename to fixture/filtering/unknownType.yaml diff --git a/filter/testdata/valid.yaml b/fixture/filtering/valid.yaml similarity index 100% rename from filter/testdata/valid.yaml rename to fixture/filtering/valid.yaml diff --git a/filter/testdata/validComplex.yaml b/fixture/filtering/validComplex.yaml similarity index 100% rename from filter/testdata/validComplex.yaml rename to fixture/filtering/validComplex.yaml diff --git a/go.mod b/go.mod deleted file mode 100644 index d1f8955..0000000 --- a/go.mod +++ /dev/null @@ -1,29 +0,0 @@ -module cli - -go 1.18 - -require ( - github.com/gorilla/websocket v1.5.0 - github.com/spf13/viper v1.13.0 - gopkg.in/guregu/null.v4 v4.0.0 -) - -require ( - github.com/fsnotify/fsnotify v1.5.4 // indirect - github.com/hashicorp/hcl v1.0.0 // indirect - github.com/magiconair/properties v1.8.6 // indirect - github.com/mitchellh/mapstructure v1.5.0 // indirect - github.com/otiai10/copy v1.12.0 // indirect - github.com/pelletier/go-toml v1.9.5 // indirect - github.com/pelletier/go-toml/v2 v2.0.5 // indirect - github.com/spf13/afero v1.8.2 // indirect - github.com/spf13/cast v1.5.0 // indirect - github.com/spf13/jwalterweatherman v1.1.0 // indirect - github.com/spf13/pflag v1.0.5 // indirect - github.com/subosito/gotenv v1.4.1 // indirect - golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect - golang.org/x/text v0.3.7 // indirect - gopkg.in/ini.v1 v1.67.0 // indirect - gopkg.in/yaml.v2 v2.4.0 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect -) diff --git a/go.sum b/go.sum deleted file mode 100644 index ecba911..0000000 --- a/go.sum +++ /dev/null @@ -1,485 +0,0 @@ -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= -cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= -cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= -cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= -cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= -cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= -cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= -cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= -cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= -cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= -cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= -cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= -cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= -cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= -cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= -cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= -cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= -cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= -cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= -cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= -cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= -cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= -cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= -cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= -cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= -cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= -cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= -cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= -cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= -cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= -cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= -cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= -cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= -dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= -github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= -github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= -github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= -github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= -github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= -github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= -github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= -github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= -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.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= -github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= -github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= -github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= -github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= -github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= -github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= -github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= -github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= -github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= -github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= -github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -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/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= -github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= -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/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo= -github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= -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/otiai10/copy v1.12.0 h1:cLMgSQnXBs1eehF0Wy/FAGsgDTDmAqFR7rQylBb1nDY= -github.com/otiai10/copy v1.12.0/go.mod h1:rSaLseMUsZFFbsFGc7wCJnnkTAvdc5L6VWxPE4308Ww= -github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= -github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= -github.com/pelletier/go-toml/v2 v2.0.5 h1:ipoSadvV8oGUjnUbMub59IDPPwfxF694nG/jwbMiyQg= -github.com/pelletier/go-toml/v2 v2.0.5/go.mod h1:OMHamSCAODeSsVrwwvcJOaoN0LIUIaFVNZzmWyNfXas= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k= -github.com/spf13/afero v1.8.2 h1:xehSyVa0YnHWsJ49JFljMpg1HX19V6NDZ1fkm1Xznbo= -github.com/spf13/afero v1.8.2/go.mod h1:CtAatgMJh6bJEIs48Ay/FOnkljP3WeGUG0MC1RfAqwo= -github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= -github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= -github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= -github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= -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.13.0 h1:BWSJ/M+f+3nmdz9bxB+bWX28kkALN2ok11D0rSo8EJU= -github.com/spf13/viper v1.13.0/go.mod h1:Icm2xNL3/8uyh/wFuB1jI7TiTNKp8632Nwegu+zgdYw= -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/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs= -github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= -github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= -go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= -go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= -golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= -golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= -golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= -golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= -golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= -golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= -golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= -golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= -golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= -golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= -golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -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.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/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-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/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= -golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/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-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/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-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/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-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/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-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ= -golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/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= -golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= -golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= -golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= -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/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= -google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= -google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= -google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= -google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= -google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= -google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= -google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= -google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= -google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= -google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= -google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= -google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= -google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= -google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= -google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= -google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= -google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= -google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= -google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= -google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= -google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= -google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/guregu/null.v4 v4.0.0 h1:1Wm3S1WEA2I26Kq+6vcW+w0gcDo44YKYD7YIEJNHDjg= -gopkg.in/guregu/null.v4 v4.0.0/go.mod h1:YoQhUrADuG3i9WqesrCmpNRwm1ypAgSHYqoOcTu/JrI= -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/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= -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= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= -rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= -rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/main.go b/main.go deleted file mode 100644 index 3348297..0000000 --- a/main.go +++ /dev/null @@ -1,96 +0,0 @@ -package main - -import ( - "cli/allure" - "cli/config" - "cli/request" - "cli/filter" - "fmt" - "os" - "time" -) - -func main() { - err := config.ReadFlags() - if err != nil { - fmt.Println("Error reading flags:\n", err.Error()) - os.Exit(7) - } - - conf := config.GetConfig() - host := conf.GetString("HOST") - login := conf.GetString("LOGIN") - password := conf.GetString("PASSWORD") - apiKey := conf.GetString("API_KEY") - app := conf.GetString("APP") - testApp := conf.GetString("TEST_APP") - commitName := conf.GetString("NAME") - commitLink := conf.GetString("LINK") - allureOutput := conf.GetString("ALLURE_OUTPUT") - platform := conf.GetString("PLATFORM") - osVersion := conf.GetString("OS_VERSION") - isolated := conf.GetString("ISOLATED") - systemImage := conf.GetString("SYSTEM_IMAGE") - filterFile := conf.GetString("FILTER_FILE") - flavor := conf.GetString("FLAVOR") - - var filteringConfigJson = "" - if len(filterFile) != 0 { - filteringConfigJson, err = filter.ValidateYAMLAndConvertToJSON(filterFile) - if err != nil { - fmt.Printf("Error happened attempting to read %s\n", filterFile) - fmt.Println(err.Error()) - os.Exit(8) - } - } - - if len(apiKey) == 0 { - token, err := request.Authorize(host, login, password) - if err != nil { - fmt.Println("Can't login: ", err.Error()) - os.Exit(6) - } - fmt.Println(time.Now().Format(time.Stamp), "Creating new run") - runId, err := request.SendNewRun(host, token, app, testApp, commitName, commitLink, platform) - if err != nil { - fmt.Println(err.Error()) - os.Exit(5) - } - go request.Subscribe(token, runId) - - state, err := request.WaitRunForEnd(host, runId, token) - if len(allureOutput) > 0 { - allure.GetArtifacts(host, token, runId, allureOutput) - } - if err != nil { - fmt.Println(err.Error()) - os.Exit(4) - } - if state != "passed" { - os.Exit(3) - } - } else { - jwtToken, err := request.RequestJwtToken(host, apiKey) - if err != nil { - fmt.Println(err) - return - } - runId, err := request.SendNewRunWithKey(host, apiKey, app, testApp, commitName, commitLink, platform, osVersion, systemImage, isolated, filteringConfigJson, flavor) - if err != nil { - fmt.Println(err.Error()) - os.Exit(5) - } - go request.Subscribe(jwtToken, runId) - state, err := request.WaitRunForEndWithApiKey(host, runId, apiKey) - if len(allureOutput) > 0 { - allure.GetArtifacts(host, jwtToken, runId, allureOutput) - } - if err != nil { - fmt.Println(err.Error()) - os.Exit(4) - } - if state != "passed" { - os.Exit(3) - } - } -} diff --git a/request/requests.go b/request/requests.go deleted file mode 100644 index c1d43be..0000000 --- a/request/requests.go +++ /dev/null @@ -1,341 +0,0 @@ -package request - -import ( - "bytes" - "errors" - "fmt" - "io" - "mime/multipart" - "net/http" - "os" - "path/filepath" - "strconv" - "time" - - "encoding/json" - - "gopkg.in/guregu/null.v4" -) - -type Login struct { - Email string `json:"email"` - Password string `json:"password"` -} - -type LoginResponse struct { - Token string `json:"token"` -} - -func Authorize(host string, login string, password string) (string, error) { - authBody := Login{Email: login, Password: password} - - reqBody, err := json.Marshal(authBody) - - resp := sendPostRequest("https://"+host+"/api/v1/cli/auth", &reqBody) - if err != nil { - fmt.Println("Error while creating auth json: ", err.Error()) - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK { - return "", errors.New("Can't authorize") - } - bodyBytes, err := io.ReadAll(resp.Body) - if err != nil { - return "", err - } - var respData LoginResponse - err = json.Unmarshal(bodyBytes, &respData) - - return respData.Token, nil -} - -func sendPostRequest(url string, reqBody *[]byte) *http.Response { - - bodyReader := bytes.NewReader(*reqBody) - - req, err := http.NewRequest(http.MethodPost, url, bodyReader) - if err != nil { - fmt.Println("Error :", err.Error()) - return nil - } - req.Header.Set("Content-Type", "devlication/json") - - client := &http.Client{} - res, err := client.Do(req) - if err != nil { - fmt.Println("Error :", err.Error()) - return nil - } - return res -} - -func SendGetRequest(url string, token string) *http.Response { - req, err := http.NewRequest(http.MethodGet, url, nil) - req.Header.Set("Authorization", "Bearer "+token) - client := &http.Client{} - res, err := client.Do(req) - if err != nil { - fmt.Println("Error :", err.Error()) - return nil - } - return res - -} - -//{"run_id":"0dfe9125-dad5-42c9-b642-5599530caa79","status":"ok"} - -type CreateRunResponse struct { - RunID string `json:"run_id"` - Status string `json:"status"` -} - -func SendNewRunWithKey(host string, apiKey string, appPath string, testAppPath string, commitName string, commitLink string, platform string, osVersion string, systemImage string, isolated string, filteringConfigJson string, flavor string) (string, error) { - body := &bytes.Buffer{} - writer := multipart.NewWriter(body) - - fmt.Println("Application file uploading...") - appFile, err := os.Open(appPath) - if err != nil { - fmt.Println("Can't read apk file") - return "", err - } - defer appFile.Close() - part, _ := writer.CreateFormFile("app", filepath.Base(appFile.Name())) - io.Copy(part, appFile) - fmt.Println("Application file uploading done") - - fmt.Println("Test Application file uploading...") - testAppFile, err := os.Open(testAppPath) - if err != nil { - fmt.Println("Can't read testapk file") - return "", err - } - defer testAppFile.Close() - part2, _ := writer.CreateFormFile("testapp", filepath.Base(testAppFile.Name())) - io.Copy(part2, testAppFile) - fmt.Println("Test Application file uploading done") - - writer.WriteField("platform", platform) - if len(commitName) > 0 { - writer.WriteField("name", commitName) - } - if len(commitLink) > 0 { - writer.WriteField("link", commitLink) - } - if len(osVersion) > 0 { - writer.WriteField("osversion", osVersion) - } - if isolated == "true" || isolated == "false" { - writer.WriteField("isolated", isolated) - } - if len(systemImage) > 0 { - writer.WriteField("system_image", systemImage) - } - if len(filteringConfigJson) > 0 { - writer.WriteField("filtering_configuration", filteringConfigJson) - } - if len(flavor) > 0 { - writer.WriteField("flavor", flavor) - } - - writer.Close() - - r, err := http.NewRequest("POST", "https://"+host+"/api/v1/run?api_key="+apiKey, body) - if err != nil { - fmt.Println(err) - } - r.Header.Add("Content-Type", writer.FormDataContentType()) - client := &http.Client{} - - fmt.Println("Making request to start the test run...") - resp, err := client.Do(r) - if err != nil { - fmt.Println(err) - return "", err - } - if resp.StatusCode != 200 { - err = fmt.Errorf("Received error with status code = %d", resp.StatusCode) - return "", err - } - - bodyBytes, err := io.ReadAll(resp.Body) - if err != nil { - fmt.Println(err) - return "", err - } - var respData CreateRunResponse - err = json.Unmarshal(bodyBytes, &respData) - if err != nil { - fmt.Println(err) - } - - fmt.Println("The test run was started. RunID=" + respData.RunID) - return respData.RunID, nil -} - -// deprecate in October 2023 -func SendNewRun(host string, token string, appPath string, testAppPath string, commitName string, commitLink string, platform string) (string, error) { - appFile, err := os.Open(appPath) - if err != nil { - fmt.Println("Can't read app file") - return "", err - } - defer appFile.Close() - testAppFile, err := os.Open(testAppPath) - if err != nil { - fmt.Println("Can't read testapp file") - return "", err - } - defer testAppFile.Close() - - body := &bytes.Buffer{} - writer := multipart.NewWriter(body) - - part, _ := writer.CreateFormFile("app", filepath.Base(appFile.Name())) - io.Copy(part, appFile) - - part2, _ := writer.CreateFormFile("testapp", filepath.Base(testAppFile.Name())) - io.Copy(part2, testAppFile) - - writer.WriteField("platform", platform) - if len(commitName) > 0 { - writer.WriteField("name", commitName) - } - if len(commitLink) > 0 { - writer.WriteField("link", commitLink) - } - - writer.Close() - - r, _ := http.NewRequest("POST", "https://"+host+"/api/v1/run", body) - r.Header.Add("Content-Type", writer.FormDataContentType()) - r.Header.Add("Authorization", "Bearer "+token) - client := &http.Client{} - resp, _ := client.Do(r) - bodyBytes, err := io.ReadAll(resp.Body) - if err != nil { - return "", err - } - var respData CreateRunResponse - err = json.Unmarshal(bodyBytes, &respData) - - return respData.RunID, nil -} - -type RunStats struct { - ID string `json:"id"` - Name null.String `json:"name"` - Link null.String `json:"link"` - State string `json:"state"` - Completed null.Time `json:"completed,omitempty"` - Ignored null.Int `json:"ignored"` - Passed null.Int `json:"passed"` - Failed null.Int `json:"failed"` - TotalRunTime float32 `json:"total_run_time"` - TestDoneAt null.Time `json:"tests_done"` - CreatedAt time.Time `json:"created"` - UpdatedAt time.Time `json:"updated"` -} - -// Deprecate after October 2023 -func WaitRunForEnd(host string, runId string, token string) (string, error) { - var respData RunStats - for { - client := &http.Client{} - req, err := http.NewRequest("GET", "https://"+host+"/api/v1/run/"+runId, nil) - if err != nil { - return "", err - } - req.Header.Add("Authorization", "Bearer "+token) - resp, err := client.Do(req) - if err != nil { - return "", err - } - bodyBytes, err := io.ReadAll(resp.Body) - if err != nil { - return "", err - } - - err = json.Unmarshal(bodyBytes, &respData) - if respData.Completed.Valid == true { - break - } - time.Sleep(5 * time.Second) - } - fmt.Println("Allure report - https://cloud.marathonlabs.io/api/v1/report/" + respData.ID) - fmt.Println("Passed - " + strconv.Itoa(int(respData.Passed.Int64))) - fmt.Println("Failed - " + strconv.Itoa(int(respData.Failed.Int64))) - fmt.Println("Ignored - " + strconv.Itoa(int(respData.Ignored.Int64))) - return respData.State, nil -} - -func WaitRunForEndWithApiKey(host string, runId string, apiKey string) (string, error) { - fmt.Println("Waiting for the test run finish...") - var respData RunStats - for { - client := &http.Client{} - req, err := http.NewRequest("GET", "https://"+host+"/api/v1/run/"+runId+"?api_key="+apiKey, nil) - if err != nil { - return "", err - } - resp, err := client.Do(req) - if err != nil { - return "", err - } - if resp.StatusCode != 200 { - fmt.Println(fmt.Sprintf("Status code = %d. Maybe it is a critical error", resp.StatusCode)) - continue - } - - bodyBytes, err := io.ReadAll(resp.Body) - if err != nil { - return "", err - } - - err = json.Unmarshal(bodyBytes, &respData) - if respData.Completed.Valid == true { - break - } - time.Sleep(5 * time.Second) - } - fmt.Println("Allure report - https://cloud.marathonlabs.io/api/v1/report/" + respData.ID) - fmt.Println("Passed - " + strconv.Itoa(int(respData.Passed.Int64))) - fmt.Println("Failed - " + strconv.Itoa(int(respData.Failed.Int64))) - fmt.Println("Ignored - " + strconv.Itoa(int(respData.Ignored.Int64))) - return respData.State, nil -} - -type TokenResponse struct { - Token string `json:"token"` -} - -func RequestJwtToken(host string, apiKey string) (string, error) { - fmt.Println("Token is requesting...") - var tokenObj TokenResponse - client := &http.Client{} - req, err := http.NewRequest("GET", "https://"+host+"/api/v1/user/jwt?api_key="+apiKey, nil) - if err != nil { - return "", err - } - resp, err := client.Do(req) - if err != nil { - return "", err - } - if resp.StatusCode != 200 { - err = fmt.Errorf("Received error with status code = %d", resp.StatusCode) - return "", err - } - - bodyBytes, err := io.ReadAll(resp.Body) - if err != nil { - return "", err - } - - err = json.Unmarshal(bodyBytes, &tokenObj) - if err != nil { - return "", err - } - fmt.Println("Token was received") - return tokenObj.Token, nil -} diff --git a/request/websocket.go b/request/websocket.go deleted file mode 100644 index 71052e4..0000000 --- a/request/websocket.go +++ /dev/null @@ -1,66 +0,0 @@ -package request - -import ( - "encoding/json" - "fmt" - "log" - "net/url" - "os" - "os/signal" - "time" - - "github.com/gorilla/websocket" -) - -type RuntimeState struct { - TotalEmulators int `json:"total_emulators"` - WorkingEmulators int `json:"working_emulators"` - State string `json:"state"` - Percents int `json:"percents"` - TestName string `json:"test_name"` - TestState string `json:"test_state"` -} - -func Subscribe(token string, runId string) { - - interrupt := make(chan os.Signal, 1) - signal.Notify(interrupt, os.Interrupt) - - u := url.URL{Scheme: "ws", Host: "runtime.testwise.pro:1005", Path: "/hello", RawQuery: "token=" + token + "&run_id=" + runId} - - c, _, err := websocket.DefaultDialer.Dial(u.String(), nil) - if err != nil { - fmt.Println("Not blocking Dial error. Ignore it now. The error:", err) - return - } - defer c.Close() - - done := make(chan struct{}) - - func() { - defer close(done) - for { - _, data, err := c.ReadMessage() - if err != nil { - log.Println("read:", err) - return - } - var message RuntimeState - err = json.Unmarshal(data, &message) - if err != nil { - fmt.Println("Error reading runtime") - continue - } - if len(message.State) > 0 { - fmt.Println(time.Now().Format(time.Stamp), message.State) - continue - } - fmt.Printf("%s Running %d%% done\n", time.Now().Format(time.Stamp), message.Percents) - if len(message.TestName) > 0 { - fmt.Printf("%s %s %s \n", time.Now().Format(time.Stamp), message.TestName, message.TestState) - } - - } - }() - -} diff --git a/src/api.rs b/src/api.rs new file mode 100644 index 0000000..518b2f1 --- /dev/null +++ b/src/api.rs @@ -0,0 +1,399 @@ +use std::{ + cmp::min, + path::{Path, PathBuf}, + time::Duration, +}; + +use anyhow::Result; +use async_trait::async_trait; +use futures::StreamExt; +use indicatif::{MultiProgress, ProgressBar, ProgressStyle}; +use reqwest::{multipart::Part, Body, Client, StatusCode}; +use serde::Deserialize; +use time::OffsetDateTime; +use tokio::{ + fs::{create_dir_all, File}, + io, +}; +use tokio_util::io::ReaderStream; + +use crate::{ + errors::{ApiError, InputError}, + filtering::SparseMarathonfile, +}; + +#[async_trait] +pub trait RapiClient { + async fn get_token(&self) -> Result; + async fn create_run( + &self, + app: Option, + test_app: PathBuf, + name: Option, + link: Option, + platform: String, + os_version: Option, + system_image: Option, + isolated: Option, + filtering_configuration: Option, + progress: bool, + ) -> Result; + async fn get_run(&self, id: &str) -> Result; + + async fn list_artifact(&self, jwt_token: &str, id: &str) -> Result>; + async fn download_artifact( + &self, + jwt_token: &str, + artifact: Artifact, + base_path: PathBuf, + ) -> Result<()>; +} + +#[derive(Clone)] +pub struct RapiReqwestClient { + base_url: String, + api_key: String, + client: Client, +} + +impl RapiReqwestClient { + pub fn new(base_url: &str, api_key: &str) -> RapiReqwestClient { + let non_sanitized = base_url.to_string(); + RapiReqwestClient { + base_url: non_sanitized + .strip_suffix('/') + .unwrap_or(&non_sanitized) + .to_string(), + api_key: api_key.to_string(), + ..Default::default() + } + } +} + +impl Default for RapiReqwestClient { + fn default() -> Self { + Self { + base_url: String::from("https:://cloud.marathonlabs.io/api/v1"), + api_key: "".into(), + client: Client::default(), + } + } +} + +#[async_trait] +impl RapiClient for RapiReqwestClient { + async fn get_token(&self) -> Result { + let url = format!("{}/user/jwt", self.base_url); + let params = [("api_key", self.api_key.clone())]; + let url = reqwest::Url::parse_with_params(&url, ¶ms) + .map_err(|error| ApiError::InvalidParameters { error })?; + let response = self + .client + .get(url) + .send() + .await + .map_err(api_error_adapter)? + .json::() + .await + .map_err(|error| ApiError::DeserializationFailure { error })?; + Ok(response.token) + } + + async fn create_run( + &self, + app: Option, + test_app: PathBuf, + name: Option, + link: Option, + platform: String, + os_version: Option, + system_image: Option, + isolated: Option, + filtering_configuration: Option, + progress: bool, + ) -> Result { + let url = format!("{}/run", self.base_url); + let params = [("api_key", self.api_key.clone())]; + let url = reqwest::Url::parse_with_params(&url, ¶ms) + .map_err(|error| ApiError::InvalidParameters { error })?; + + let mut form = reqwest::multipart::Form::new().text("platform", platform); + + let file = File::open(&test_app) + .await + .map_err(|error| InputError::OpenFileFailure { + path: test_app.clone(), + error, + })?; + let test_app_file_name = test_app + .file_name() + .map(|val| val.to_string_lossy().to_string()) + .ok_or(InputError::InvalidFileName { + path: test_app.clone(), + })?; + let test_app_total_size = (&file).metadata().await?.len(); + let mut test_app_reader = ReaderStream::new(file); + let mut multi_progress: Option = if progress { + Some(MultiProgress::new()) + } else { + None + }; + let test_app_progress_bar; + let test_app_body; + if progress { + let sty = ProgressStyle::with_template( + "{spinner} [{elapsed_precise}] [{wide_bar}] {bytes}/{total_bytes} ({bytes_per_sec}, {eta})" + ) + .unwrap() + .progress_chars("#>-"); + + let pb = ProgressBar::new(test_app_total_size); + pb.enable_steady_tick(Duration::from_millis(120)); + test_app_progress_bar = multi_progress.as_mut().unwrap().add(pb); + test_app_progress_bar.set_style(sty.clone()); + let mut test_app_progress = 0u64; + let test_app_stream = async_stream::stream! { + while let Some(chunk) = test_app_reader.next().await { + let test_app_progress_bar = test_app_progress_bar.clone(); + if let Ok(chunk) = &chunk { + let new = min(test_app_progress + (chunk.len() as u64), test_app_total_size); + test_app_progress = new; + test_app_progress_bar.set_position(new); + if test_app_progress >= test_app_total_size { + test_app_progress_bar.finish_and_clear(); + } + } + yield chunk; + } + }; + test_app_body = Body::wrap_stream(test_app_stream); + } else { + test_app_body = Body::wrap_stream(test_app_reader); + } + form = form.part( + "testapp", + Part::stream_with_length(test_app_body, test_app_total_size) + .file_name(test_app_file_name), + ); + + if let Some(app) = app { + let file = File::open(&app) + .await + .map_err(|error| InputError::OpenFileFailure { + path: app.clone(), + error, + })?; + + let app_file_name = app + .file_name() + .map(|val| val.to_string_lossy().to_string()) + .ok_or(InputError::InvalidFileName { path: app.clone() })?; + + let app_total_size = (&file).metadata().await?.len(); + let mut app_reader = ReaderStream::new(file); + let app_body; + + if progress { + let pb = ProgressBar::new(app_total_size); + pb.enable_steady_tick(Duration::from_millis(120)); + let app_progress_bar = multi_progress.unwrap().add(pb); + let sty = ProgressStyle::with_template( + "{spinner} [{elapsed_precise}] [{wide_bar}] {bytes}/{total_bytes} ({bytes_per_sec}, {eta})" + ) + .unwrap() + .progress_chars("#>-"); + app_progress_bar.set_style(sty); + + let mut app_progress = 0u64; + let app_stream = async_stream::stream! { + while let Some(chunk) = app_reader.next().await { + let app_progress_bar = app_progress_bar.clone(); + if let Ok(chunk) = &chunk { + let new = min(app_progress + (chunk.len() as u64), app_total_size); + app_progress = new; + app_progress_bar.set_position(new); + if app_progress >= app_total_size { + app_progress_bar.finish_and_clear(); + } + } + yield chunk; + } + }; + app_body = Body::wrap_stream(app_stream); + } else { + app_body = Body::wrap_stream(app_reader); + } + + form = form.part( + "app", + Part::stream_with_length(app_body, app_total_size).file_name(app_file_name), + ); + } + + if let Some(name) = name { + form = form.text("name", name) + } + + if let Some(link) = link { + form = form.text("link", link) + } + + if let Some(os_version) = os_version { + form = form.text("osversion", os_version) + } + + if let Some(system_image) = system_image { + form = form.text("system_image", system_image) + } + + if let Some(isolated) = isolated { + form = form.text("isolated", isolated.to_string()) + } + + if let Some(filtering_configuration) = filtering_configuration { + form = form.text( + "filtering_configuration", + serde_json::to_string(&filtering_configuration)?, + ); + } + + let response = self + .client + .post(url) + .multipart(form) + .send() + .await + .map_err(api_error_adapter)? + .json::() + .await + .map_err(|error| ApiError::DeserializationFailure { error })?; + + Ok(response.run_id) + } + + async fn get_run(&self, id: &str) -> Result { + let url = format!("{}/run/{}", self.base_url, id); + let params = [("api_key", self.api_key.clone())]; + let url = reqwest::Url::parse_with_params(&url, ¶ms) + .map_err(|error| ApiError::InvalidParameters { error })?; + + let response = self + .client + .get(url) + .send() + .await + .map_err(api_error_adapter)? + .json::() + .await + .map_err(|error| ApiError::DeserializationFailure { error })?; + Ok(response) + } + + async fn list_artifact(&self, jwt_token: &str, id: &str) -> Result> { + let url = format!("{}/artifact/{}", self.base_url, id); + + let response = self + .client + .get(url) + .header("Authorization", format!("Bearer {}", jwt_token)) + .send() + .await + .map_err(api_error_adapter)? + .json::>() + .await + .map_err(|error| ApiError::DeserializationFailure { error })?; + + Ok(response) + } + + async fn download_artifact( + &self, + jwt_token: &str, + artifact: Artifact, + base_path: PathBuf, + ) -> Result<()> { + let url = format!("{}/artifact", self.base_url); + let params = [("key", artifact.id.to_owned())]; + let url = reqwest::Url::parse_with_params(&url, ¶ms) + .map_err(|error| ApiError::InvalidParameters { error })?; + + let relative_path = artifact.id.strip_prefix('/').unwrap_or(&artifact.id); + let relative_path = Path::new(&relative_path); + let mut absolute_path = base_path.clone(); + absolute_path.push(relative_path); + + let mut src = self + .client + .get(url) + .header("Authorization", format!("Bearer {}", jwt_token)) + .send() + .await + .map_err(api_error_adapter)? + .bytes_stream(); + + let dst_dir = absolute_path.parent(); + if let Some(dst_dir) = dst_dir { + if !dst_dir.is_dir() { + create_dir_all(dst_dir).await?; + } + } + let mut dst = File::create(absolute_path).await?; + + while let Some(chunk) = src.next().await { + io::copy(&mut chunk?.as_ref(), &mut dst).await?; + } + + Ok(()) + } +} + +fn api_error_adapter(error: reqwest::Error) -> ApiError { + if let Some(status) = error.status() { + match status { + StatusCode::UNAUTHORIZED => ApiError::Unauthorized { error }, + _ => ApiError::RequestFailed { error }, + } + } else { + ApiError::RequestFailed { error } + } +} + +#[derive(Deserialize)] +pub struct CreateRunResponse { + #[serde(rename = "run_id")] + pub run_id: String, + #[serde(rename = "status")] + pub status: String, +} + +#[derive(Deserialize)] +pub struct TestRun { + #[serde(rename = "id")] + pub id: String, + #[serde(rename = "state")] + pub state: String, + #[serde(rename = "passed")] + pub passed: Option, + #[serde(rename = "failed")] + pub failed: Option, + #[serde(rename = "ignored")] + pub ignored: Option, + #[serde(rename = "completed", with = "time::serde::iso8601::option")] + pub completed: Option, +} + +#[derive(Deserialize)] +pub struct GetTokenResponse { + #[serde(rename = "token")] + pub token: String, +} + +#[derive(Deserialize, Clone)] +pub struct Artifact { + #[serde(rename = "id")] + pub id: String, + #[serde(rename = "name")] + pub name: String, + #[serde(rename = "is_file")] + pub is_file: bool, +} diff --git a/src/artifacts.rs b/src/artifacts.rs new file mode 100644 index 0000000..f6a4adf --- /dev/null +++ b/src/artifacts.rs @@ -0,0 +1,90 @@ +use std::path::PathBuf; + +use ::futures::{stream, StreamExt, TryStreamExt}; +use anyhow::Result; +use indicatif::ProgressBar; +use log::debug; + +use crate::api::{Artifact, RapiClient, RapiReqwestClient}; +use crate::errors::ArtifactError; + +pub async fn fetch_artifact_list( + client: &RapiReqwestClient, + id: &str, + token: &str, +) -> Result> { + let mut artifacts: Vec = Vec::new(); + let mut list: Vec = vec![id.to_owned()]; + + loop { + let stats: Vec = stream::iter(list.clone().into_iter()) + .map(|dir| { + let client = client.clone(); + let token = token.to_owned(); + tokio::spawn(async move { client.list_artifact(&token, &dir).await.unwrap() }) + }) + .buffer_unordered(num_cpus::get()) + .try_concat() + .await + .map_err(|error| ArtifactError::ListFailed { error })?; + + list.clear(); + for f in stats { + if f.is_file { + artifacts.push(f); + } else { + list.push(f.id); + } + } + + if list.is_empty() { + break; + } + } + + Ok(artifacts) +} + +pub async fn download_artifacts( + client: &RapiReqwestClient, + artifacts: Vec, + path: &PathBuf, + token: &str, + progress: bool, +) -> Result<()> { + debug!("Downloading {} artifacts:", artifacts.len()); + + artifacts.iter().for_each(|f| debug!("{}", f.id)); + + let mut progress_bar: Option = None; + if progress { + progress_bar = Some(ProgressBar::new(artifacts.len() as u64)) + } + + stream::iter(artifacts.into_iter()) + .map(|artifact| { + let client = client.clone(); + let token = token.to_owned(); + let base_path = path.clone(); + let progress_bar = progress_bar.clone(); + tokio::spawn(async move { + client + .download_artifact(&token, artifact, base_path) + .await + .unwrap(); + + if let Some(progress_bar) = progress_bar { + progress_bar.inc(1); + } + }) + }) + .buffer_unordered(num_cpus::get()) + .try_collect() + .await + .map_err(|error| ArtifactError::DownloadFailed { error })?; + + if let Some(progress_bar) = progress_bar { + progress_bar.finish_with_message("done"); + } + Ok(()) +} diff --git a/src/bin/mangen.rs b/src/bin/mangen.rs new file mode 100644 index 0000000..7a8f2e0 --- /dev/null +++ b/src/bin/mangen.rs @@ -0,0 +1,29 @@ +use clap::CommandFactory; +use clap_mangen::Man; +use marathon_cloud::cli::Cli; +use std::env; +use std::ffi::OsString; +use std::fs; +use std::io::Result; + +/// Man page can be created with: +/// `cargo run --bin marathon-cloud-mangen` +/// in a directory specified by the environment variable OUT_DIR. +/// See +fn main() -> Result<()> { + let args: Vec = env::args().collect(); + let first_arg: Option = args.get(1).map(|s| s.into()); + let out_dir = std::env::var_os("OUT_DIR") + .or(first_arg) + .ok_or(std::io::ErrorKind::NotFound)?; + let out_dir = std::path::PathBuf::from(out_dir); + + let out_path = out_dir.join(format!("{}.1", env!("CARGO_PKG_NAME"))); + let app = Cli::command(); + let man = Man::new(app); + let mut buffer = Vec::::new(); + man.render(&mut buffer)?; + fs::write(&out_path, buffer)?; + println!("Man page is generated at {out_path:?}"); + Ok(()) +} diff --git a/src/cli.rs b/src/cli.rs new file mode 100644 index 0000000..e5af12b --- /dev/null +++ b/src/cli.rs @@ -0,0 +1,290 @@ +use anyhow::Result; +use clap::CommandFactory; +use clap::{Args, Parser, Subcommand}; +use std::{fmt::Display, path::PathBuf}; + +use crate::errors::default_error_handler; +use crate::interactor::{DownloadArtifactsInteractor, TriggerTestRunInteractor}; + +#[derive(Parser)] +#[command( + name = "marathon-cloud", + about = "Marathon Cloud command-line interface", + long_about = None, + author, + version, + about, + arg_required_else_help = true +)] +pub struct Cli { + #[command(subcommand)] + command: Option, + #[command(flatten)] + verbose: clap_verbosity_flag::Verbosity, +} + +impl Cli { + pub async fn run() -> Result<()> { + let cli = Cli::parse(); + simple_logger::SimpleLogger::new() + .env() + .with_level(cli.verbose.log_level_filter()) + .init() + .unwrap(); + + let result = match cli.command { + Some(Commands::Run(args)) => { + let run_cmd = args.command.unwrap(); + match run_cmd { + RunCommands::Android { + application, + test_application, + os_version, + system_image, + common, + api_args, + } => { + TriggerTestRunInteractor {} + .execute( + &api_args.base_url, + &api_args.api_key, + common.wait, + common.isolated, + common.filter_file, + &common.output, + application, + test_application, + os_version, + system_image.map(|x| x.to_string()), + "Android".to_owned(), + true, + ) + .await + } + RunCommands::iOS { + application, + test_application, + common, + api_args, + } => { + TriggerTestRunInteractor {} + .execute( + &api_args.base_url, + &api_args.api_key, + common.wait, + common.isolated, + common.filter_file, + &common.output, + Some(application), + test_application, + None, + None, + "iOS".to_owned(), + true, + ) + .await + } + } + } + Some(Commands::Download(args)) => { + let interactor = DownloadArtifactsInteractor {}; + let _ = interactor + .execute( + &args.api_args.base_url, + &args.api_args.api_key, + &args.id, + args.wait, + &args.output, + ) + .await; + Ok(true) + } + Some(Commands::Completions { shell }) => { + let mut app = Self::command(); + let bin_name = app.get_name().to_string(); + clap_complete::generate(shell, &mut app, bin_name, &mut std::io::stdout()); + Ok(true) + } + None => Ok(true), + }; + + match result { + Ok(true) => ::std::process::exit(0), + Ok(false) => ::std::process::exit(1), + Err(error) => { + let stderr = std::io::stderr(); + default_error_handler(error.into(), &mut stderr.lock()); + ::std::process::exit(1); + } + } + } +} + +#[derive(Subcommand)] +enum Commands { + #[clap(about = "Submit a test run")] + Run(RunArgs), + #[clap(about = "Download artifacts from a previous test run")] + Download(DownloadArgs), + #[clap(about = "Output shell completion code for the specified shell (bash, zsh, fish)")] + Completions { shell: clap_complete::Shell }, +} + +#[derive(Debug, clap::Parser)] +#[command(args_conflicts_with_subcommands = true)] +struct RunArgs { + #[command(subcommand)] + command: Option, +} +/// Options valid for any subcommand. +#[derive(Debug, Clone, clap::Args)] +struct CommonRunArgs { + #[arg(short, long, help = "Output folder for test run results")] + output: Option, + + #[arg(long, help = "Run each test in isolation, i.e. isolated batching.")] + isolated: Option, + + #[arg( + long, + help = "Test filters supplied as a YAML file following the schema at https://docs.marathonlabs.io/runner/configuration/filtering/#filtering-logic. For iOS see also https://docs.marathonlabs.io/runner/next/ios#test-plans" + )] + filter_file: Option, + + #[arg( + long, + default_value_t = true, + help = "Wait for test run to finish if true, exits after triggering a run if false" + )] + wait: bool, + + #[arg( + long, + help = "name for run, for example it could be description of commit" + )] + name: Option, + + #[arg(long, help = "link to commit")] + link: Option, +} + +#[derive(Debug, Args)] +#[command(args_conflicts_with_subcommands = true)] +struct DownloadArgs { + #[arg(short, long, help = "Output folder for test run results")] + output: PathBuf, + + #[arg(long, help = "Test run id")] + id: String, + + #[arg( + long, + default_value_t = true, + help = "Wait for test run to finish if true, exits immediately if false" + )] + wait: bool, + + #[command(flatten)] + api_args: ApiArgs, +} + +#[derive(Debug, Args)] +#[command(args_conflicts_with_subcommands = true)] +struct ApiArgs { + #[arg(long, env("MARATHON_CLOUD_API_KEY"), help = "Marathon Cloud API key")] + api_key: String, + + #[arg( + long, + default_value = "https://cloud.marathonlabs.io/api/v1", + help = "Base url for Marathon Cloud API" + )] + base_url: String, +} + +#[derive(Debug, Subcommand)] +enum RunCommands { + #[clap(about = "Run tests for Android")] + Android { + #[arg( + short, + long, + help = "application filepath, example: /home/user/workspace/sample.apk" + )] + application: Option, + + #[arg( + short, + long, + help = "test application filepath, example: /home/user/workspace/testSample.apk" + )] + test_application: PathBuf, + + #[arg(long, help = "OS version [10, 11, 12, 13]")] + os_version: Option, + + #[arg(value_enum, long, help = "Runtime system image")] + system_image: Option, + + #[command(flatten)] + common: CommonRunArgs, + + #[command(flatten)] + api_args: ApiArgs, + }, + #[allow(non_camel_case_types)] + #[command(name = "ios")] + #[clap(about = "Run tests for iOS")] + iOS { + #[arg( + short, + long, + help = "application filepath, example: /home/user/workspace/sample.zip" + )] + application: PathBuf, + + #[arg( + short, + long, + help = "test application filepath, example: /home/user/workspace/sampleUITests-Runner.zip" + )] + test_application: PathBuf, + + #[command(flatten)] + common: CommonRunArgs, + + #[command(flatten)] + api_args: ApiArgs, + }, +} + +#[derive(Debug)] +pub enum Platform { + Android, + #[allow(non_camel_case_types)] + iOS, +} + +impl Display for Platform { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Platform::Android => f.write_str("Android"), + Platform::iOS => f.write_str("iOS"), + } + } +} + +#[derive(Debug, clap::ValueEnum, Clone)] +pub enum AndroidSystemImage { + Default, + GoogleApis, +} + +impl Display for AndroidSystemImage { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + AndroidSystemImage::Default => f.write_str("default"), + AndroidSystemImage::GoogleApis => f.write_str("google_apis"), + } + } +} diff --git a/src/errors.rs b/src/errors.rs new file mode 100644 index 0000000..9bbe40c --- /dev/null +++ b/src/errors.rs @@ -0,0 +1,55 @@ +use std::{io::Write, path::PathBuf}; + +use console::Style; +use reqwest::Error as ReqwestError; +use thiserror::Error; +use tokio::{io, task::JoinError}; +use url::ParseError; + +#[derive(Error, Debug)] +pub enum ApiError { + #[error("Unauthorized client. Double check you've supplied correct api key or you have appropriate permissions\nerror = ${error}")] + Unauthorized { error: ReqwestError }, + #[error("Invalid parameters for url")] + InvalidParameters { error: ParseError }, + #[error("Failed to parse API response\nerror = ${error}")] + DeserializationFailure { error: reqwest::Error }, + #[error("API request failed\nerror = ${error}")] + RequestFailed { error: ReqwestError }, +} + +#[derive(Error, Debug)] +pub enum ArtifactError { + #[error("Failed to retrieve artifact list.\nerror = ${error}")] + ListFailed { error: JoinError }, + + #[error("Failed to download artifacts.\nerror = ${error}")] + DownloadFailed { error: JoinError }, +} + +#[derive(Error, Debug)] +pub enum InputError { + #[error("Invalid input file. Double check you've supplied correct path\npath= ${path}")] + InvalidFileName { path: PathBuf }, + + #[error("Can't open file. Double check you've supplied correct path\npath= ${path}")] + OpenFileFailure { path: PathBuf, error: io::Error }, +} + +#[derive(Error, Debug)] +pub enum FilteringConfigurationError { + #[error("Filter type ${mtype} is not supported by Marathon Cloud")] + UnsupportedFilterType { mtype: String }, + #[error("Filter type ${mtype} is invalid")] + InvalidFilterType { mtype: String }, + #[error("Invalid configuration for filter ${mtype}: ${message}")] + InvalidFilterConfiguration { mtype: String, message: String }, +} + +pub fn default_error_handler( + error: Box, + output: &mut dyn Write, +) { + let red = Style::new().red(); + writeln!(output, "{}", red.apply_to(error)); +} diff --git a/src/filtering.rs b/src/filtering.rs new file mode 100644 index 0000000..c0dff00 --- /dev/null +++ b/src/filtering.rs @@ -0,0 +1,345 @@ +use anyhow::Result; +use std::path::{Path, PathBuf}; +use tokio::{ + fs::{self, File}, + io::AsyncReadExt, +}; + +use serde::{Deserialize, Serialize}; +use serde_with::skip_serializing_none; + +use crate::errors::{FilteringConfigurationError, InputError}; + +pub async fn convert(cnf: PathBuf) -> Result { + let content = fs::read_to_string(&cnf) + .await + .map_err(|error| InputError::OpenFileFailure { + path: cnf.clone(), + error, + })?; + + let mut filtering_configuration: SparseMarathonfile = serde_yaml::from_str(&content)?; + + let absolute_path = fs::canonicalize(&cnf).await?; + let workdir = absolute_path.parent().unwrap_or(Path::new("")); + validate( + &mut filtering_configuration.filtering_configuration, + workdir, + ) + .await?; + + Ok(filtering_configuration) +} + +pub async fn validate(cnf: &mut FilteringConfiguration, workdir: &Path) -> Result<()> { + let supported_types = vec![ + "fully-qualified-class-name", + "fully-qualified-test-name", + "simple-class-name", + "package", + "method", + "annotation", + ]; + let unsupported_types = vec!["allure", "fragmentation", "annotationData"]; + + for list in [&mut cnf.allowlist, &mut cnf.blocklist] { + match list { + Some(filters) => { + validate_filters(filters, &supported_types, &unsupported_types, workdir).await? + } + None => continue, + } + } + + Ok(()) +} + +async fn validate_filters( + filters: &mut [Filter], + supported_types: &[&str], + unsupported_types: &[&str], + workdir: &Path, +) -> Result<()> { + for filter in filters.iter_mut() { + if filter.mtype == "composition" { + if filter.op.is_none() { + anyhow::bail!(FilteringConfigurationError::InvalidFilterConfiguration { + mtype: filter.mtype.clone(), + message: "missing 'op' field".to_owned() + }); + } else if filter.op.as_ref().is_some_and(|op| op.is_empty()) { + anyhow::bail!(FilteringConfigurationError::InvalidFilterConfiguration { + mtype: filter.mtype.clone(), + message: "empty 'op' field".to_owned() + }); + } else { + match filter.filters.as_mut() { + Some(filters) => { + for filter in filters.iter_mut() { + validate_filter(filter, supported_types, unsupported_types, workdir) + .await?; + } + } + None => { + anyhow::bail!(FilteringConfigurationError::InvalidFilterConfiguration { + mtype: filter.mtype.clone(), + message: "missing composition filters".to_owned() + }); + } + } + } + } else { + validate_filter(filter, supported_types, unsupported_types, workdir).await?; + } + } + Ok(()) +} + +async fn validate_filter( + filter: &mut Filter, + supported_types: &[&str], + unsupported_types: &[&str], + workdir: &Path, +) -> Result<()> { + if unsupported_types.iter().any(|&t| t == filter.mtype) { + anyhow::bail!(FilteringConfigurationError::UnsupportedFilterType { + mtype: filter.mtype.clone(), + }); + } else if !supported_types.iter().any(|&t| t == filter.mtype) { + anyhow::bail!(FilteringConfigurationError::InvalidFilterType { + mtype: filter.mtype.clone(), + }); + } + + match (&filter.regex, &filter.values, &filter.file) { + (None, None, None) => { + anyhow::bail!(FilteringConfigurationError::InvalidFilterConfiguration { + mtype: filter.mtype.clone(), + message: "At least one of regex, values or file should be specified".into() + }) + } + + (None, None, Some(path)) => { + if !path.is_relative() { + anyhow::bail!(FilteringConfigurationError::InvalidFilterConfiguration { + mtype: filter.mtype.clone(), + message: "File should be specified relative to the filter file".into() + }) + } else if !workdir.join(path).is_file() { + anyhow::bail!(FilteringConfigurationError::InvalidFilterConfiguration { + mtype: filter.mtype.clone(), + message: "File does not exist or is not a regular file".into() + }) + } else { + let mut values_file = File::open(workdir.join(path)).await?; + let size = values_file.metadata().await?.len(); + if size == 0 { + anyhow::bail!(FilteringConfigurationError::InvalidFilterConfiguration { + mtype: filter.mtype.clone(), + message: "File does not exist or is not a regular file".into() + }) + } + + let mut buffer = String::new(); + values_file.read_to_string(&mut buffer).await?; + + let mut values = Vec::new(); + for value in buffer.lines() { + values.push(value.to_owned()); + } + filter.values = Some(values); + filter.file = None; + } + Ok(()) + } + (None, Some(_), None) => Ok(()), + (Some(_), None, None) => Ok(()), + + _ => anyhow::bail!(FilteringConfigurationError::InvalidFilterConfiguration { + mtype: filter.mtype.clone(), + message: "only one of [regex, values, file] can be specified".into() + }), + } +} + +#[skip_serializing_none] +#[derive(Deserialize, Serialize)] +pub struct SparseMarathonfile { + #[serde(rename = "filteringConfiguration")] + pub filtering_configuration: FilteringConfiguration, +} + +#[skip_serializing_none] +#[derive(Deserialize, Serialize)] +pub struct FilteringConfiguration { + #[serde(rename = "allowlist")] + pub allowlist: Option>, + #[serde(rename = "blocklist")] + pub blocklist: Option>, +} + +// Very simplstic and flattened representation of https://github.com/MarathonLabs/marathon/blob/0.9.1/configuration/src/main/kotlin/com/malinskiy/marathon/config/FilteringConfiguration.kt +#[skip_serializing_none] +#[derive(Deserialize, Serialize)] +pub struct Filter { + #[serde(rename = "type")] + pub mtype: String, + + #[serde[rename = "regex"]] + pub regex: Option, + #[serde[rename = "values"]] + pub values: Option>, + #[serde[rename = "file"]] + pub file: Option, + + #[serde[rename = "filters"]] + pub filters: Option>, + #[serde[rename = "op"]] + pub op: Option, +} + +#[cfg(test)] +mod tests { + use anyhow::Result; + use std::path::Path; + + use crate::filtering::convert; + + #[tokio::test] + async fn test_valid() -> Result<()> { + let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap(); + let fixture = Path::new(&manifest_dir) + .join("fixture") + .join("filtering") + .join("valid.yaml"); + let result = convert(fixture).await?; + let result = serde_json::to_string(&result)?; + assert_eq!( + result, + r#"{"filteringConfiguration":{"allowlist":[{"type":"fully-qualified-test-name","regex":".*Test"}]}}"# + ); + Ok(()) + } + + #[tokio::test] + async fn test_valid_complex() -> Result<()> { + let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap(); + let fixture = Path::new(&manifest_dir) + .join("fixture") + .join("filtering") + .join("validComplex.yaml"); + let result = convert(fixture).await?; + let result = serde_json::to_string(&result)?; + + assert_eq!( + result, + r#"{"filteringConfiguration":{"allowlist":[{"type":"package","values":["com.example.tests"]},{"type":"composition","filters":[{"type":"method","regex":"test.*"},{"type":"annotation","values":["com.example.MyAnnotation"]}],"op":"UNION"}],"blocklist":[{"type":"package","values":["com.example.tests2"]}]}}"# + ); + Ok(()) + } + + #[tokio::test] + async fn test_unknown_type() -> Result<()> { + let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap(); + let fixture = Path::new(&manifest_dir) + .join("fixture") + .join("filtering") + .join("unknownType.yaml"); + let result = convert(fixture).await; + + assert!(result.is_err()); + Ok(()) + } + + #[tokio::test] + async fn test_invalid_composition_fields() -> Result<()> { + let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap(); + let fixture = Path::new(&manifest_dir) + .join("fixture") + .join("filtering") + .join("invalidCompositionFields.yaml"); + let result = convert(fixture).await; + + assert!(result.is_err()); + Ok(()) + } + + #[tokio::test] + async fn test_invalid() -> Result<()> { + let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap(); + let fixture = Path::new(&manifest_dir) + .join("fixture") + .join("filtering") + .join("invalid.yaml"); + let result = convert(fixture).await; + + assert!(result.is_err()); + Ok(()) + } + + #[tokio::test] + async fn test_grammar_error() -> Result<()> { + let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap(); + let fixture = Path::new(&manifest_dir) + .join("fixture") + .join("filtering") + .join("grammarError.yaml"); + let result = convert(fixture).await; + + assert!(result.is_err()); + Ok(()) + } + + #[tokio::test] + async fn test_fragmentation() -> Result<()> { + let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap(); + let fixture = Path::new(&manifest_dir) + .join("fixture") + .join("filtering") + .join("fragmentation.yaml"); + let result = convert(fixture).await; + + assert!(result.is_err()); + Ok(()) + } + + #[tokio::test] + async fn test_filetype() -> Result<()> { + let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap(); + let fixture = Path::new(&manifest_dir) + .join("fixture") + .join("filtering") + .join("filetype.yaml"); + let result = convert(fixture).await?; + let result = serde_json::to_string(&result)?; + assert_eq!( + result, + r#"{"filteringConfiguration":{"allowlist":[{"type":"fully-qualified-test-name","values":["com.malinskiy.adam.SimpleTest#test1","com.malinskiy.adam.SimpleTest#test2"]}]}}"# + ); + Ok(()) + } + + #[tokio::test] + async fn test_correct_no_fields() -> Result<()> { + let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap(); + let fixture = Path::new(&manifest_dir) + .join("fixture") + .join("filtering") + .join("correctTypeNoFields.yaml"); + let result = convert(fixture).await; + assert!(result.is_err()); + Ok(()) + } + + #[tokio::test] + async fn test_correct_two_fields() -> Result<()> { + let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap(); + let fixture = Path::new(&manifest_dir) + .join("fixture") + .join("filtering") + .join("correctTypeTwoFields.yaml"); + let result = convert(fixture).await; + assert!(result.is_err()); + Ok(()) + } +} diff --git a/src/interactor.rs b/src/interactor.rs new file mode 100644 index 0000000..d2f0064 --- /dev/null +++ b/src/interactor.rs @@ -0,0 +1,197 @@ +use anyhow::Result; +use indicatif::{HumanDuration, ProgressBar, ProgressStyle}; +use std::{path::PathBuf, time::Duration}; + +use console::style; +use log::debug; +use tokio::time::{sleep, Instant}; + +use crate::{ + api::{RapiClient, RapiReqwestClient}, + artifacts::{download_artifacts, fetch_artifact_list}, + filtering, +}; + +pub struct DownloadArtifactsInteractor {} + +impl DownloadArtifactsInteractor { + pub(crate) async fn execute( + &self, + base_url: &str, + api_key: &str, + id: &str, + wait: bool, + output: &PathBuf, + ) -> Result<()> { + let started = Instant::now(); + println!("{} Checking test run state...", style("[1/4]").bold().dim()); + let client = RapiReqwestClient::new(base_url, api_key); + let stat = client.get_run(id).await?; + if stat.completed.is_none() && wait { + loop { + if stat.completed.is_some() { + break; + } + sleep(Duration::new(5, 0)).await; + } + } else { + debug!("Test run {} finished", &id); + } + println!("{} Fetching file list...", style("[2/4]").bold().dim()); + let token = client.get_token().await?; + let artifacts = fetch_artifact_list(&client, id, &token).await?; + println!("{} Downloading files...", style("[3/4]").bold().dim()); + download_artifacts(&client, artifacts, output, &token, true).await?; + println!( + "{} Patching local relative paths...", + style("[4/4]").bold().dim() + ); + + println!("Done in {}", HumanDuration(started.elapsed())); + Ok(()) + } +} + +pub struct TriggerTestRunInteractor {} + +impl TriggerTestRunInteractor { + pub(crate) async fn execute( + &self, + base_url: &str, + api_key: &str, + wait: bool, + isolated: Option, + filter_file: Option, + output: &Option, + application: Option, + test_application: PathBuf, + os_version: Option, + system_image: Option, + platform: String, + progress: bool, + ) -> Result { + let client = RapiReqwestClient::new(base_url, api_key); + let steps = match (wait, output) { + (true, Some(_)) => 5, + (true, None) => 2, + _ => 1, + }; + + println!( + "{} Submitting new run...", + style(format!("[1/{}]", steps)).bold().dim() + ); + let filter_file = filter_file.map(filtering::convert); + let filtering_configuration = match filter_file { + Some(future) => Some(future.await?), + None => None, + }; + let id = client + .create_run( + application, + test_application, + None, + None, + platform, + os_version, + system_image, + isolated, + filtering_configuration, + progress, + ) + .await?; + + if wait { + println!( + "{} Waiting for test run to finish...", + style(format!("[2/{}]", steps)).bold().dim() + ); + + let spinner = if progress { + let pb = ProgressBar::new_spinner(); + pb.enable_steady_tick(Duration::from_millis(120)); + pb.set_style( + ProgressStyle::with_template("{spinner}") + .unwrap() + .tick_strings(&[ + "( ● )", + "( ● )", + "( ● )", + "( ● )", + "( ●)", + "( ● )", + "( ● )", + "( ● )", + "( ● )", + "(● )", + ]), + ); + Some(pb) + } else { + None + }; + loop { + let stat = client.get_run(&id).await?; + if stat.completed.is_some() { + if let Some(s) = spinner { + s.finish_and_clear() + } + + match stat.state.as_ref() { + "passed" => println!("Marathon Cloud execution finished"), + "failure" => println!("Marathon Cloud execution finished with failures"), + _ => println!("Marathon cloud execution crashed"), + }; + println!("\tstate: {}", stat.state); + println!("\treport: {}/report/{}", base_url, id); + println!( + "\tpassed: {}", + stat.passed + .map(|x| x.to_string()) + .unwrap_or("missing".to_owned()) + ); + println!( + "\tfailed: {}", + stat.failed + .map(|x| x.to_string()) + .unwrap_or("missing".to_owned()) + ); + println!( + "\tignored: {}", + stat.ignored + .map(|x| x.to_string()) + .unwrap_or("missing".to_owned()) + ); + + if let Some(output) = output { + println!( + "{} Fetching file list...", + style(format!("[3/{}]", steps)).bold().dim() + ); + let token = client.get_token().await?; + let artifacts = fetch_artifact_list(&client, &id, &token).await?; + println!( + "{} Downloading files...", + style(format!("[4/{}]", steps)).bold().dim() + ); + download_artifacts(&client, artifacts, output, &token, true).await?; + println!( + "{} Patching local relative paths...", + style(format!("[5/{}]", steps)).bold().dim() + ); + } + + return if stat.state == "failure" { + Ok(false) + } else { + Ok(true) + }; + } + sleep(Duration::new(5, 0)).await; + } + } else { + println!("Test run {} started", id); + Ok(true) + } + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..30882b3 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,6 @@ +mod api; +mod artifacts; +pub mod cli; +mod errors; +mod filtering; +mod interactor; diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..4e75bd4 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,7 @@ +use anyhow::Result; +use marathon_cloud::cli; + +#[tokio::main] +async fn main() -> Result<()> { + cli::Cli::run().await +} diff --git a/src/result.rs b/src/result.rs new file mode 100644 index 0000000..6e1b8d7 --- /dev/null +++ b/src/result.rs @@ -0,0 +1,3 @@ +use crate::errors::CliError; + +pub type Result = std::result::Result;