Compare commits
138 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 56e22622d9 | |||
| a64f4dbf79 | |||
| 1b83d9b199 | |||
| f006503736 | |||
| 9abd2f88cf | |||
| 29acad5eed | |||
|
|
aba958ff8e | ||
|
|
b273c75018 | ||
| 1f7f5dbcae | |||
| 00802795ee | |||
| 2a01189a07 | |||
|
|
d3ca5d52bc | ||
|
|
7124e067a2 | ||
| 5f282fef87 | |||
|
|
e0121c88e9 | ||
| 9e3065794a | |||
|
|
3fecd6a00a | ||
| be8e3263c9 | |||
|
|
d563cc286f | ||
|
|
fc75487da9 | ||
| a015c9b382 | |||
|
|
e84f3da8d8 | ||
| f4a54a412a | |||
|
|
d1bfe9fb62 | ||
|
|
af01a4855a | ||
| 9ceaa1078d | |||
|
|
7f3edcaa9d | ||
| 3d93b5b479 | |||
|
|
32ea7ea35d | ||
| cce8a23b63 | |||
|
|
5ad5c14acb | ||
| 10e2beba3c | |||
|
|
07b14935df | ||
|
|
10f826b23b | ||
| 0871f9de7b | |||
|
|
5c5107ed5f | ||
| 13a074c3be | |||
|
|
cf7d010bc4 | ||
| c8696177c5 | |||
|
|
5fd25df6ac | ||
| be8a5535fa | |||
|
|
48d07e697b | ||
|
|
6c5b59f619 | ||
| 8252191317 | |||
| 261ec0bb6d | |||
| e8de47dc52 | |||
| dbb4d265c4 | |||
| 924976ee1b | |||
| ae6fe8be44 | |||
| 76df717fea | |||
|
|
2885decede | ||
|
|
353ce16782 | ||
| 2098aa65c1 | |||
| 1385aacc62 | |||
| a2106a06a1 | |||
|
|
f0d763c269 | ||
| 4c30bc7e7d | |||
|
|
21127f3ec3 | ||
| 6adc4ce228 | |||
|
|
9e740ec550 | ||
| 81f7e86adc | |||
|
|
8a79de2fc8 | ||
| 9116d1ed53 | |||
|
|
966b69b43b | ||
| 71f4781780 | |||
| fbd2503136 | |||
| 00080543bd | |||
| 1c02106bdc | |||
| 595917bb2b | |||
|
|
c0c0ae0b99 | ||
| 477c87aea9 | |||
| 4b9a84cf70 | |||
| a1e0a97b84 | |||
|
|
77676efffa | ||
| cfc296dd75 | |||
|
|
47d5159fd3 | ||
| ec115d470a | |||
|
|
c48301dead | ||
| d6a2606b7d | |||
| 7dc4995c9b | |||
|
|
419077b1a5 | ||
| befd414bfe | |||
| 2b971602c3 | |||
| ad1be71f41 | |||
| 18ee7e4a9f | |||
|
|
8e2d2a9b61 | ||
| 68bc150d30 | |||
|
|
ef0a687031 | ||
| d0278bfa65 | |||
|
|
f8b78a1325 | ||
| 35183f0e34 | |||
| 54bc914554 | |||
| 2a74aa3588 | |||
| cc5d4cd45d | |||
| c6bf2e10db | |||
| b326e7ed4c | |||
| 396cb4c3e4 | |||
| 9963d9cd0b | |||
| ba64e8141d | |||
| 53ce30e0b1 | |||
| 9aea77661b | |||
| e412f01c0a | |||
| 1e34429f52 | |||
| 25c428ddbd | |||
| f19153b196 | |||
| 8b3c9c822e | |||
| b5ad622798 | |||
| 1063038c52 | |||
|
|
9a3c7d360c | ||
| b956e95619 | |||
|
|
6c7f1c7ecd | ||
| a8b3c185c6 | |||
|
|
9b5b2a603f | ||
| 84ffc8b71c | |||
|
|
06885e3d86 | ||
|
|
77fe30b267 | ||
| eb7c78327b | |||
| 6fe33bffa0 | |||
| cd2c3d6c3d | |||
|
|
77f5c7824c | ||
| 5d1cfffc74 | |||
|
|
ee8bbad82c | ||
| fcab216926 | |||
|
|
ce42f51606 | ||
| c0755d8751 | |||
|
|
4a3058ce55 | ||
|
|
79ee93cc06 | ||
| 08dea6a190 | |||
|
|
671429dfcf | ||
|
|
2cb9e0cd14 | ||
| 8c5dd6fd84 | |||
| 63eef4a70a | |||
|
|
1be44cc855 | ||
|
|
8362359093 | ||
|
|
c20bbf07d9 | ||
|
|
d94e611b12 | ||
| e334b375da | |||
|
|
b95ac1a686 |
+153
-271
@@ -8,9 +8,9 @@ on:
|
|||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
inputs:
|
inputs:
|
||||||
bump_type:
|
bump_type:
|
||||||
description: 'Specify the type of version bump'
|
description: "Specify the type of version bump"
|
||||||
required: true
|
required: true
|
||||||
default: 'patch'
|
default: "patch"
|
||||||
type: choice
|
type: choice
|
||||||
options:
|
options:
|
||||||
- patch
|
- patch
|
||||||
@@ -46,7 +46,7 @@ jobs:
|
|||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@v4
|
uses: actions/setup-python@v4
|
||||||
with:
|
with:
|
||||||
python-version: '3.10'
|
python-version: "3.10"
|
||||||
|
|
||||||
- name: Install Commitizen
|
- name: Install Commitizen
|
||||||
run: |
|
run: |
|
||||||
@@ -126,9 +126,7 @@ jobs:
|
|||||||
- name: Generate changelog for the version bump
|
- name: Generate changelog for the version bump
|
||||||
id: changelog
|
id: changelog
|
||||||
run: |
|
run: |
|
||||||
changelog=$(conventional-changelog -p angular -i CHANGELOG.md -s --from ${{ env.prev_version }} --to ${{ env.version }})
|
conventional-changelog -p conventionalcommits -i CHANGELOG.md --from ${{ env.prev_version }} --to v${{ env.version }} > artifacts/changelog.md
|
||||||
echo "$changelog" > artifacts/changelog.md
|
|
||||||
echo "changelog_body=$(cat artifacts/changelog.md)" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
- name: Push changes
|
- name: Push changes
|
||||||
if: env.ACT != 'true'
|
if: env.ACT != 'true'
|
||||||
@@ -151,30 +149,42 @@ jobs:
|
|||||||
Cargo.toml
|
Cargo.toml
|
||||||
Cargo.lock
|
Cargo.lock
|
||||||
|
|
||||||
build-release-artifacts:
|
publish-github-release:
|
||||||
name: build-release
|
name: build-release
|
||||||
needs: [bump-version]
|
needs: [bump-version]
|
||||||
runs-on: ${{ matrix.job.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
env:
|
env:
|
||||||
RUST_BACKTRACE: 1
|
RUST_BACKTRACE: 1
|
||||||
|
BUILD_CMD: cargo
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: true
|
fail-fast: true
|
||||||
matrix:
|
matrix:
|
||||||
# prettier-ignore
|
include:
|
||||||
job:
|
- target: aarch64-unknown-linux-musl
|
||||||
- { name: "macOS-arm64", os: "macOS-latest", target: "aarch64-apple-darwin", artifact_suffix: "macos-arm64", use-cross: true }
|
os: ubuntu-latest
|
||||||
- { name: "macOS-amd64", os: "macOS-latest", target: "x86_64-apple-darwin", artifact_suffix: "macos" }
|
use-cross: true
|
||||||
- { name: "windows-amd64", os: "windows-latest", target: "x86_64-pc-windows-msvc", artifact_suffix: "windows" }
|
cargo-flags: ""
|
||||||
- { name: "windows-aarch64", os: "windows-latest", target: "aarch64-pc-windows-msvc", artifact_suffix: "windows-aarch64", use-cross: true }
|
- target: aarch64-apple-darwin
|
||||||
- { name: "linux-gnu", os: "ubuntu-latest", target: "x86_64-unknown-linux-gnu", artifact_suffix: "linux" }
|
os: macos-latest
|
||||||
- { name: "linux-musl", os: "ubuntu-latest", target: "x86_64-unknown-linux-musl", artifact_suffix: "linux-musl", use-cross: true, }
|
use-cross: true
|
||||||
- { name: "linux-aarch64-gnu", os: "ubuntu-latest", target: "aarch64-unknown-linux-gnu", artifact_suffix: "aarch64-gnu", use-cross: true, test-bin: "--bin gman" }
|
cargo-flags: ""
|
||||||
- { name: "linux-aarch64-musl", os: "ubuntu-latest", target: "aarch64-unknown-linux-musl", artifact_suffix: "aarch64-musl", use-cross: true, test-bin: "--bin gman" }
|
- target: aarch64-pc-windows-msvc
|
||||||
- { name: "linux-arm-gnu", os: "ubuntu-latest", target: "arm-unknown-linux-gnueabi", artifact_suffix: "armv6-gnu", use-cross: true, test-bin: "--bin gman" }
|
os: windows-latest
|
||||||
- { name: "linux-arm-musl", os: "ubuntu-latest", target: "arm-unknown-linux-musleabihf", artifact_suffix: "armv6-musl", use-cross: true, test-bin: "--bin gman" }
|
use-cross: true
|
||||||
- { name: "linux-armv7-gnu", os: "ubuntu-latest", target: "armv7-unknown-linux-gnueabihf", artifact_suffix: "armv7-gnu", use-cross: true, test-bin: "--bin gman" }
|
cargo-flags: ""
|
||||||
- { name: "linux-armv7-musl", os: "ubuntu-latest", target: "armv7-unknown-linux-musleabihf", artifact_suffix: "armv7-musl", use-cross: true, test-bin: "--bin gman" }
|
- target: x86_64-apple-darwin
|
||||||
rust: [stable]
|
os: macos-latest
|
||||||
|
cargo-flags: ""
|
||||||
|
- target: x86_64-pc-windows-msvc
|
||||||
|
os: windows-latest
|
||||||
|
cargo-flags: ""
|
||||||
|
- target: x86_64-unknown-linux-musl
|
||||||
|
os: ubuntu-latest
|
||||||
|
use-cross: true
|
||||||
|
cargo-flags: ""
|
||||||
|
- target: x86_64-unknown-linux-gnu
|
||||||
|
os: ubuntu-latest
|
||||||
|
cargo-flags: ""
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Check if actor is repository owner
|
- name: Check if actor is repository owner
|
||||||
@@ -194,154 +204,6 @@ jobs:
|
|||||||
git fetch --all
|
git fetch --all
|
||||||
git pull
|
git pull
|
||||||
|
|
||||||
- name: Get bumped Cargo files (Act)
|
|
||||||
if: env.ACT == 'true'
|
|
||||||
uses: actions/download-artifact@v4
|
|
||||||
with:
|
|
||||||
name: bumped-cargo-files
|
|
||||||
path: ${{ github.workspace }}
|
|
||||||
|
|
||||||
- uses: actions/cache@v3
|
|
||||||
name: Cache Cargo registry
|
|
||||||
with:
|
|
||||||
path: ~/.cargo/registry
|
|
||||||
key: ${{ runner.os }}-cargo-registry-${{ hashFiles('Cargo.lock') }}
|
|
||||||
|
|
||||||
- uses: actions/cache@v3
|
|
||||||
if: startsWith(matrix.job.name, 'linux-')
|
|
||||||
with:
|
|
||||||
path: ~/.cargo/bin
|
|
||||||
key: ${{ runner.os }}-cargo-bin-${{ hashFiles('.github/workflows/release.yml') }}
|
|
||||||
|
|
||||||
- uses: dtolnay/rust-toolchain@stable
|
|
||||||
name: Set Rust toolchain
|
|
||||||
with:
|
|
||||||
targets: ${{ matrix.job.target }}
|
|
||||||
|
|
||||||
- uses: taiki-e/setup-cross-toolchain-action@v1
|
|
||||||
with:
|
|
||||||
# NB: sets CARGO_BUILD_TARGET evar - do not need --target flag in build
|
|
||||||
target: ${{ matrix.job.target }}
|
|
||||||
|
|
||||||
- uses: taiki-e/install-action@cross
|
|
||||||
if: ${{ matrix.job.use-cross }}
|
|
||||||
|
|
||||||
- name: Installing needed Ubuntu dependencies
|
|
||||||
if: matrix.job.os == 'ubuntu-latest'
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
sudo apt-get -y update
|
|
||||||
case ${{ matrix.job.target }} in
|
|
||||||
arm*-linux-*) sudo apt-get -y install gcc-arm-linux-gnueabihf ;;
|
|
||||||
aarch64-*-linux-*) sudo apt-get -y install gcc-aarch64-linux-gnu ;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
- name: Install LLVM/Clang for bindgen
|
|
||||||
if: matrix.job.os == 'ubuntu-latest'
|
|
||||||
run: |
|
|
||||||
sudo apt-get update
|
|
||||||
sudo apt-get install -y clang llvm-dev libclang-dev pkg-config musl-tools
|
|
||||||
echo "LIBCLANG_PATH=$(llvm-config --libdir)" >> $GITHUB_ENV
|
|
||||||
echo "BINDGEN_EXTRA_CLANG_ARGS=--sysroot=/usr --target=x86_64-unknown-linux-musl" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
- name: Install LLVM/Clang for bindgen
|
|
||||||
if: matrix.job.os == 'macOS-latest'
|
|
||||||
run: |
|
|
||||||
brew update
|
|
||||||
brew install llvm
|
|
||||||
echo "LIBCLANG_PATH=$(brew --prefix llvm)/lib" >> $GITHUB_ENV
|
|
||||||
echo "LLVM_CONFIG_PATH=$(brew --prefix llvm)/bin/llvm-config" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
- name: Install LLVM (libclang) for bindgen
|
|
||||||
if: matrix.job.os == 'windows-latest'
|
|
||||||
shell: pwsh
|
|
||||||
run: |
|
|
||||||
choco install llvm -y
|
|
||||||
# libclang.dll lives in <LLVM>\bin; point bindgen at it
|
|
||||||
$llvm = "C:\Program Files\LLVM"
|
|
||||||
echo "LIBCLANG_PATH=$llvm\bin" | Out-File -FilePath $env:GITHUB_ENV -Append
|
|
||||||
|
|
||||||
- name: Install the bindgen-cli
|
|
||||||
run: cargo install --force --locked bindgen-cli
|
|
||||||
|
|
||||||
- name: Build
|
|
||||||
run: cargo build --release --verbose --target=${{ matrix.job.target }} --locked
|
|
||||||
|
|
||||||
- name: Verify file
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
file target/${{ matrix.job.target }}/release/gman
|
|
||||||
|
|
||||||
- name: Test
|
|
||||||
if: matrix.job.target != 'aarch64-apple-darwin' && matrix.job.target != 'aarch64-pc-windows-msvc'
|
|
||||||
run: cargo test --release --verbose --target=${{ matrix.job.target }} ${{ matrix.job.test-bin }}
|
|
||||||
|
|
||||||
- name: Packaging final binary (Windows)
|
|
||||||
if: matrix.job.os == 'windows-latest'
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
cd target/${{ matrix.job.target }}/release
|
|
||||||
BINARY_NAME=gman.exe
|
|
||||||
if [ "${{ matrix.job.target }}" != "aarch64-pc-windows-msvc" ]; then
|
|
||||||
# strip the binary
|
|
||||||
strip $BINARY_NAME
|
|
||||||
fi
|
|
||||||
RELEASE_NAME=gman-${{ matrix.job.artifact_suffix }}
|
|
||||||
mkdir -p artifacts
|
|
||||||
tar czvf $RELEASE_NAME.tar.gz $BINARY_NAME
|
|
||||||
# create sha checksum files
|
|
||||||
certutil -hashfile $RELEASE_NAME.tar.gz sha256 | grep -E [A-Fa-f0-9]{64} > $RELEASE_NAME.sha256
|
|
||||||
echo "RELEASE_NAME=$RELEASE_NAME" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
- name: Packaging final binary (macOS and Linux)
|
|
||||||
if: matrix.job.os != 'windows-latest'
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
# set the right strip executable
|
|
||||||
STRIP="strip";
|
|
||||||
case ${{ matrix.job.target }} in
|
|
||||||
arm*-linux-*) STRIP="arm-linux-gnueabihf-strip" ;;
|
|
||||||
aarch64-*-linux-*) STRIP="aarch64-linux-gnu-strip" ;;
|
|
||||||
esac;
|
|
||||||
cd target/${{ matrix.job.target }}/release
|
|
||||||
BINARY_NAME=gman
|
|
||||||
# strip the binary
|
|
||||||
"$STRIP" "$BINARY_NAME"
|
|
||||||
RELEASE_NAME=gman-${{ matrix.job.artifact_suffix }}
|
|
||||||
tar czvf $RELEASE_NAME.tar.gz $BINARY_NAME
|
|
||||||
# create sha checksum files
|
|
||||||
shasum -a 256 $RELEASE_NAME.tar.gz > $RELEASE_NAME.sha256
|
|
||||||
echo "RELEASE_NAME=$RELEASE_NAME" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
- name: Add artifacts
|
|
||||||
run: |
|
|
||||||
mkdir -p artifacts
|
|
||||||
cp target/${{ matrix.job.target }}/release/${{ env.RELEASE_NAME }}.tar.gz artifacts/
|
|
||||||
cp target/${{ matrix.job.target }}/release/${{ env.RELEASE_NAME }}.sha256 artifacts/
|
|
||||||
|
|
||||||
- name: Upload artifacts
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: artifacts-${{ env.RELEASE_NAME }}
|
|
||||||
path: artifacts
|
|
||||||
overwrite: true
|
|
||||||
|
|
||||||
publish-github-release:
|
|
||||||
name: publish-github-release
|
|
||||||
needs: [build-release-artifacts]
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Check if actor is repository owner
|
|
||||||
if: ${{ github.actor != github.repository_owner && env.ACT != 'true' }}
|
|
||||||
run: |
|
|
||||||
echo "You are not authorized to run this workflow."
|
|
||||||
exit 1
|
|
||||||
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- name: Download all artifacts
|
- name: Download all artifacts
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v4
|
||||||
with:
|
with:
|
||||||
@@ -355,114 +217,145 @@ jobs:
|
|||||||
git pull
|
git pull
|
||||||
|
|
||||||
- name: Set environment variables
|
- name: Set environment variables
|
||||||
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
release_version="$(cat ./artifacts/release-version)"
|
release_version="$(cat ./artifacts/release-version)"
|
||||||
echo "RELEASE_VERSION=$release_version" >> $GITHUB_ENV
|
echo "RELEASE_VERSION=$release_version" >> $GITHUB_ENV
|
||||||
changelog_body="$(cat ./artifacts/changelog.md)"
|
|
||||||
echo "changelog_body=$(cat artifacts/changelog.md)" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
- name: Validate release environment variables
|
- name: Validate release environment variables
|
||||||
run: |
|
run: |
|
||||||
echo "Release version: ${{ env.RELEASE_VERSION }}"
|
echo "Release version: ${{ env.RELEASE_VERSION }}"
|
||||||
echo "Changelog body: ${{ env.changelog_body }}"
|
echo "Changelog body: $(cat artifacts/changelog.md)"
|
||||||
|
|
||||||
- name: Create a GitHub Release
|
- name: Get bumped Cargo files (Act)
|
||||||
|
if: env.ACT == 'true'
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
name: bumped-cargo-files
|
||||||
|
path: ${{ github.workspace }}
|
||||||
|
|
||||||
|
- uses: dtolnay/rust-toolchain@stable
|
||||||
|
name: Set Rust toolchain
|
||||||
|
with:
|
||||||
|
targets: ${{ matrix.target }}
|
||||||
|
|
||||||
|
- name: Install cross
|
||||||
|
if: matrix.use-cross
|
||||||
|
uses: taiki-e/install-action@v2
|
||||||
|
with:
|
||||||
|
tool: cross
|
||||||
|
|
||||||
|
- name: Overwrite build command env variable
|
||||||
|
if: matrix.use-cross
|
||||||
|
shell: bash
|
||||||
|
run: echo "BUILD_CMD=cross" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Install latest LLVM/Clang
|
||||||
|
if: matrix.os == 'ubuntu-latest'
|
||||||
|
run: |
|
||||||
|
wget https://apt.llvm.org/llvm.sh
|
||||||
|
chmod +x llvm.sh
|
||||||
|
# omit the version to get the latest stable for your Ubuntu (24.04 "noble" on ubuntu-latest)
|
||||||
|
sudo ./llvm.sh all
|
||||||
|
# ensure libclang dev package is present (adjust the "22" if a newer major exists)
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y libclang-20-dev libclang-dev
|
||||||
|
|
||||||
|
- 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: Build
|
||||||
|
shell: bash
|
||||||
|
run: $BUILD_CMD build --locked --release --target=${{ matrix.target }} ${{ matrix.cargo-flags }}
|
||||||
|
|
||||||
|
- name: Verify file
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
file target/${{ matrix.target }}/release/gman
|
||||||
|
|
||||||
|
- name: Test
|
||||||
|
if: matrix.target != 'aarch64-apple-darwin' && matrix.target != 'aarch64-pc-windows-msvc'
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
set -euxo pipefail
|
||||||
|
if [[ "${{ matrix.use-cross || 'false' }}" == 'true' ]]; then
|
||||||
|
cross test --release --locked --target=${{ matrix.target }} --verbose
|
||||||
|
else
|
||||||
|
cargo test --release --locked --target=${{ matrix.target }} --verbose
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Build Archive
|
||||||
|
shell: bash
|
||||||
|
id: package
|
||||||
|
env:
|
||||||
|
target: ${{ matrix.target }}
|
||||||
|
run: |
|
||||||
|
set -euxo pipefail
|
||||||
|
|
||||||
|
bin=${GITHUB_REPOSITORY##*/}
|
||||||
|
dist_dir=`pwd`/dist
|
||||||
|
name=$bin-$target
|
||||||
|
executable=target/$target/release/$bin
|
||||||
|
|
||||||
|
if [[ "$RUNNER_OS" == "Windows" ]]; then
|
||||||
|
executable=$executable.exe
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir $dist_dir
|
||||||
|
cp $executable $dist_dir
|
||||||
|
cd $dist_dir
|
||||||
|
|
||||||
|
if [[ "$RUNNER_OS" == "Windows" ]]; then
|
||||||
|
archive=$dist_dir/$name.zip
|
||||||
|
sha=$dist_dir/$name.sha256
|
||||||
|
7z a $archive *
|
||||||
|
certutil -hashfile $archive sha256 | grep -E [A-Fa-f0-9]{64} > $sha
|
||||||
|
echo "archive=dist/$name.zip" >> $GITHUB_OUTPUT
|
||||||
|
echo "sha=dist/$name.sha256" >> $GITHUB_OUTPUT
|
||||||
|
else
|
||||||
|
archive=$dist_dir/$name.tar.gz
|
||||||
|
sha=$dist_dir/$name.sha256
|
||||||
|
tar -czf $archive *
|
||||||
|
shasum -a 256 $archive > $sha
|
||||||
|
echo "archive=dist/$name.tar.gz" >> $GITHUB_OUTPUT
|
||||||
|
echo "sha=dist/$name.sha256" >> $GITHUB_OUTPUT
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Publish Archive and SHA
|
||||||
if: env.ACT != 'true'
|
if: env.ACT != 'true'
|
||||||
uses: softprops/action-gh-release@v1
|
uses: softprops/action-gh-release@v2
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
with:
|
with:
|
||||||
files: |
|
files: |
|
||||||
artifacts/gman-macos-arm64.tar.gz
|
${{ steps.package.outputs.archive }}
|
||||||
artifacts/gman-macos-arm64.sha256
|
${{ steps.package.outputs.sha }}
|
||||||
artifacts/gman-macos.tar.gz
|
|
||||||
artifacts/gman-macos.sha256
|
|
||||||
artifacts/gman-windows.tar.gz
|
|
||||||
artifacts/gman-windows.sha256
|
|
||||||
artifacts/gman-windows-aarch64.tar.gz
|
|
||||||
artifacts/gman-windows-aarch64.sha256
|
|
||||||
artifacts/gman-linux.tar.gz
|
|
||||||
artifacts/gman-linux.sha256
|
|
||||||
artifacts/gman-linux-musl.tar.gz
|
|
||||||
artifacts/gman-linux-musl.sha256
|
|
||||||
artifacts/gman-aarch64-gnu.tar.gz
|
|
||||||
artifacts/gman-aarch64-gnu.sha256
|
|
||||||
artifacts/gman-aarch64-musl.tar.gz
|
|
||||||
artifacts/gman-aarch64-musl.sha256
|
|
||||||
artifacts/gman-armv6-gnu.tar.gz
|
|
||||||
artifacts/gman-armv6-gnu.sha256
|
|
||||||
artifacts/gman-armv6-musl.tar.gz
|
|
||||||
artifacts/gman-armv6-musl.sha256
|
|
||||||
artifacts/gman-armv7-gnu.tar.gz
|
|
||||||
artifacts/gman-armv7-gnu.sha256
|
|
||||||
artifacts/gman-armv7-musl.tar.gz
|
|
||||||
artifacts/gman-armv7-musl.sha256
|
|
||||||
tag_name: v${{ env.RELEASE_VERSION }}
|
tag_name: v${{ env.RELEASE_VERSION }}
|
||||||
name: 'v${{ env.RELEASE_VERSION }}'
|
name: "v${{ env.RELEASE_VERSION }}"
|
||||||
body: ${{ env.changelog_body }}
|
body_path: artifacts/changelog.md
|
||||||
draft: false
|
|
||||||
prerelease: false
|
prerelease: false
|
||||||
|
|
||||||
|
- name: Add artifacts
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
[[ -d artifacts ]] || mkdir -p artifacts
|
||||||
|
cp ${{ steps.package.outputs.archive }} artifacts/
|
||||||
|
cp ${{ steps.package.outputs.sha }} artifacts/
|
||||||
|
|
||||||
- name: Upload artifacts
|
- name: Upload artifacts
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
|
name: artifacts-v${{ env.RELEASE_VERSION }}-${{ matrix.target }}
|
||||||
path: artifacts
|
path: artifacts
|
||||||
overwrite: true
|
overwrite: true
|
||||||
|
|
||||||
publish-chocolatey-package:
|
|
||||||
needs: [publish-github-release]
|
|
||||||
name: Publish Chocolatey Package
|
|
||||||
runs-on: windows-latest
|
|
||||||
steps:
|
|
||||||
- name: Check if actor is repository owner
|
|
||||||
if: ${{ github.actor != github.repository_owner && env.ACT != 'true' }}
|
|
||||||
run: |
|
|
||||||
echo "You are not authorized to run this workflow."
|
|
||||||
exit 1
|
|
||||||
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 1
|
|
||||||
|
|
||||||
- name: Get release artifacts
|
|
||||||
uses: actions/download-artifact@v4
|
|
||||||
with:
|
|
||||||
path: artifacts
|
|
||||||
merge-multiple: true
|
|
||||||
|
|
||||||
- name: Set release assets and version
|
|
||||||
shell: pwsh
|
|
||||||
run: |
|
|
||||||
# Read the first column from the SHA256 file
|
|
||||||
$windows_sha = Get-Content ./artifacts/gman-windows.sha256 | ForEach-Object { $_.Split(' ')[0] }
|
|
||||||
Add-Content -Path $env:GITHUB_ENV -Value "WINDOWS_SHA=$windows_sha"
|
|
||||||
|
|
||||||
# Read the release version from the release-version file
|
|
||||||
$release_version = Get-Content ./artifacts/release-version
|
|
||||||
Add-Content -Path $env:GITHUB_ENV -Value "RELEASE_VERSION=$release_version"
|
|
||||||
|
|
||||||
- name: Validate release environment variables
|
|
||||||
run: |
|
|
||||||
echo "Release SHA windows: ${{ env.WINDOWS_SHA }}"
|
|
||||||
echo "Release version: ${{ env.RELEASE_VERSION }}"
|
|
||||||
|
|
||||||
- name: Package and Publish package to Chocolatey
|
|
||||||
if: env.ACT != 'true'
|
|
||||||
run: |
|
|
||||||
mkdir ./deployment/chocolatey/tools
|
|
||||||
# Run packaging script
|
|
||||||
python "./deployment/chocolatey/packager.py" ${{ env.RELEASE_VERSION }} "./deployment/chocolatey/gman.nuspec.template" "./deployment/chocolatey/gman.nuspec" ${{ env.WINDOWS_SHA }}
|
|
||||||
python "./deployment/chocolatey/packager.py" ${{ env.RELEASE_VERSION }} "./deployment/chocolatey/chocolateyinstall.ps1.template" "./deployment/chocolatey/tools/chocolateyinstall.ps1" ${{ env.WINDOWS_SHA }}
|
|
||||||
|
|
||||||
# Publish to Chocolatey
|
|
||||||
cd ./deployment/chocolatey
|
|
||||||
choco pack
|
|
||||||
echo y | choco install gman -dv -s .
|
|
||||||
$version = gman --version
|
|
||||||
$version = $version -replace " ", "."
|
|
||||||
choco push $version.nupkg -s https://push.chocolatey.org/ --api-key ${{ secrets.CHOCOLATEY_API_KEY }};
|
|
||||||
|
|
||||||
publish-homebrew-formula:
|
publish-homebrew-formula:
|
||||||
needs: [publish-github-release]
|
needs: [publish-github-release]
|
||||||
name: Update Homebrew formulas
|
name: Update Homebrew formulas
|
||||||
@@ -489,11 +382,11 @@ jobs:
|
|||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
# Set environment variables
|
# Set environment variables
|
||||||
macos_sha="$(cat ./artifacts/gman-macos.sha256 | awk '{print $1}')"
|
macos_sha="$(cat ./artifacts/gman-x86_64-apple-darwin.sha256 | awk '{print $1}')"
|
||||||
echo "MACOS_SHA=$macos_sha" >> $GITHUB_ENV
|
echo "MACOS_SHA=$macos_sha" >> $GITHUB_ENV
|
||||||
macos_sha_arm="$(cat ./artifacts/gman-macos-arm64.sha256 | awk '{print $1}')"
|
macos_sha_arm="$(cat ./artifacts/gman-aarch64-apple-darwin.sha256 | awk '{print $1}')"
|
||||||
echo "MACOS_SHA_ARM=$macos_sha_arm" >> $GITHUB_ENV
|
echo "MACOS_SHA_ARM=$macos_sha_arm" >> $GITHUB_ENV
|
||||||
linux_sha="$(cat ./artifacts/gman-linux-musl.sha256 | awk '{print $1}')"
|
linux_sha="$(cat ./artifacts/gman-x86_64-unknown-linux-musl.sha256 | awk '{print $1}')"
|
||||||
echo "LINUX_SHA=$linux_sha" >> $GITHUB_ENV
|
echo "LINUX_SHA=$linux_sha" >> $GITHUB_ENV
|
||||||
release_version="$(cat ./artifacts/release-version)"
|
release_version="$(cat ./artifacts/release-version)"
|
||||||
echo "RELEASE_VERSION=$release_version" >> $GITHUB_ENV
|
echo "RELEASE_VERSION=$release_version" >> $GITHUB_ENV
|
||||||
@@ -556,17 +449,6 @@ jobs:
|
|||||||
git fetch --all
|
git fetch --all
|
||||||
git pull
|
git pull
|
||||||
|
|
||||||
- uses: actions/cache@v3
|
|
||||||
name: Cache Cargo registry
|
|
||||||
with:
|
|
||||||
path: ~/.cargo/registry
|
|
||||||
key: ${{ runner.os }}-cargo-registry-${{ hashFiles('Cargo.lock') }}
|
|
||||||
|
|
||||||
- uses: actions/cache@v3
|
|
||||||
with:
|
|
||||||
path: ~/.cargo/bin
|
|
||||||
key: ${{ runner.os }}-cargo-bin-${{ hashFiles('.github/workflows/release.yml') }}
|
|
||||||
|
|
||||||
- name: Install Rust stable
|
- name: Install Rust stable
|
||||||
uses: dtolnay/rust-toolchain@stable
|
uses: dtolnay/rust-toolchain@stable
|
||||||
|
|
||||||
|
|||||||
+47
-2
@@ -7,5 +7,50 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
## [0.0.1] - 2025-09-10
|
## [0.0.1] - 2025-09-10
|
||||||
|
|
||||||
### Other
|
## v0.2.0 (2025-09-29)
|
||||||
- Initial test release of the `gman` project.
|
|
||||||
|
### Feat
|
||||||
|
|
||||||
|
- gopass support
|
||||||
|
- Added command aliases to make the CLI more universal
|
||||||
|
- Added dynamic tab completions for the profile, providers, and the secrets in any given secret manager
|
||||||
|
- Users can now specify a default provider to use with each run config, so they don't need to explicitly specify which to use when wanting to run different applications.
|
||||||
|
|
||||||
|
## v0.1.0 (2025-09-17)
|
||||||
|
|
||||||
|
### Feat
|
||||||
|
|
||||||
|
- Subcommand to edit the config directly instead of having to find the file
|
||||||
|
|
||||||
|
### Fix
|
||||||
|
|
||||||
|
- improved user messages for local provider sync set up
|
||||||
|
- Pass the changelog to the GHA properly using a file
|
||||||
|
- Potential bug in changelog variable generation
|
||||||
|
- Revert back hacky stuff so I can test with act now
|
||||||
|
- Attempting to use pre-generated bindgens for the aws-lc-sys library
|
||||||
|
- Install openSSL differently to make this work
|
||||||
|
- Address edge case for unknown_musl targets
|
||||||
|
- Install LLVM prereqs for release flow
|
||||||
|
- Updated the release flow to install the external bindgen-cli
|
||||||
|
|
||||||
|
## v0.0.1 (2025-09-12)
|
||||||
|
|
||||||
|
### Feat
|
||||||
|
|
||||||
|
- Azure Key Vault support
|
||||||
|
- GCP Secret Manager support
|
||||||
|
- Full AWS SecretsManager support
|
||||||
|
- AWS Secrets Manager support
|
||||||
|
- Added two new flags to output where gman writes logs to and where it expects the config file to live
|
||||||
|
|
||||||
|
### Fix
|
||||||
|
|
||||||
|
- Made the vault file location more fault tolerant
|
||||||
|
- Attempting to maybe be a bit more explicit about config file handling to fix MacOS tests
|
||||||
|
|
||||||
|
### Refactor
|
||||||
|
|
||||||
|
- Refactor configuration structs directly into the provider definition to simplify validation, structs, and future extensions
|
||||||
|
- Made the creation of the log directories a bit more fault tolerant
|
||||||
|
- Renamed the provider field in a config file to type to make things a little easier to understand; also removed husky
|
||||||
|
|||||||
Generated
+119
-70
@@ -438,7 +438,7 @@ dependencies = [
|
|||||||
"rustls-native-certs 0.8.1",
|
"rustls-native-certs 0.8.1",
|
||||||
"rustls-pki-types",
|
"rustls-pki-types",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-rustls 0.26.2",
|
"tokio-rustls 0.26.3",
|
||||||
"tower",
|
"tower",
|
||||||
"tracing",
|
"tracing",
|
||||||
]
|
]
|
||||||
@@ -921,11 +921,14 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap_complete"
|
name = "clap_complete"
|
||||||
version = "4.5.57"
|
version = "4.5.58"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4d9501bd3f5f09f7bbee01da9a511073ed30a80cd7a509f1214bb74eadea71ad"
|
checksum = "75bf0b32ad2e152de789bb635ea4d3078f6b838ad7974143e99b99f45a04af4a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"clap",
|
"clap",
|
||||||
|
"clap_lex",
|
||||||
|
"is_executable",
|
||||||
|
"shlex",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1306,6 +1309,12 @@ version = "1.0.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0"
|
checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "env_home"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c7f84e12ccf0a7ddc17a6c41c93326024c42920d7ee630d04950e6926645c0fe"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "equivalent"
|
name = "equivalent"
|
||||||
version = "1.0.2"
|
version = "1.0.2"
|
||||||
@@ -1501,9 +1510,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "gcloud-sdk"
|
name = "gcloud-sdk"
|
||||||
version = "0.28.1"
|
version = "0.28.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "41dcccf7c0cc0986cb5f476854a5c63b95bab4835f12884704f6aa33ac7d14bc"
|
checksum = "7fe603014c94ee883f514ea12a3df0ad99da67124472bd3c52ee7fed0ccd4ea1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"bytes",
|
"bytes",
|
||||||
@@ -1559,7 +1568,7 @@ dependencies = [
|
|||||||
"cfg-if",
|
"cfg-if",
|
||||||
"libc",
|
"libc",
|
||||||
"r-efi",
|
"r-efi",
|
||||||
"wasi 0.14.5+wasi-0.2.4",
|
"wasi 0.14.7+wasi-0.2.4",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1576,7 +1585,7 @@ checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "gman"
|
name = "gman"
|
||||||
version = "0.1.0"
|
version = "0.2.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"argon2",
|
"argon2",
|
||||||
@@ -1604,6 +1613,7 @@ dependencies = [
|
|||||||
"indoc",
|
"indoc",
|
||||||
"log",
|
"log",
|
||||||
"log4rs",
|
"log4rs",
|
||||||
|
"once_cell",
|
||||||
"openssl",
|
"openssl",
|
||||||
"predicates",
|
"predicates",
|
||||||
"pretty_assertions",
|
"pretty_assertions",
|
||||||
@@ -1618,6 +1628,7 @@ dependencies = [
|
|||||||
"tempfile",
|
"tempfile",
|
||||||
"tokio",
|
"tokio",
|
||||||
"validator",
|
"validator",
|
||||||
|
"which",
|
||||||
"zeroize",
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -1633,7 +1644,7 @@ dependencies = [
|
|||||||
"futures-sink",
|
"futures-sink",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"http 0.2.12",
|
"http 0.2.12",
|
||||||
"indexmap 2.11.1",
|
"indexmap 2.11.3",
|
||||||
"slab",
|
"slab",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-util",
|
"tokio-util",
|
||||||
@@ -1652,7 +1663,7 @@ dependencies = [
|
|||||||
"futures-core",
|
"futures-core",
|
||||||
"futures-sink",
|
"futures-sink",
|
||||||
"http 1.3.1",
|
"http 1.3.1",
|
||||||
"indexmap 2.11.1",
|
"indexmap 2.11.3",
|
||||||
"slab",
|
"slab",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-util",
|
"tokio-util",
|
||||||
@@ -1858,7 +1869,7 @@ dependencies = [
|
|||||||
"rustls-native-certs 0.8.1",
|
"rustls-native-certs 0.8.1",
|
||||||
"rustls-pki-types",
|
"rustls-pki-types",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-rustls 0.26.2",
|
"tokio-rustls 0.26.3",
|
||||||
"tower-service",
|
"tower-service",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -1893,9 +1904,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hyper-util"
|
name = "hyper-util"
|
||||||
version = "0.1.16"
|
version = "0.1.17"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8d9b05277c7e8da2c93a568989bb6207bef0112e8d17df7a6eda4a3cf143bc5e"
|
checksum = "3c6995591a8f1380fcb4ba966a252a4b29188d51d2b89e3a252f5305be65aea8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64 0.22.1",
|
"base64 0.22.1",
|
||||||
"bytes",
|
"bytes",
|
||||||
@@ -2065,13 +2076,14 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "indexmap"
|
name = "indexmap"
|
||||||
version = "2.11.1"
|
version = "2.11.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "206a8042aec68fa4a62e8d3f7aa4ceb508177d9324faf261e1959e495b7a1921"
|
checksum = "92119844f513ffa41556430369ab02c295a3578af21cf945caa3e9e0c2481ac3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"equivalent",
|
"equivalent",
|
||||||
"hashbrown 0.15.5",
|
"hashbrown 0.15.5",
|
||||||
"serde",
|
"serde",
|
||||||
|
"serde_core",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2116,6 +2128,15 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "is_executable"
|
||||||
|
version = "1.0.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "baabb8b4867b26294d818bf3f651a454b6901431711abb96e296245888d6e8c4"
|
||||||
|
dependencies = [
|
||||||
|
"windows-sys 0.60.2",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "is_terminal_polyfill"
|
name = "is_terminal_polyfill"
|
||||||
version = "1.70.1"
|
version = "1.70.1"
|
||||||
@@ -2158,9 +2179,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "js-sys"
|
name = "js-sys"
|
||||||
version = "0.3.78"
|
version = "0.3.80"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0c0b063578492ceec17683ef2f8c5e89121fbd0b172cbc280635ab7567db2738"
|
checksum = "852f13bec5eba4ba9afbeb93fd7c13fe56147f055939ae21c43a29a0ecb2702e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
@@ -2205,9 +2226,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libredox"
|
name = "libredox"
|
||||||
version = "0.1.9"
|
version = "0.1.10"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "391290121bad3d37fbddad76d8f5d1c1c314cfc646d143d7e07a3086ddff0ce3"
|
checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags",
|
||||||
"libc",
|
"libc",
|
||||||
@@ -2623,12 +2644,12 @@ checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "plist"
|
name = "plist"
|
||||||
version = "1.7.4"
|
version = "1.8.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3af6b589e163c5a788fab00ce0c0366f6efbb9959c2f9874b224936af7fce7e1"
|
checksum = "740ebea15c5d1428f910cd1a5f52cebf8d25006245ed8ade92702f4943d91e07"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64 0.22.1",
|
"base64 0.22.1",
|
||||||
"indexmap 2.11.1",
|
"indexmap 2.11.3",
|
||||||
"quick-xml",
|
"quick-xml",
|
||||||
"serde",
|
"serde",
|
||||||
"time",
|
"time",
|
||||||
@@ -2987,7 +3008,7 @@ dependencies = [
|
|||||||
"sync_wrapper",
|
"sync_wrapper",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-native-tls",
|
"tokio-native-tls",
|
||||||
"tokio-rustls 0.26.2",
|
"tokio-rustls 0.26.3",
|
||||||
"tokio-util",
|
"tokio-util",
|
||||||
"tower",
|
"tower",
|
||||||
"tower-http",
|
"tower-http",
|
||||||
@@ -3090,7 +3111,7 @@ dependencies = [
|
|||||||
"log",
|
"log",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"rustls-pki-types",
|
"rustls-pki-types",
|
||||||
"rustls-webpki 0.103.5",
|
"rustls-webpki 0.103.6",
|
||||||
"subtle",
|
"subtle",
|
||||||
"zeroize",
|
"zeroize",
|
||||||
]
|
]
|
||||||
@@ -3149,9 +3170,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustls-webpki"
|
name = "rustls-webpki"
|
||||||
version = "0.103.5"
|
version = "0.103.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b5a37813727b78798e53c2bec3f5e8fe12a6d6f8389bf9ca7802add4c9905ad8"
|
checksum = "8572f3c2cb9934231157b45499fc41e1f58c589fdfb81a844ba873265e80f8eb"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aws-lc-rs",
|
"aws-lc-rs",
|
||||||
"ring",
|
"ring",
|
||||||
@@ -3290,16 +3311,17 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "semver"
|
name = "semver"
|
||||||
version = "1.0.26"
|
version = "1.0.27"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0"
|
checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
version = "1.0.219"
|
version = "1.0.225"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
|
checksum = "fd6c24dee235d0da097043389623fb913daddf92c76e9f5a1db88607a0bcbd1d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"serde_core",
|
||||||
"serde_derive",
|
"serde_derive",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -3314,10 +3336,19 @@ dependencies = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_derive"
|
name = "serde_core"
|
||||||
version = "1.0.219"
|
version = "1.0.225"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
|
checksum = "659356f9a0cb1e529b24c01e43ad2bdf520ec4ceaf83047b83ddcc2251f96383"
|
||||||
|
dependencies = [
|
||||||
|
"serde_derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_derive"
|
||||||
|
version = "1.0.225"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0ea936adf78b1f766949a4977b91d2f5595825bd6ec079aa9543ad2685fc4516"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -3326,23 +3357,24 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_json"
|
name = "serde_json"
|
||||||
version = "1.0.143"
|
version = "1.0.145"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d401abef1d108fbd9cbaebc3e46611f4b1021f714a0597a71f41ee463f5f4a5a"
|
checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"itoa",
|
"itoa",
|
||||||
"memchr",
|
"memchr",
|
||||||
"ryu",
|
"ryu",
|
||||||
"serde",
|
"serde",
|
||||||
|
"serde_core",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_spanned"
|
name = "serde_spanned"
|
||||||
version = "1.0.0"
|
version = "1.0.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "40734c41988f7306bb04f0ecf60ec0f3f1caa34290e4e8ea471dcd3346483b83"
|
checksum = "2789234a13a53fc4be1b51ea1bab45a3c338bdb884862a257d10e5a74ae009e6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde",
|
"serde_core",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -3367,7 +3399,7 @@ dependencies = [
|
|||||||
"chrono",
|
"chrono",
|
||||||
"hex",
|
"hex",
|
||||||
"indexmap 1.9.3",
|
"indexmap 1.9.3",
|
||||||
"indexmap 2.11.1",
|
"indexmap 2.11.3",
|
||||||
"schemars 0.9.0",
|
"schemars 0.9.0",
|
||||||
"schemars 1.0.4",
|
"schemars 1.0.4",
|
||||||
"serde",
|
"serde",
|
||||||
@@ -3395,7 +3427,7 @@ version = "0.9.34+deprecated"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47"
|
checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"indexmap 2.11.1",
|
"indexmap 2.11.3",
|
||||||
"itoa",
|
"itoa",
|
||||||
"ryu",
|
"ryu",
|
||||||
"serde",
|
"serde",
|
||||||
@@ -3701,9 +3733,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio-rustls"
|
name = "tokio-rustls"
|
||||||
version = "0.26.2"
|
version = "0.26.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b"
|
checksum = "05f63835928ca123f1bef57abbcd23bb2ba0ac9ae1235f1e65bda0d06e7786bd"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"rustls 0.23.31",
|
"rustls 0.23.31",
|
||||||
"tokio",
|
"tokio",
|
||||||
@@ -3735,11 +3767,11 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "toml"
|
name = "toml"
|
||||||
version = "0.9.5"
|
version = "0.9.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "75129e1dc5000bfbaa9fee9d1b21f974f9fbad9daec557a521ee6e080825f6e8"
|
checksum = "ae2a4cf385da23d1d53bc15cdfa5c2109e93d8d362393c801e87da2f72f0e201"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde",
|
"serde_core",
|
||||||
"serde_spanned",
|
"serde_spanned",
|
||||||
"toml_datetime",
|
"toml_datetime",
|
||||||
"toml_writer",
|
"toml_writer",
|
||||||
@@ -3747,11 +3779,11 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "toml_datetime"
|
name = "toml_datetime"
|
||||||
version = "0.7.0"
|
version = "0.7.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bade1c3e902f58d73d3f294cd7f20391c1cb2fbcb643b73566bc773971df91e3"
|
checksum = "a197c0ec7d131bfc6f7e82c8442ba1595aeab35da7adbf05b6b73cd06a16b6be"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde",
|
"serde_core",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -3783,7 +3815,7 @@ dependencies = [
|
|||||||
"socket2 0.6.0",
|
"socket2 0.6.0",
|
||||||
"sync_wrapper",
|
"sync_wrapper",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-rustls 0.26.2",
|
"tokio-rustls 0.26.3",
|
||||||
"tokio-stream",
|
"tokio-stream",
|
||||||
"tower",
|
"tower",
|
||||||
"tower-layer",
|
"tower-layer",
|
||||||
@@ -3810,7 +3842,7 @@ checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"indexmap 2.11.1",
|
"indexmap 2.11.3",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"slab",
|
"slab",
|
||||||
"sync_wrapper",
|
"sync_wrapper",
|
||||||
@@ -4135,27 +4167,27 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasi"
|
name = "wasi"
|
||||||
version = "0.14.5+wasi-0.2.4"
|
version = "0.14.7+wasi-0.2.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a4494f6290a82f5fe584817a676a34b9d6763e8d9d18204009fb31dceca98fd4"
|
checksum = "883478de20367e224c0090af9cf5f9fa85bed63a95c1abf3afc5c083ebc06e8c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"wasip2",
|
"wasip2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasip2"
|
name = "wasip2"
|
||||||
version = "1.0.0+wasi-0.2.4"
|
version = "1.0.1+wasi-0.2.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "03fa2761397e5bd52002cd7e73110c71af2109aca4e521a9f40473fe685b0a24"
|
checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"wit-bindgen",
|
"wit-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen"
|
name = "wasm-bindgen"
|
||||||
version = "0.2.101"
|
version = "0.2.103"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7e14915cadd45b529bb8d1f343c4ed0ac1de926144b746e2710f9cd05df6603b"
|
checksum = "ab10a69fbd0a177f5f649ad4d8d3305499c42bab9aef2f7ff592d0ec8f833819"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
@@ -4166,9 +4198,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-backend"
|
name = "wasm-bindgen-backend"
|
||||||
version = "0.2.101"
|
version = "0.2.103"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e28d1ba982ca7923fd01448d5c30c6864d0a14109560296a162f80f305fb93bb"
|
checksum = "0bb702423545a6007bbc368fde243ba47ca275e549c8a28617f56f6ba53b1d1c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bumpalo",
|
"bumpalo",
|
||||||
"log",
|
"log",
|
||||||
@@ -4180,9 +4212,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-futures"
|
name = "wasm-bindgen-futures"
|
||||||
version = "0.4.51"
|
version = "0.4.53"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0ca85039a9b469b38336411d6d6ced91f3fc87109a2a27b0c197663f5144dffe"
|
checksum = "a0b221ff421256839509adbb55998214a70d829d3a28c69b4a6672e9d2a42f67"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
@@ -4193,9 +4225,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-macro"
|
name = "wasm-bindgen-macro"
|
||||||
version = "0.2.101"
|
version = "0.2.103"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7c3d463ae3eff775b0c45df9da45d68837702ac35af998361e2c84e7c5ec1b0d"
|
checksum = "fc65f4f411d91494355917b605e1480033152658d71f722a90647f56a70c88a0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"quote",
|
"quote",
|
||||||
"wasm-bindgen-macro-support",
|
"wasm-bindgen-macro-support",
|
||||||
@@ -4203,9 +4235,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-macro-support"
|
name = "wasm-bindgen-macro-support"
|
||||||
version = "0.2.101"
|
version = "0.2.103"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7bb4ce89b08211f923caf51d527662b75bdc9c9c7aab40f86dcb9fb85ac552aa"
|
checksum = "ffc003a991398a8ee604a401e194b6b3a39677b3173d6e74495eb51b82e99a32"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -4216,9 +4248,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-shared"
|
name = "wasm-bindgen-shared"
|
||||||
version = "0.2.101"
|
version = "0.2.103"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f143854a3b13752c6950862c906306adb27c7e839f7414cec8fea35beab624c1"
|
checksum = "293c37f4efa430ca14db3721dfbe48d8c33308096bd44d80ebaa775ab71ba1cf"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
@@ -4238,14 +4270,25 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "web-sys"
|
name = "web-sys"
|
||||||
version = "0.3.78"
|
version = "0.3.80"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "77e4b637749ff0d92b8fad63aa1f7cff3cbe125fd49c175cd6345e7272638b12"
|
checksum = "fbe734895e869dc429d78c4b433f8d17d95f8d05317440b4fad5ab2d33e596dc"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "which"
|
||||||
|
version = "8.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d3fabb953106c3c8eea8306e4393700d7657561cb43122571b172bbfb7c7ba1d"
|
||||||
|
dependencies = [
|
||||||
|
"env_home",
|
||||||
|
"rustix",
|
||||||
|
"winsafe",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winapi"
|
name = "winapi"
|
||||||
version = "0.3.9"
|
version = "0.3.9"
|
||||||
@@ -4499,10 +4542,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486"
|
checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wit-bindgen"
|
name = "winsafe"
|
||||||
version = "0.45.1"
|
version = "0.0.19"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5c573471f125075647d03df72e026074b7203790d41351cd6edc96f46bcccd36"
|
checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wit-bindgen"
|
||||||
|
version = "0.46.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "writeable"
|
name = "writeable"
|
||||||
|
|||||||
+13
-6
@@ -1,10 +1,10 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "gman"
|
name = "gman"
|
||||||
version = "0.0.6"
|
version = "0.2.0"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
authors = ["Alex Clarke <alex.j.tusa@gmail.com>"]
|
authors = ["Alex Clarke <alex.j.tusa@gmail.com>"]
|
||||||
description = "Universal secret management and injection tool"
|
description = "Universal command line secret management and injection tool"
|
||||||
keywords = ["cli", "secrets", "credentials", "command-line", "encryption"]
|
keywords = ["cli", "secrets-manager", "secret-injection", "command-runner", "vault"]
|
||||||
documentation = "https://github.com/Dark-Alex-17/gman"
|
documentation = "https://github.com/Dark-Alex-17/gman"
|
||||||
repository = "https://github.com/Dark-Alex-17/gman"
|
repository = "https://github.com/Dark-Alex-17/gman"
|
||||||
homepage = "https://github.com/Dark-Alex-17/gman"
|
homepage = "https://github.com/Dark-Alex-17/gman"
|
||||||
@@ -25,7 +25,7 @@ clap = { version = "4.5.47", features = [
|
|||||||
"env",
|
"env",
|
||||||
"wrap_help",
|
"wrap_help",
|
||||||
] }
|
] }
|
||||||
clap_complete = "4.5.57"
|
clap_complete = { version = "4.5.57", features = ["unstable-dynamic"] }
|
||||||
confy = { version = "1.0.0", default-features = false, features = [
|
confy = { version = "1.0.0", default-features = false, features = [
|
||||||
"yaml_conf",
|
"yaml_conf",
|
||||||
] }
|
] }
|
||||||
@@ -58,8 +58,15 @@ gcloud-sdk = { version = "0.28.1", features = [
|
|||||||
crc32c = "0.6.8"
|
crc32c = "0.6.8"
|
||||||
azure_identity = "0.27.0"
|
azure_identity = "0.27.0"
|
||||||
azure_security_keyvault_secrets = "0.6.0"
|
azure_security_keyvault_secrets = "0.6.0"
|
||||||
openssl = { version = "0.10.73", features = ["vendored"] }
|
aws-lc-sys = { version = "0.31.0", features = ["bindgen"] }
|
||||||
aws-lc-sys = { version = "0.31.0", features = ["bindgen", "ssl"] }
|
which = "8.0.0"
|
||||||
|
once_cell = "1.21.3"
|
||||||
|
|
||||||
|
[target.'cfg(all(target_os="linux", target_env="musl"))'.dependencies]
|
||||||
|
openssl = { version = "0.10", features = ["vendored"] }
|
||||||
|
|
||||||
|
[target.'cfg(target_os="macos")'.dependencies]
|
||||||
|
openssl = { version = "0.10", features = ["vendored"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
pretty_assertions = "1.4.1"
|
pretty_assertions = "1.4.1"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# G-Man - Universal Credential Manager
|
# G-Man - Universal Command Line Secret Manager and Injection Tool
|
||||||
|
|
||||||

|

|
||||||

|

|
||||||
@@ -94,7 +94,8 @@ gman aws sts get-caller-identity
|
|||||||
- [AWS Secrets Manager](#provider-aws_secrets_manager)
|
- [AWS Secrets Manager](#provider-aws_secrets_manager)
|
||||||
- [GCP Secret Manager](#provider-gcp_secret_manager)
|
- [GCP Secret Manager](#provider-gcp_secret_manager)
|
||||||
- [Azure Key Vault](#provider-azure_key_vault)
|
- [Azure Key Vault](#provider-azure_key_vault)
|
||||||
- [Run Configurations](#run-configurations)
|
- [Run Configurations](#run-configurations)
|
||||||
|
- [Specifying a Default Provider per Run Config](#specifying-a-default-provider-per-run-config)
|
||||||
- [Environment Variable Secret Injection](#environment-variable-secret-injection)
|
- [Environment Variable Secret Injection](#environment-variable-secret-injection)
|
||||||
- [Inject Secrets via Command-Line Flags](#inject-secrets-via-command-line-flags)
|
- [Inject Secrets via Command-Line Flags](#inject-secrets-via-command-line-flags)
|
||||||
- [Inject Secrets into Files](#inject-secrets-into-files)
|
- [Inject Secrets into Files](#inject-secrets-into-files)
|
||||||
@@ -133,33 +134,31 @@ To upgrade `gman` using Homebrew:
|
|||||||
brew upgrade gman
|
brew upgrade gman
|
||||||
```
|
```
|
||||||
|
|
||||||
### Chocolatey (Windows)
|
### Scripts
|
||||||
The G-Man Chocolatey package is located [here](https://community.chocolatey.org/packages/gman). Please note that validation
|
#### Linux/MacOS (`bash`)
|
||||||
of Chocolatey packages take quite some time, and thus the package may not be available immediately after a new release.
|
You can use the following command to run a bash script that downloads and installs the latest version of `gman` for your
|
||||||
|
OS (Linux/MacOS) and architecture (x86_64/arm64):
|
||||||
|
|
||||||
```powershell
|
```shell
|
||||||
choco install gman
|
curl -fsSL https://raw.githubusercontent.com/Dark-Alex-17/gman/main/install.sh | bash
|
||||||
|
|
||||||
# Some newer releases may require a version number, so you can specify it like so:
|
|
||||||
choco install gman --version=0.1.0
|
|
||||||
```
|
```
|
||||||
|
|
||||||
To upgrade to the latest and greatest version of G-Man:
|
#### Windows/Linux/MacOS (`PowerShell`)
|
||||||
```powershell
|
You can use the following command to run a PowerShell script that downloads and installs the latest version of `gman`
|
||||||
choco upgrade gman
|
for your OS (Windows/Linux/MacOS) and architecture (x86_64/arm64):
|
||||||
|
|
||||||
# To upgrade to a specific version:
|
```powershell
|
||||||
choco upgrade gman --version=0.1.0
|
powershell -NoProfile -ExecutionPolicy Bypass -Command "iwr -useb https://raw.githubusercontent.com/Dark-Alex-17/gman/main/scripts/install_gman.ps1 | iex"
|
||||||
```
|
```
|
||||||
|
|
||||||
### Manual
|
### Manual
|
||||||
Binaries are available on the [releases](https://github.com/Dark-Alex-17/gman/releases) page for the following platforms:
|
Binaries are available on the [releases](https://github.com/Dark-Alex-17/gman/releases) page for the following platforms:
|
||||||
|
|
||||||
| Platform | Architecture(s) |
|
| Platform | Architecture(s) |
|
||||||
|----------------|----------------------------|
|
|----------------|-----------------|
|
||||||
| macOS | x86_64, arm64 |
|
| macOS | x86_64, arm64 |
|
||||||
| Linux GNU/MUSL | x86_64,armv6,armv7,aarch64 |
|
| Linux GNU/MUSL | x86_64, aarch64 |
|
||||||
| Windows | x86_64,aarch64 |
|
| Windows | x86_64, aarch64 |
|
||||||
|
|
||||||
#### Windows Instructions
|
#### Windows Instructions
|
||||||
To use a binary from the releases page on Windows, do the following:
|
To use a binary from the releases page on Windows, do the following:
|
||||||
@@ -176,6 +175,22 @@ To use a binary from the releases page on Linux/MacOS, do the following:
|
|||||||
3. Extract the binary with `tar -C /usr/local/bin -xzf gman-<arch>.tar.gz` (Note: This may require `sudo`)
|
3. Extract the binary with `tar -C /usr/local/bin -xzf gman-<arch>.tar.gz` (Note: This may require `sudo`)
|
||||||
4. Now you can run `gman`!
|
4. Now you can run `gman`!
|
||||||
|
|
||||||
|
### Enable Tab Completion
|
||||||
|
`gman` supports shell tab completion for `bash`, `zsh`, and `fish`. To enable it, run the following command for your
|
||||||
|
shell:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
# Bash
|
||||||
|
echo 'source <(COMPLETE=bash gman)' >> ~/.bashrc
|
||||||
|
# Zsh
|
||||||
|
echo 'source <(COMPLETE=zsh gman)' >> ~/.zshrc
|
||||||
|
# Fish
|
||||||
|
echo 'COMPLETE=fish gman | source' >> ~/.config/fish/config.fish
|
||||||
|
```
|
||||||
|
|
||||||
|
Then restart your shell or `source` the appropriate config file.
|
||||||
|
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
`gman` reads a YAML configuration file located at an OS-specific path:
|
`gman` reads a YAML configuration file located at an OS-specific path:
|
||||||
@@ -247,6 +262,7 @@ documented and added without breaking existing setups. The following table shows
|
|||||||
| [`hashicorp_vault`](https://www.hashicorp.com/en/products/vault) | 🕒 | | |
|
| [`hashicorp_vault`](https://www.hashicorp.com/en/products/vault) | 🕒 | | |
|
||||||
| [`azure_key_vault`](https://azure.microsoft.com/en-us/products/key-vault/) | ✅ | [Azure Key Vault](#provider-azure_key_vault) | |
|
| [`azure_key_vault`](https://azure.microsoft.com/en-us/products/key-vault/) | ✅ | [Azure Key Vault](#provider-azure_key_vault) | |
|
||||||
| [`gcp_secret_manager`](https://cloud.google.com/security/products/secret-manager?hl=en) | ✅ | [GCP Secret Manager](#provider-gcp_secret_manager) | |
|
| [`gcp_secret_manager`](https://cloud.google.com/security/products/secret-manager?hl=en) | ✅ | [GCP Secret Manager](#provider-gcp_secret_manager) | |
|
||||||
|
| [`gopass`](https://www.gopass.pw/) | ✅ | | |
|
||||||
| [`1password`](https://1password.com/) | 🕒 | | |
|
| [`1password`](https://1password.com/) | 🕒 | | |
|
||||||
| [`bitwarden`](https://bitwarden.com/) | 🕒 | | |
|
| [`bitwarden`](https://bitwarden.com/) | 🕒 | | |
|
||||||
| [`dashlane`](https://www.dashlane.com/) | 🕒 | | Waiting for CLI support for adding secrets |
|
| [`dashlane`](https://www.dashlane.com/) | 🕒 | | Waiting for CLI support for adding secrets |
|
||||||
@@ -390,6 +406,26 @@ Important notes:
|
|||||||
- Ensure your identity has the necessary Key Vault permissions (RBAC such as `Key Vault Secrets User`/`Administrator`,
|
- Ensure your identity has the necessary Key Vault permissions (RBAC such as `Key Vault Secrets User`/`Administrator`,
|
||||||
or appropriate access policies) for get/set/list/delete.
|
or appropriate access policies) for get/set/list/delete.
|
||||||
|
|
||||||
|
### Provider: `gopass`
|
||||||
|
The `gopass` provider uses [gopass](https://www.gopass.pw/) as the backing storage location for secrets.
|
||||||
|
|
||||||
|
- Optional: `store` (string) to specify a particular gopass store if you have multiple.
|
||||||
|
|
||||||
|
Configuration example:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
default_provider: gopass
|
||||||
|
providers:
|
||||||
|
- name: gopass
|
||||||
|
type: gopass
|
||||||
|
store: my-store # Optional; if omitted, uses the default configured gopass store
|
||||||
|
```
|
||||||
|
|
||||||
|
Important notes:
|
||||||
|
- Ensure `gopass` is installed and initialized on your system.
|
||||||
|
- Secrets are managed using gopass's native commands; `gman` acts as a wrapper to interface with gopass.
|
||||||
|
- Updates overwrite existing secrets
|
||||||
|
- If no store is specified, the default gopass store is used and `gman sync` will sync with all configured stores.
|
||||||
## Run Configurations
|
## Run Configurations
|
||||||
|
|
||||||
Run configurations (or "profiles") tell `gman` how to inject secrets into a command. Three modes of secret injection are
|
Run configurations (or "profiles") tell `gman` how to inject secrets into a command. Three modes of secret injection are
|
||||||
@@ -406,6 +442,45 @@ will error out and report that it could not find the run config with that name.
|
|||||||
You can manually specify which run configuration to use with the `--profile` flag. Again, if no profile is found with
|
You can manually specify which run configuration to use with the `--profile` flag. Again, if no profile is found with
|
||||||
that name, `gman` will error out.
|
that name, `gman` will error out.
|
||||||
|
|
||||||
|
|
||||||
|
### Specifying a Default Provider per Run Config
|
||||||
|
All run configs also support the `provider` field, which lets you override the default provider for that specific
|
||||||
|
profile. This is useful if you have multiple providers configured and want to use a different one for a specific command
|
||||||
|
, but that provider may not be the `default_provider`, and you don't want to have to specify `--provider` on the command
|
||||||
|
line every time.
|
||||||
|
|
||||||
|
For Example:
|
||||||
|
```yaml
|
||||||
|
default_provider: local
|
||||||
|
run_configs:
|
||||||
|
# `gman aws ...` uses the `aws` provider instead of `local` if no
|
||||||
|
# `--provider` is given.
|
||||||
|
- name: aws
|
||||||
|
# Can be overridden by explicitly specifying a `--provider`
|
||||||
|
provider: aws
|
||||||
|
secrets:
|
||||||
|
- DB_USERNAME
|
||||||
|
- DB_PASSWORD
|
||||||
|
# `gman docker ...` uses the default_provider `local` because no
|
||||||
|
# `provider` is specified.
|
||||||
|
- name: docker
|
||||||
|
secrets:
|
||||||
|
- MY_APP_API_KEY
|
||||||
|
- MY_APP_DB_PASSWORD
|
||||||
|
# `gman managarr ...` uses the `local` provider; This is useful
|
||||||
|
# if you change the default provider to something else.
|
||||||
|
- name: managarr
|
||||||
|
provider: local
|
||||||
|
secrets:
|
||||||
|
- RADARR_API_KEY
|
||||||
|
- SONARR_API_KEY
|
||||||
|
files:
|
||||||
|
- /home/user/.config/managarr/config.yml
|
||||||
|
```
|
||||||
|
|
||||||
|
**Important Note:** Any run config with a `provider` field can be overridden by specifying `--provider` on the command
|
||||||
|
line.
|
||||||
|
|
||||||
### Environment Variable Secret Injection
|
### Environment Variable Secret Injection
|
||||||
|
|
||||||
By default, secrets are injected as environment variables. The two required fields are `name` and `secrets`.
|
By default, secrets are injected as environment variables. The two required fields are `name` and `secrets`.
|
||||||
|
|||||||
@@ -1,20 +0,0 @@
|
|||||||
$ErrorActionPreference = 'Stop';
|
|
||||||
|
|
||||||
$PackageName = 'gman'
|
|
||||||
$toolsDir = "$(Split-Path -parent $MyInvocation.MyCommand.Definition)"
|
|
||||||
$url64 = 'https://github.com/Dark-Alex-17/gman/releases/download/v$version/gman-windows.tar.gz'
|
|
||||||
$checksum64 = '$hash_64'
|
|
||||||
|
|
||||||
$packageArgs = @{
|
|
||||||
packageName = $packageName
|
|
||||||
softwareName = $packageName
|
|
||||||
unzipLocation = $toolsDir
|
|
||||||
fileType = 'exe'
|
|
||||||
url = $url64
|
|
||||||
checksum = $checksum64
|
|
||||||
checksumType = 'sha256'
|
|
||||||
|
|
||||||
}
|
|
||||||
Install-ChocolateyZipPackage @packageArgs
|
|
||||||
$File = Get-ChildItem -File -Path $env:ChocolateyInstall\lib\$packageName\tools\ -Filter *.tar
|
|
||||||
Get-ChocolateyUnzip -fileFullPath $File.FullName -destination $env:ChocolateyInstall\lib\$packageName\tools\
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<!-- Read this before creating packages: https://chocolatey.org/docs/create-packages -->
|
|
||||||
<!-- It is especially important to read the above link to understand additional requirements when publishing packages to the community feed aka dot org (https://chocolatey.org/packages). -->
|
|
||||||
|
|
||||||
<!-- Test your packages in a test environment: https://github.com/chocolatey/chocolatey-test-environment -->
|
|
||||||
|
|
||||||
<!--
|
|
||||||
This is a nuspec. It mostly adheres to https://docs.nuget.org/create/Nuspec-Reference. Chocolatey uses a special version of NuGet.Core that allows us to do more than was initially possible. As such there are certain things to be aware of:
|
|
||||||
|
|
||||||
* the package xmlns schema url may cause issues with nuget.exe
|
|
||||||
* Any of the following elements can ONLY be used by choco tools - projectSourceUrl, docsUrl, mailingListUrl, bugTrackerUrl, packageSourceUrl, provides, conflicts, replaces
|
|
||||||
* nuget.exe can still install packages with those elements but they are ignored. Any authoring tools or commands will error on those elements
|
|
||||||
-->
|
|
||||||
|
|
||||||
<!-- You can embed software files directly into packages, as long as you are not bound by distribution rights. -->
|
|
||||||
<!-- * If you are an organization making private packages, you probably have no issues here -->
|
|
||||||
<!-- * If you are releasing to the community feed, you need to consider distribution rights. -->
|
|
||||||
<!-- Do not remove this test for UTF-8: if “Ω” doesn’t appear as greek uppercase omega letter enclosed in quotation marks, you should use an editor that supports UTF-8, not this one. -->
|
|
||||||
<package xmlns="http://schemas.microsoft.com/packaging/2015/06/nuspec.xsd">
|
|
||||||
<metadata>
|
|
||||||
<!-- == PACKAGE SPECIFIC SECTION == -->
|
|
||||||
<id>gman</id>
|
|
||||||
<version>$version</version>
|
|
||||||
|
|
||||||
<!-- == SOFTWARE SPECIFIC SECTION == -->
|
|
||||||
<!-- This section is about the software itself -->
|
|
||||||
<title>G-Man</title>
|
|
||||||
<authors>Alex Clarke</authors>
|
|
||||||
<projectUrl>https://github.com/Dark-Alex-17/gman</projectUrl>
|
|
||||||
<licenseUrl>https://github.com/Dark-Alex-17/gman/blob/main/LICENSE</licenseUrl>
|
|
||||||
<requireLicenseAcceptance>true</requireLicenseAcceptance>
|
|
||||||
<projectSourceUrl>https://github.com/Dark-Alex-17/gman</projectSourceUrl>
|
|
||||||
<docsUrl>https://github.com/Dark-Alex-17/gman/blob/main/README.md</docsUrl>
|
|
||||||
<bugTrackerUrl>https://github.com/Dark-Alex-17/gman/issues</bugTrackerUrl>
|
|
||||||
<tags>cli cross-platform terminal credential-management secret-management rust</tags>
|
|
||||||
<summary>Universal command line credential management and injection tool</summary>
|
|
||||||
<description>
|
|
||||||
Universal command line credential management and injection tool.
|
|
||||||
|
|
||||||
**Usage**
|
|
||||||
To get started, run `gman --help` in a terminal.
|
|
||||||
|
|
||||||
For more [documentation and usage](https://github.com/Dark-Alex-17/gman/blob/main/README.md), see the [official repo](https://github.com/Dark-Alex-17/gman).
|
|
||||||
|
|
||||||
</description>
|
|
||||||
<releaseNotes>https://github.com/Dark-Alex-17/gman/releases/tag/v$version/</releaseNotes>
|
|
||||||
</metadata>
|
|
||||||
<files>
|
|
||||||
<!-- this section controls what actually gets packaged into the Chocolatey package -->
|
|
||||||
<file src="tools\**" target="tools" />
|
|
||||||
<!--Building from Linux? You may need this instead: <file src="tools/**" target="tools" />-->
|
|
||||||
</files>
|
|
||||||
</package>
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
import hashlib
|
|
||||||
import sys
|
|
||||||
from string import Template
|
|
||||||
|
|
||||||
sys.stdout.reconfigure(encoding='utf-8')
|
|
||||||
args = sys.argv
|
|
||||||
version = args[1].replace("v", "")
|
|
||||||
template_file_path = args[2]
|
|
||||||
generated_file_path = args[3]
|
|
||||||
|
|
||||||
# Deployment files
|
|
||||||
hash_64 = args[4].strip()
|
|
||||||
|
|
||||||
print("Generating formula")
|
|
||||||
print(" VERSION: %s" % version)
|
|
||||||
print(" TEMPLATE PATH: %s" % template_file_path)
|
|
||||||
print(" SAVING AT: %s" % generated_file_path)
|
|
||||||
print(" HASH: %s" % hash_64)
|
|
||||||
|
|
||||||
with open(template_file_path, "r", encoding="utf-8") as template_file:
|
|
||||||
template = Template(template_file.read())
|
|
||||||
substitute = template.safe_substitute(version=version, hash_64=hash_64)
|
|
||||||
print("\n================== Generated package file ==================\n")
|
|
||||||
print(substitute)
|
|
||||||
print("\n============================================================\n")
|
|
||||||
|
|
||||||
with open(generated_file_path, "w", encoding="utf-8") as generated_file:
|
|
||||||
generated_file.write(substitute)
|
|
||||||
@@ -1,16 +1,16 @@
|
|||||||
# Documentation: https://docs.brew.sh/Formula-Cookbook
|
# Documentation: https://docs.brew.sh/Formula-Cookbook
|
||||||
# https://rubydoc.brew.sh/Formula
|
# https://rubydoc.brew.sh/Formula
|
||||||
class GMan < Formula
|
class Gman < Formula
|
||||||
desc "Universal command line credential management and injection tool"
|
desc "Universal command line credential management and injection tool"
|
||||||
homepage "https://github.com/Dark-Alex-17/gman"
|
homepage "https://github.com/Dark-Alex-17/gman"
|
||||||
if OS.mac? and Hardware::CPU.arm?
|
if OS.mac? and Hardware::CPU.arm?
|
||||||
url "https://github.com/Dark-Alex-17/gman/releases/download/v$version/gman-macos-arm64.tar.gz"
|
url "https://github.com/Dark-Alex-17/gman/releases/download/v$version/gman-aarch64-apple-darwin.tar.gz"
|
||||||
sha256 "$hash_mac_arm"
|
sha256 "$hash_mac_arm"
|
||||||
elsif OS.mac? and Hardware::CPU.intel?
|
elsif OS.mac? and Hardware::CPU.intel?
|
||||||
url "https://github.com/Dark-Alex-17/gman/releases/download/v$version/gman-macos.tar.gz"
|
url "https://github.com/Dark-Alex-17/gman/releases/download/v$version/gman-x86_64-apple-darwin.tar.gz"
|
||||||
sha256 "$hash_mac"
|
sha256 "$hash_mac"
|
||||||
else
|
else
|
||||||
url "https://github.com/Dark-Alex-17/gman/releases/download/v$version/gman-linux-musl.tar.gz"
|
url "https://github.com/Dark-Alex-17/gman/releases/download/v$version/gman-x86_64-unknown-linux-musl.tar.gz"
|
||||||
sha256 "$hash_linux"
|
sha256 "$hash_linux"
|
||||||
end
|
end
|
||||||
version "$version"
|
version "$version"
|
||||||
|
|||||||
@@ -0,0 +1,139 @@
|
|||||||
|
<#
|
||||||
|
gman installer (Windows/PowerShell 5+ and PowerShell 7)
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
powershell -NoProfile -ExecutionPolicy Bypass -Command "iwr -useb https://raw.githubusercontent.com/Dark-Alex-17/gman/main/scripts/install_gman.ps1 | iex"
|
||||||
|
pwsh -c "irm https://raw.githubusercontent.com/Dark-Alex-17/gman/main/scripts/install_gman.ps1 | iex -Version vX.Y.Z"
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
-Version <tag> (default: latest)
|
||||||
|
-BinDir <path> (default: %LOCALAPPDATA%\gman\bin on Windows; ~/.local/bin on *nix PowerShell)
|
||||||
|
#>
|
||||||
|
|
||||||
|
[CmdletBinding()]
|
||||||
|
param(
|
||||||
|
[string]$Version = $env:GMAN_VERSION,
|
||||||
|
[string]$BinDir = $env:BIN_DIR
|
||||||
|
)
|
||||||
|
|
||||||
|
$Repo = 'Dark-Alex-17/gman'
|
||||||
|
|
||||||
|
function Write-Info($msg) { Write-Host "[gman-install] $msg" }
|
||||||
|
function Fail($msg) { Write-Error $msg; exit 1 }
|
||||||
|
|
||||||
|
Add-Type -AssemblyName System.Runtime
|
||||||
|
$isWin = [System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform([System.Runtime.InteropServices.OSPlatform]::Windows)
|
||||||
|
$isMac = [System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform([System.Runtime.InteropServices.OSPlatform]::OSX)
|
||||||
|
$isLin = [System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform([System.Runtime.InteropServices.OSPlatform]::Linux)
|
||||||
|
|
||||||
|
if ($isWin) { $os = 'windows' }
|
||||||
|
elseif ($isMac) { $os = 'darwin' }
|
||||||
|
elseif ($isLin) { $os = 'linux' }
|
||||||
|
else { Fail "Unsupported OS" }
|
||||||
|
|
||||||
|
switch ([System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture) {
|
||||||
|
'X64' { $arch = 'x86_64' }
|
||||||
|
'Arm64'{ $arch = 'aarch64' }
|
||||||
|
default { Fail "Unsupported arch: $([System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture)" }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (-not $BinDir) {
|
||||||
|
if ($isWin) { $BinDir = Join-Path $env:LOCALAPPDATA 'gman\bin' }
|
||||||
|
else { $home = $env:HOME; if (-not $home) { $home = (Get-Item -Path ~).FullName }; $BinDir = Join-Path $home '.local/bin' }
|
||||||
|
}
|
||||||
|
New-Item -ItemType Directory -Force -Path $BinDir | Out-Null
|
||||||
|
|
||||||
|
Write-Info "Target: $os-$arch"
|
||||||
|
|
||||||
|
$apiBase = "https://api.github.com/repos/$Repo/releases"
|
||||||
|
$relUrl = if ($Version) { "$apiBase/tags/$Version" } else { "$apiBase/latest" }
|
||||||
|
Write-Info "Fetching release: $relUrl"
|
||||||
|
try {
|
||||||
|
$release = Invoke-RestMethod -UseBasicParsing -Headers @{ 'User-Agent' = 'gman-installer' } -Uri $relUrl -Method GET
|
||||||
|
} catch { Fail "Failed to fetch release metadata. $_" }
|
||||||
|
if (-not $release.assets) { Fail "No assets found in the release." }
|
||||||
|
|
||||||
|
$candidates = @()
|
||||||
|
if ($os -eq 'windows') {
|
||||||
|
if ($arch -eq 'x86_64') { $candidates += 'gman-x86_64-pc-windows-msvc.zip' }
|
||||||
|
else { $candidates += 'gman-aarch64-pc-windows-msvc.zip' }
|
||||||
|
} elseif ($os -eq 'darwin') {
|
||||||
|
if ($arch -eq 'x86_64') { $candidates += 'gman-x86_64-apple-darwin.tar.gz' }
|
||||||
|
else { $candidates += 'gman-aarch64-apple-darwin.tar.gz' }
|
||||||
|
} elseif ($os -eq 'linux') {
|
||||||
|
if ($arch -eq 'x86_64') {
|
||||||
|
$candidates += 'gman-x86_64-unknown-linux-gnu.tar.gz'
|
||||||
|
$candidates += 'gman-x86_64-unknown-linux-musl.tar.gz'
|
||||||
|
} else {
|
||||||
|
$candidates += 'gman-aarch64-unknown-linux-musl.tar.gz'
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Fail "Unsupported OS for this installer: $os"
|
||||||
|
}
|
||||||
|
|
||||||
|
$asset = $null
|
||||||
|
foreach ($c in $candidates) {
|
||||||
|
$asset = $release.assets | Where-Object { $_.name -eq $c } | Select-Object -First 1
|
||||||
|
if ($asset) { break }
|
||||||
|
}
|
||||||
|
if (-not $asset) {
|
||||||
|
Write-Error "No matching asset found for $os-$arch. Tried:"; $candidates | ForEach-Object { Write-Error " - $_" }
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Info "Selected asset: $($asset.name)"
|
||||||
|
Write-Info "Download URL: $($asset.browser_download_url)"
|
||||||
|
|
||||||
|
$tmp = New-Item -ItemType Directory -Force -Path ([IO.Path]::Combine([IO.Path]::GetTempPath(), "gman-$(Get-Random)"))
|
||||||
|
$archive = Join-Path $tmp.FullName 'asset'
|
||||||
|
try { Invoke-WebRequest -UseBasicParsing -Headers @{ 'User-Agent' = 'gman-installer' } -Uri $asset.browser_download_url -OutFile $archive } catch { Fail "Failed to download asset. $_" }
|
||||||
|
|
||||||
|
$extractDir = Join-Path $tmp.FullName 'extract'; New-Item -ItemType Directory -Force -Path $extractDir | Out-Null
|
||||||
|
|
||||||
|
if ($asset.name -match '\.zip$') {
|
||||||
|
Add-Type -AssemblyName System.IO.Compression.FileSystem
|
||||||
|
[System.IO.Compression.ZipFile]::ExtractToDirectory($archive, $extractDir)
|
||||||
|
} elseif ($asset.name -match '\.tar\.gz$' -or $asset.name -match '\.tgz$') {
|
||||||
|
$tar = Get-Command tar -ErrorAction SilentlyContinue
|
||||||
|
if ($tar) { & $tar.FullName -xzf $archive -C $extractDir }
|
||||||
|
else { Fail "Asset is tar archive but 'tar' is not available." }
|
||||||
|
} else {
|
||||||
|
try { Add-Type -AssemblyName System.IO.Compression.FileSystem; [System.IO.Compression.ZipFile]::ExtractToDirectory($archive, $extractDir) }
|
||||||
|
catch {
|
||||||
|
$tar = Get-Command tar -ErrorAction SilentlyContinue
|
||||||
|
if ($tar) { & $tar.FullName -xf $archive -C $extractDir } else { Fail "Unknown archive format; neither zip nor tar workable." }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$bin = $null
|
||||||
|
Get-ChildItem -Recurse -File $extractDir | ForEach-Object {
|
||||||
|
if ($isWin) { if ($_.Name -ieq 'gman.exe') { $bin = $_.FullName } }
|
||||||
|
else { if ($_.Name -ieq 'gman') { $bin = $_.FullName } }
|
||||||
|
}
|
||||||
|
if (-not $bin) { Fail "Could not find gman binary inside the archive." }
|
||||||
|
|
||||||
|
if (-not $isWin) { try { & chmod +x -- $bin } catch {} }
|
||||||
|
|
||||||
|
$exec = if ($isWin) { 'gman.exe'} else { 'gman' }
|
||||||
|
$dest = Join-Path $BinDir $exec
|
||||||
|
Copy-Item -Force $bin $dest
|
||||||
|
Write-Info "Installed: $dest"
|
||||||
|
|
||||||
|
if ($isWin) {
|
||||||
|
$pathParts = ($env:Path -split ';') | Where-Object { $_ -ne '' }
|
||||||
|
if ($pathParts -notcontains $BinDir) {
|
||||||
|
$userPath = [Environment]::GetEnvironmentVariable('Path', 'User'); if (-not $userPath) { $userPath = '' }
|
||||||
|
if (-not ($userPath -split ';' | Where-Object { $_ -eq $BinDir })) {
|
||||||
|
$newUserPath = if ($userPath.Trim().Length -gt 0) { "$userPath;$BinDir" } else { $BinDir }
|
||||||
|
[Environment]::SetEnvironmentVariable('Path', $newUserPath, 'User')
|
||||||
|
Write-Info "Added to User PATH: $BinDir (restart shell to take effect)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (-not ($env:PATH -split ':' | Where-Object { $_ -eq $BinDir })) {
|
||||||
|
Write-Info "Note: $BinDir is not in PATH. Add it to your shell profile."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Info "Done. Try: gman --help"
|
||||||
|
|
||||||
@@ -0,0 +1,220 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# gman installer (Linux/macOS)
|
||||||
|
#
|
||||||
|
# Usage examples:
|
||||||
|
# curl -fsSL https://raw.githubusercontent.com/Dark-Alex-17/gman/main/scripts/install_gman.sh | bash
|
||||||
|
# curl -fsSL https://raw.githubusercontent.com/Dark-Alex-17/gman/main/scripts/install_gman.sh | bash -s -- --version vX.Y.Z
|
||||||
|
# BIN_DIR="$HOME/.local/bin" bash scripts/install_gman.sh
|
||||||
|
#
|
||||||
|
# Flags / Env:
|
||||||
|
# --version <tag> Release tag (default: latest). Or set GMAN_VERSION.
|
||||||
|
# --bin-dir <dir> Install directory (default: /usr/local/bin or ~/.local/bin). Or set BIN_DIR.
|
||||||
|
|
||||||
|
REPO="Dark-Alex-17/gman"
|
||||||
|
VERSION="${GMAN_VERSION:-}"
|
||||||
|
BIN_DIR="${BIN_DIR:-}"
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
echo "gman installer (Linux/macOS)"
|
||||||
|
echo
|
||||||
|
echo "Options:"
|
||||||
|
echo " --version <tag> Release tag (default: latest)"
|
||||||
|
echo " --bin-dir <dir> Install directory (default: /usr/local/bin or ~/.local/bin)"
|
||||||
|
echo " -h, --help Show help"
|
||||||
|
}
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
--version) VERSION="$2"; shift 2;;
|
||||||
|
--bin-dir) BIN_DIR="$2"; shift 2;;
|
||||||
|
-h|--help) usage; exit 0;;
|
||||||
|
*) echo "Unknown argument: $1" >&2; usage; exit 2;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ -z "${BIN_DIR}" ]]; then
|
||||||
|
if [[ -w "/usr/local/bin" ]]; then
|
||||||
|
BIN_DIR="/usr/local/bin"
|
||||||
|
else
|
||||||
|
BIN_DIR="${HOME}/.local/bin"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
mkdir -p "${BIN_DIR}"
|
||||||
|
|
||||||
|
log() {
|
||||||
|
echo "[gman-install] $*"
|
||||||
|
}
|
||||||
|
|
||||||
|
need_cmd() {
|
||||||
|
if ! command -v "$1" >/dev/null 2>&1; then
|
||||||
|
echo "Error: required command '$1' not found" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
need_cmd uname
|
||||||
|
need_cmd mktemp
|
||||||
|
need_cmd tar
|
||||||
|
|
||||||
|
if command -v curl >/dev/null 2>&1; then
|
||||||
|
DL=curl
|
||||||
|
elif command -v wget >/dev/null 2>&1; then
|
||||||
|
DL=wget
|
||||||
|
else
|
||||||
|
echo "Error: need curl or wget" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
UNAME_OS=$(uname -s | tr '[:upper:]' '[:lower:]')
|
||||||
|
case "$UNAME_OS" in
|
||||||
|
linux) OS=linux ;;
|
||||||
|
darwin) OS=darwin ;;
|
||||||
|
*) echo "Error: unsupported OS '$UNAME_OS'" >&2; exit 1;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
UNAME_ARCH=$(uname -m)
|
||||||
|
case "$UNAME_ARCH" in
|
||||||
|
x86_64|amd64) ARCH=x86_64 ;;
|
||||||
|
aarch64|arm64) ARCH=aarch64 ;;
|
||||||
|
*) echo "Error: unsupported arch '$UNAME_ARCH'" >&2; exit 1;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
log "Target: ${OS}-${ARCH}"
|
||||||
|
|
||||||
|
API_BASE="https://api.github.com/repos/${REPO}/releases"
|
||||||
|
if [[ -z "${VERSION}" ]]; then
|
||||||
|
RELEASE_URL="${API_BASE}/latest"
|
||||||
|
else
|
||||||
|
RELEASE_URL="${API_BASE}/tags/${VERSION}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
http_get() {
|
||||||
|
if [[ "$DL" == "curl" ]]; then
|
||||||
|
curl -fsSL -H 'User-Agent: gman-installer' "$1"
|
||||||
|
else
|
||||||
|
wget -qO- --header='User-Agent: gman-installer' "$1"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
TMPDIR="$(mktemp -d)"
|
||||||
|
trap 'rm -rf "$TMPDIR"' EXIT
|
||||||
|
|
||||||
|
log "Fetching release metadata from $RELEASE_URL"
|
||||||
|
JSON="$TMPDIR/release.json"
|
||||||
|
if ! http_get "$RELEASE_URL" > "$JSON"; then
|
||||||
|
echo "Error: failed to fetch release metadata. Check version tag." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
ASSET_CANDIDATES=()
|
||||||
|
if [[ "$OS" == "darwin" ]]; then
|
||||||
|
if [[ "$ARCH" == "x86_64" ]]; then
|
||||||
|
ASSET_CANDIDATES+=("gman-x86_64-apple-darwin.tar.gz")
|
||||||
|
else
|
||||||
|
ASSET_CANDIDATES+=("gman-aarch64-apple-darwin.tar.gz")
|
||||||
|
fi
|
||||||
|
elif [[ "$OS" == "linux" ]]; then
|
||||||
|
if [[ "$ARCH" == "x86_64" ]]; then
|
||||||
|
LIBC="musl"
|
||||||
|
if command -v getconf >/dev/null 2>&1 && getconf GNU_LIBC_VERSION >/dev/null 2>&1; then LIBC="gnu"; fi
|
||||||
|
if ldd --version 2>&1 | grep -qi glibc; then LIBC="gnu"; fi
|
||||||
|
|
||||||
|
if [[ "$LIBC" == "gnu" ]]; then
|
||||||
|
ASSET_CANDIDATES+=("gman-x86_64-unknown-linux-gnu.tar.gz")
|
||||||
|
fi
|
||||||
|
|
||||||
|
ASSET_CANDIDATES+=("gman-x86_64-unknown-linux-musl.tar.gz")
|
||||||
|
else
|
||||||
|
ASSET_CANDIDATES+=("gman-aarch64-unknown-linux-musl.tar.gz")
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "Error: unsupported OS for this installer: $OS" >&2; exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
ASSET_NAME=""; ASSET_URL=""
|
||||||
|
for candidate in "${ASSET_CANDIDATES[@]}"; do
|
||||||
|
NAME=$(grep -oE '"name":\s*"[^"]+"' "$JSON" | sed 's/"name":\s*"//; s/"$//' | grep -Fx "$candidate" || true)
|
||||||
|
if [[ -n "$NAME" ]]; then
|
||||||
|
ASSET_NAME="$NAME"
|
||||||
|
ASSET_URL=$(awk -v pat="$NAME" '
|
||||||
|
BEGIN{ FS=":"; want=0 }
|
||||||
|
/"name"/ {
|
||||||
|
line=$0;
|
||||||
|
gsub(/^\s+|\s+$/,"",line);
|
||||||
|
gsub(/"name"\s*:\s*"|"/ ,"", line);
|
||||||
|
want = (line==pat) ? 1 : 0;
|
||||||
|
next
|
||||||
|
}
|
||||||
|
want==1 && /"browser_download_url"/ {
|
||||||
|
u=$0;
|
||||||
|
gsub(/^\s+|\s+$/,"",u);
|
||||||
|
gsub(/.*"browser_download_url"\s*:\s*"|".*/ ,"", u);
|
||||||
|
print u;
|
||||||
|
exit
|
||||||
|
}
|
||||||
|
' "$JSON")
|
||||||
|
if [[ -n "$ASSET_URL" ]]; then break; fi
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ -z "$ASSET_URL" ]]; then
|
||||||
|
echo "Error: no matching asset found for ${OS}-${ARCH}. Tried:" >&2
|
||||||
|
for c in "${ASSET_CANDIDATES[@]}"; do echo " - $c" >&2; done
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
log "Selected asset: $ASSET_NAME"
|
||||||
|
log "Download URL: $ASSET_URL"
|
||||||
|
|
||||||
|
ARCHIVE="$TMPDIR/asset"
|
||||||
|
if [[ "$DL" == "curl" ]]; then
|
||||||
|
curl -fL -H 'User-Agent: gman-installer' "$ASSET_URL" -o "$ARCHIVE"
|
||||||
|
else
|
||||||
|
wget -q --header='User-Agent: gman-installer' "$ASSET_URL" -O "$ARCHIVE"
|
||||||
|
fi
|
||||||
|
|
||||||
|
WORK="$TMPDIR/work"; mkdir -p "$WORK"
|
||||||
|
EXTRACTED_DIR="$WORK/extracted"; mkdir -p "$EXTRACTED_DIR"
|
||||||
|
|
||||||
|
if tar -tf "$ARCHIVE" >/dev/null 2>&1; then
|
||||||
|
tar -xzf "$ARCHIVE" -C "$EXTRACTED_DIR"
|
||||||
|
else
|
||||||
|
if command -v unzip >/dev/null 2>&1; then
|
||||||
|
unzip -q "$ARCHIVE" -d "$EXTRACTED_DIR"
|
||||||
|
else
|
||||||
|
echo "Error: unknown archive format; install 'unzip'" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
BIN_PATH=""
|
||||||
|
while IFS= read -r -d '' f; do
|
||||||
|
base=$(basename "$f")
|
||||||
|
if [[ "$base" == "gman" ]]; then
|
||||||
|
BIN_PATH="$f"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done < <(find "$EXTRACTED_DIR" -type f -print0)
|
||||||
|
|
||||||
|
if [[ -z "$BIN_PATH" ]]; then
|
||||||
|
echo "Error: could not find 'gman' binary in the archive" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
chmod +x "$BIN_PATH"
|
||||||
|
install -m 0755 "$BIN_PATH" "${BIN_DIR}/gman"
|
||||||
|
|
||||||
|
log "Installed: ${BIN_DIR}/gman"
|
||||||
|
|
||||||
|
case ":$PATH:" in
|
||||||
|
*":${BIN_DIR}:"*) ;;
|
||||||
|
*)
|
||||||
|
log "Note: ${BIN_DIR} is not in PATH. Add it, e.g.:"
|
||||||
|
log " export PATH=\"${BIN_DIR}:\$PATH\""
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
log "Done. Try: gman --help"
|
||||||
|
|
||||||
+63
-35
@@ -1,21 +1,22 @@
|
|||||||
use crate::command::preview_command;
|
use crate::command::preview_command;
|
||||||
use anyhow::{Context, Result, anyhow};
|
use anyhow::{Context, Result, anyhow};
|
||||||
|
use clap_complete::CompletionCandidate;
|
||||||
use futures::future::join_all;
|
use futures::future::join_all;
|
||||||
use gman::config::{Config, RunConfig};
|
use gman::config::{Config, RunConfig, load_config};
|
||||||
use gman::providers::SecretProvider;
|
|
||||||
use log::{debug, error};
|
use log::{debug, error};
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::ffi::OsString;
|
use std::ffi::{OsStr, OsString};
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
|
use tokio::runtime::Handle;
|
||||||
|
|
||||||
const ARG_FORMAT_PLACEHOLDER_KEY: &str = "{{key}}";
|
const ARG_FORMAT_PLACEHOLDER_KEY: &str = "{{key}}";
|
||||||
const ARG_FORMAT_PLACEHOLDER_VALUE: &str = "{{value}}";
|
const ARG_FORMAT_PLACEHOLDER_VALUE: &str = "{{value}}";
|
||||||
|
|
||||||
pub async fn wrap_and_run_command(
|
pub async fn wrap_and_run_command(
|
||||||
secrets_provider: &mut dyn SecretProvider,
|
provider: Option<String>,
|
||||||
config: &Config,
|
config: &Config,
|
||||||
tokens: Vec<OsString>,
|
tokens: Vec<OsString>,
|
||||||
profile_name: Option<String>,
|
profile_name: Option<String>,
|
||||||
@@ -36,6 +37,9 @@ pub async fn wrap_and_run_command(
|
|||||||
.find(|c| c.name.as_deref() == Some(run_config_profile_name))
|
.find(|c| c.name.as_deref() == Some(run_config_profile_name))
|
||||||
});
|
});
|
||||||
if let Some(run_cfg) = run_config_opt {
|
if let Some(run_cfg) = run_config_opt {
|
||||||
|
let mut provider_config =
|
||||||
|
config.extract_provider_config(provider.or(run_cfg.provider.clone()))?;
|
||||||
|
let secrets_provider = provider_config.extract_provider();
|
||||||
let secrets_result_futures = run_cfg
|
let secrets_result_futures = run_cfg
|
||||||
.secrets
|
.secrets
|
||||||
.as_ref()
|
.as_ref()
|
||||||
@@ -163,7 +167,7 @@ fn generate_files_secret_injections(
|
|||||||
secrets: HashMap<&str, String>,
|
secrets: HashMap<&str, String>,
|
||||||
run_config: &RunConfig,
|
run_config: &RunConfig,
|
||||||
) -> Result<Vec<(PathBuf, String, String)>> {
|
) -> Result<Vec<(PathBuf, String, String)>> {
|
||||||
let re = Regex::new(r"\{\{(.+)\}\}")?;
|
let re = Regex::new(r"\{\{(.+)}}")?;
|
||||||
let mut results = Vec::new();
|
let mut results = Vec::new();
|
||||||
for file in run_config
|
for file in run_config
|
||||||
.files
|
.files
|
||||||
@@ -251,6 +255,52 @@ pub fn parse_args(
|
|||||||
Ok(args)
|
Ok(args)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn run_config_completer(current: &OsStr) -> Vec<CompletionCandidate> {
|
||||||
|
let cur = current.to_string_lossy();
|
||||||
|
match load_config() {
|
||||||
|
Ok(config) => {
|
||||||
|
if let Some(run_configs) = config.run_configs {
|
||||||
|
run_configs
|
||||||
|
.iter()
|
||||||
|
.filter(|rc| {
|
||||||
|
rc.name
|
||||||
|
.as_ref()
|
||||||
|
.expect("run config has no name")
|
||||||
|
.starts_with(&*cur)
|
||||||
|
})
|
||||||
|
.map(|rc| {
|
||||||
|
CompletionCandidate::new(rc.name.as_ref().expect("run config has no name"))
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
} else {
|
||||||
|
vec![]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(_) => vec![],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn secrets_completer(current: &OsStr) -> Vec<CompletionCandidate> {
|
||||||
|
let cur = current.to_string_lossy();
|
||||||
|
match load_config() {
|
||||||
|
Ok(config) => {
|
||||||
|
let mut provider_config = match config.extract_provider_config(None) {
|
||||||
|
Ok(pc) => pc,
|
||||||
|
Err(_) => return vec![],
|
||||||
|
};
|
||||||
|
let secrets_provider = provider_config.extract_provider();
|
||||||
|
let h = Handle::current();
|
||||||
|
tokio::task::block_in_place(|| h.block_on(secrets_provider.list_secrets()))
|
||||||
|
.unwrap_or_default()
|
||||||
|
.into_iter()
|
||||||
|
.filter(|s| s.starts_with(&*cur))
|
||||||
|
.map(CompletionCandidate::new)
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
Err(_) => vec![],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
@@ -260,26 +310,6 @@ mod tests {
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::ffi::OsString;
|
use std::ffi::OsString;
|
||||||
|
|
||||||
struct DummyProvider;
|
|
||||||
#[async_trait::async_trait]
|
|
||||||
impl SecretProvider for DummyProvider {
|
|
||||||
fn name(&self) -> &'static str {
|
|
||||||
"Dummy"
|
|
||||||
}
|
|
||||||
async fn get_secret(&self, key: &str) -> Result<String> {
|
|
||||||
Ok(format!("{}_VAL", key))
|
|
||||||
}
|
|
||||||
async fn set_secret(&self, _key: &str, _value: &str) -> Result<()> {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
async fn delete_secret(&self, _key: &str) -> Result<()> {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
async fn sync(&mut self) -> Result<()> {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_generate_files_secret_injections() {
|
fn test_generate_files_secret_injections() {
|
||||||
let mut secrets = HashMap::new();
|
let mut secrets = HashMap::new();
|
||||||
@@ -290,6 +320,7 @@ mod tests {
|
|||||||
|
|
||||||
let run_config = RunConfig {
|
let run_config = RunConfig {
|
||||||
name: Some("test".to_string()),
|
name: Some("test".to_string()),
|
||||||
|
provider: None,
|
||||||
secrets: Some(vec!["testing/SOME-secret".to_string()]),
|
secrets: Some(vec!["testing/SOME-secret".to_string()]),
|
||||||
files: Some(vec![file_path.clone()]),
|
files: Some(vec![file_path.clone()]),
|
||||||
flag: None,
|
flag: None,
|
||||||
@@ -309,6 +340,7 @@ mod tests {
|
|||||||
fn test_parse_args_insert_and_append() {
|
fn test_parse_args_insert_and_append() {
|
||||||
let run_config = RunConfig {
|
let run_config = RunConfig {
|
||||||
name: Some("docker".into()),
|
name: Some("docker".into()),
|
||||||
|
provider: None,
|
||||||
secrets: Some(vec!["api_key".into()]),
|
secrets: Some(vec!["api_key".into()]),
|
||||||
files: None,
|
files: None,
|
||||||
flag: Some("-e".into()),
|
flag: Some("-e".into()),
|
||||||
@@ -347,10 +379,8 @@ mod tests {
|
|||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_wrap_and_run_command_no_profile() {
|
async fn test_wrap_and_run_command_no_profile() {
|
||||||
let cfg = Config::default();
|
let cfg = Config::default();
|
||||||
let mut dummy = DummyProvider;
|
|
||||||
let prov: &mut dyn SecretProvider = &mut dummy;
|
|
||||||
let tokens = vec![OsString::from("echo"), OsString::from("hi")];
|
let tokens = vec![OsString::from("echo"), OsString::from("hi")];
|
||||||
let err = wrap_and_run_command(prov, &cfg, tokens, None, true)
|
let err = wrap_and_run_command(None, &cfg, tokens, None, true)
|
||||||
.await
|
.await
|
||||||
.unwrap_err();
|
.unwrap_err();
|
||||||
assert!(err.to_string().contains("No run profile found"));
|
assert!(err.to_string().contains("No run profile found"));
|
||||||
@@ -361,6 +391,7 @@ mod tests {
|
|||||||
// Create a config with a matching run profile for command "echo"
|
// Create a config with a matching run profile for command "echo"
|
||||||
let run_cfg = RunConfig {
|
let run_cfg = RunConfig {
|
||||||
name: Some("echo".into()),
|
name: Some("echo".into()),
|
||||||
|
provider: None,
|
||||||
secrets: Some(vec!["api_key".into()]),
|
secrets: Some(vec!["api_key".into()]),
|
||||||
files: None,
|
files: None,
|
||||||
flag: None,
|
flag: None,
|
||||||
@@ -371,14 +402,11 @@ mod tests {
|
|||||||
run_configs: Some(vec![run_cfg]),
|
run_configs: Some(vec![run_cfg]),
|
||||||
..Config::default()
|
..Config::default()
|
||||||
};
|
};
|
||||||
let mut dummy = DummyProvider;
|
|
||||||
let prov: &mut dyn SecretProvider = &mut dummy;
|
|
||||||
|
|
||||||
// Capture stderr for dry_run preview
|
|
||||||
let tokens = vec![OsString::from("echo"), OsString::from("hello")];
|
let tokens = vec![OsString::from("echo"), OsString::from("hello")];
|
||||||
// Best-effort: ensure function does not error under dry_run
|
let err = wrap_and_run_command(None, &cfg, tokens, None, true)
|
||||||
let res = wrap_and_run_command(prov, &cfg, tokens, None, true).await;
|
.await
|
||||||
assert!(res.is_ok());
|
.expect_err("expected failed secret resolution in dry_run");
|
||||||
// Not asserting output text to keep test platform-agnostic
|
assert!(err.to_string().contains("Failed to fetch"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+44
-9
@@ -1,19 +1,24 @@
|
|||||||
|
use crate::cli::run_config_completer;
|
||||||
|
use crate::cli::secrets_completer;
|
||||||
|
use anyhow::{Context, Result};
|
||||||
|
use clap::Subcommand;
|
||||||
use clap::{
|
use clap::{
|
||||||
CommandFactory, Parser, ValueEnum, crate_authors, crate_description, crate_name, crate_version,
|
CommandFactory, Parser, ValueEnum, crate_authors, crate_description, crate_name, crate_version,
|
||||||
};
|
};
|
||||||
use std::ffi::OsString;
|
use clap_complete::{ArgValueCompleter, CompleteEnv};
|
||||||
|
|
||||||
use anyhow::{Context, Result};
|
|
||||||
use clap::Subcommand;
|
|
||||||
use crossterm::execute;
|
use crossterm::execute;
|
||||||
use crossterm::terminal::{LeaveAlternateScreen, disable_raw_mode};
|
use crossterm::terminal::{LeaveAlternateScreen, disable_raw_mode};
|
||||||
use gman::config::{get_config_file_path, load_config};
|
use gman::config::{Config, get_config_file_path, load_config};
|
||||||
|
use std::ffi::OsString;
|
||||||
use std::io::{self, IsTerminal, Read, Write};
|
use std::io::{self, IsTerminal, Read, Write};
|
||||||
use std::panic::PanicHookInfo;
|
use std::panic::PanicHookInfo;
|
||||||
|
|
||||||
use crate::cli::wrap_and_run_command;
|
use crate::cli::wrap_and_run_command;
|
||||||
|
use crate::utils::persist_config_file;
|
||||||
|
use dialoguer::Editor;
|
||||||
use std::panic;
|
use std::panic;
|
||||||
use std::process::exit;
|
use std::process::exit;
|
||||||
|
use validator::Validate;
|
||||||
|
|
||||||
mod cli;
|
mod cli;
|
||||||
mod command;
|
mod command;
|
||||||
@@ -46,11 +51,11 @@ struct Cli {
|
|||||||
output: Option<OutputFormat>,
|
output: Option<OutputFormat>,
|
||||||
|
|
||||||
/// Specify the secret provider to use (defaults to 'default_provider' in config (usually 'local'))
|
/// Specify the secret provider to use (defaults to 'default_provider' in config (usually 'local'))
|
||||||
#[arg(long, value_enum, global = true, env = "GMAN_PROVIDER")]
|
#[arg(long, global = true, env = "GMAN_PROVIDER", value_parser = ["local", "aws_secrets_manager", "azure_key_vault", "gcp_secret_manager", "gopass"])]
|
||||||
provider: Option<String>,
|
provider: Option<String>,
|
||||||
|
|
||||||
/// Specify a run profile to use when wrapping a command
|
/// Specify a run profile to use when wrapping a command
|
||||||
#[arg(long, short)]
|
#[arg(long, short, add = ArgValueCompleter::new(run_config_completer))]
|
||||||
profile: Option<String>,
|
profile: Option<String>,
|
||||||
|
|
||||||
/// Output the command that will be run instead of executing it
|
/// Output the command that will be run instead of executing it
|
||||||
@@ -72,14 +77,17 @@ struct Cli {
|
|||||||
#[derive(Subcommand, Clone, Debug)]
|
#[derive(Subcommand, Clone, Debug)]
|
||||||
enum Commands {
|
enum Commands {
|
||||||
/// Add a secret to the configured secret provider
|
/// Add a secret to the configured secret provider
|
||||||
|
#[clap(aliases = &["set", "create"])]
|
||||||
Add {
|
Add {
|
||||||
/// Name of the secret to store
|
/// Name of the secret to store
|
||||||
name: String,
|
name: String,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// Decrypt a secret and print the plaintext
|
/// Decrypt a secret and print the plaintext
|
||||||
|
#[clap(alias = "show")]
|
||||||
Get {
|
Get {
|
||||||
/// Name of the secret to retrieve
|
/// Name of the secret to retrieve
|
||||||
|
#[arg(add = ArgValueCompleter::new(secrets_completer))]
|
||||||
name: String,
|
name: String,
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -87,22 +95,29 @@ enum Commands {
|
|||||||
/// If a provider does not support updating secrets, this command will return an error.
|
/// If a provider does not support updating secrets, this command will return an error.
|
||||||
Update {
|
Update {
|
||||||
/// Name of the secret to update
|
/// Name of the secret to update
|
||||||
|
#[arg(add = ArgValueCompleter::new(secrets_completer))]
|
||||||
name: String,
|
name: String,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// Delete a secret from the configured secret provider
|
/// Delete a secret from the configured secret provider
|
||||||
|
#[clap(aliases = &["remove", "rm"])]
|
||||||
Delete {
|
Delete {
|
||||||
/// Name of the secret to delete
|
/// Name of the secret to delete
|
||||||
|
#[arg(add = ArgValueCompleter::new(secrets_completer))]
|
||||||
name: String,
|
name: String,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// List all secrets stored in the configured secret provider (if supported by the provider)
|
/// List all secrets stored in the configured secret provider (if supported by the provider)
|
||||||
/// If a provider does not support listing secrets, this command will return an error.
|
/// If a provider does not support listing secrets, this command will return an error.
|
||||||
|
#[clap(alias = "ls")]
|
||||||
List {},
|
List {},
|
||||||
|
|
||||||
/// Sync secrets with remote storage (if supported by the provider)
|
/// Sync secrets with remote storage (if supported by the provider)
|
||||||
Sync {},
|
Sync {},
|
||||||
|
|
||||||
|
/// Open and edit the config file in the default text editor
|
||||||
|
Config {},
|
||||||
|
|
||||||
/// Wrap the provided command and supply it with secrets as environment variables or as
|
/// Wrap the provided command and supply it with secrets as environment variables or as
|
||||||
/// configured in a corresponding run profile
|
/// configured in a corresponding run profile
|
||||||
#[command(external_subcommand)]
|
#[command(external_subcommand)]
|
||||||
@@ -124,6 +139,7 @@ async fn main() -> Result<()> {
|
|||||||
panic::set_hook(Box::new(|info| {
|
panic::set_hook(Box::new(|info| {
|
||||||
panic_hook(info);
|
panic_hook(info);
|
||||||
}));
|
}));
|
||||||
|
CompleteEnv::with_factory(Cli::command).complete();
|
||||||
let cli = Cli::parse();
|
let cli = Cli::parse();
|
||||||
|
|
||||||
if cli.show_log_path {
|
if cli.show_log_path {
|
||||||
@@ -220,6 +236,26 @@ async fn main() -> Result<()> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Commands::Config {} => {
|
||||||
|
let config_yaml = serde_yaml::to_string(&config)
|
||||||
|
.with_context(|| "failed to serialize existing configuration")?;
|
||||||
|
let new_config = Editor::new()
|
||||||
|
.edit(&config_yaml)
|
||||||
|
.with_context(|| "unable to process user changes")?;
|
||||||
|
if new_config.is_none() {
|
||||||
|
println!("✗ No changes made to configuration");
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let new_config = new_config.unwrap();
|
||||||
|
let new_config: Config = serde_yaml::from_str(&new_config)
|
||||||
|
.with_context(|| "failed to parse updated configuration")?;
|
||||||
|
new_config
|
||||||
|
.validate()
|
||||||
|
.with_context(|| "updated configuration is invalid")?;
|
||||||
|
persist_config_file(&new_config)?;
|
||||||
|
println!("✓ Configuration updated successfully");
|
||||||
|
}
|
||||||
Commands::Sync {} => {
|
Commands::Sync {} => {
|
||||||
secrets_provider.sync().await.map(|_| {
|
secrets_provider.sync().await.map(|_| {
|
||||||
if cli.output.is_none() {
|
if cli.output.is_none() {
|
||||||
@@ -228,8 +264,7 @@ async fn main() -> Result<()> {
|
|||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
Commands::External(tokens) => {
|
Commands::External(tokens) => {
|
||||||
wrap_and_run_command(secrets_provider, &config, tokens, cli.profile, cli.dry_run)
|
wrap_and_run_command(cli.provider, &config, tokens, cli.profile, cli.dry_run).await?;
|
||||||
.await?;
|
|
||||||
}
|
}
|
||||||
Commands::Completions { shell } => {
|
Commands::Completions { shell } => {
|
||||||
let mut cmd = Cli::command();
|
let mut cmd = Cli::command();
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
use anyhow::{Context, Result};
|
||||||
|
use gman::config::{Config, get_config_file_path};
|
||||||
use log::LevelFilter;
|
use log::LevelFilter;
|
||||||
use log4rs::append::console::ConsoleAppender;
|
use log4rs::append::console::ConsoleAppender;
|
||||||
use log4rs::append::file::FileAppender;
|
use log4rs::append::file::FileAppender;
|
||||||
@@ -60,6 +62,28 @@ pub fn get_log_path() -> PathBuf {
|
|||||||
dir.join("gman.log")
|
dir.join("gman.log")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn persist_config_file(config: &Config) -> Result<()> {
|
||||||
|
let config_path =
|
||||||
|
get_config_file_path().with_context(|| "unable to determine config file path")?;
|
||||||
|
let ext = config_path
|
||||||
|
.extension()
|
||||||
|
.and_then(|s| s.to_str())
|
||||||
|
.unwrap_or("");
|
||||||
|
if ext.eq_ignore_ascii_case("yml") || ext.eq_ignore_ascii_case("yaml") {
|
||||||
|
if let Some(parent) = config_path.parent() {
|
||||||
|
fs::create_dir_all(parent)?;
|
||||||
|
}
|
||||||
|
let s = serde_yaml::to_string(config)?;
|
||||||
|
fs::write(&config_path, s)
|
||||||
|
.with_context(|| format!("failed to write {}", config_path.display()))?;
|
||||||
|
} else {
|
||||||
|
confy::store("gman", "config", config)
|
||||||
|
.with_context(|| "failed to save updated config via confy")?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::utils::get_log_path;
|
use crate::utils::get_log_path;
|
||||||
|
|||||||
+27
-1
@@ -11,6 +11,7 @@
|
|||||||
//!
|
//!
|
||||||
//! let rc = RunConfig{
|
//! let rc = RunConfig{
|
||||||
//! name: Some("echo".into()),
|
//! name: Some("echo".into()),
|
||||||
|
//! provider: None,
|
||||||
//! secrets: Some(vec!["api_key".into()]),
|
//! secrets: Some(vec!["api_key".into()]),
|
||||||
//! files: None,
|
//! files: None,
|
||||||
//! flag: None,
|
//! flag: None,
|
||||||
@@ -19,16 +20,18 @@
|
|||||||
//! };
|
//! };
|
||||||
//! rc.validate().unwrap();
|
//! rc.validate().unwrap();
|
||||||
//! ```
|
//! ```
|
||||||
|
|
||||||
use crate::providers::local::LocalProvider;
|
use crate::providers::local::LocalProvider;
|
||||||
use crate::providers::{SecretProvider, SupportedProvider};
|
use crate::providers::{SecretProvider, SupportedProvider};
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
|
use collections::HashSet;
|
||||||
use log::debug;
|
use log::debug;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_with::serde_as;
|
use serde_with::serde_as;
|
||||||
use serde_with::skip_serializing_none;
|
use serde_with::skip_serializing_none;
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::{env, fs};
|
use std::{collections, env, fs};
|
||||||
use validator::{Validate, ValidationError};
|
use validator::{Validate, ValidationError};
|
||||||
|
|
||||||
#[skip_serializing_none]
|
#[skip_serializing_none]
|
||||||
@@ -43,6 +46,7 @@ use validator::{Validate, ValidationError};
|
|||||||
pub struct RunConfig {
|
pub struct RunConfig {
|
||||||
#[validate(required)]
|
#[validate(required)]
|
||||||
pub name: Option<String>,
|
pub name: Option<String>,
|
||||||
|
pub provider: Option<String>,
|
||||||
#[validate(required)]
|
#[validate(required)]
|
||||||
pub secrets: Option<Vec<String>>,
|
pub secrets: Option<Vec<String>>,
|
||||||
pub files: Option<Vec<PathBuf>>,
|
pub files: Option<Vec<PathBuf>>,
|
||||||
@@ -144,6 +148,7 @@ impl ProviderConfig {
|
|||||||
match &mut self.provider_type {
|
match &mut self.provider_type {
|
||||||
SupportedProvider::Local { provider_def } => {
|
SupportedProvider::Local { provider_def } => {
|
||||||
debug!("Using local secret provider");
|
debug!("Using local secret provider");
|
||||||
|
provider_def.runtime_provider_name = self.name.clone();
|
||||||
provider_def
|
provider_def
|
||||||
}
|
}
|
||||||
SupportedProvider::AwsSecretsManager { provider_def } => {
|
SupportedProvider::AwsSecretsManager { provider_def } => {
|
||||||
@@ -158,6 +163,10 @@ impl ProviderConfig {
|
|||||||
debug!("Using Azure Key Vault provider");
|
debug!("Using Azure Key Vault provider");
|
||||||
provider_def
|
provider_def
|
||||||
}
|
}
|
||||||
|
SupportedProvider::Gopass { provider_def } => {
|
||||||
|
debug!("Using Gopass provider");
|
||||||
|
provider_def
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -181,6 +190,7 @@ impl ProviderConfig {
|
|||||||
/// ```
|
/// ```
|
||||||
#[derive(Debug, Clone, Validate, Serialize, Deserialize, PartialEq, Eq)]
|
#[derive(Debug, Clone, Validate, Serialize, Deserialize, PartialEq, Eq)]
|
||||||
#[validate(schema(function = "default_provider_exists"))]
|
#[validate(schema(function = "default_provider_exists"))]
|
||||||
|
#[validate(schema(function = "providers_names_are_unique"))]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
pub default_provider: Option<String>,
|
pub default_provider: Option<String>,
|
||||||
#[validate(length(min = 1))]
|
#[validate(length(min = 1))]
|
||||||
@@ -210,6 +220,22 @@ fn default_provider_exists(config: &Config) -> Result<(), ValidationError> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn providers_names_are_unique(config: &Config) -> Result<(), ValidationError> {
|
||||||
|
let mut names = HashSet::new();
|
||||||
|
for provider in &config.providers {
|
||||||
|
if let Some(name) = &provider.name
|
||||||
|
&& !names.insert(name)
|
||||||
|
{
|
||||||
|
let mut err = ValidationError::new("duplicate_provider_name");
|
||||||
|
err.message = Some(Cow::Borrowed(
|
||||||
|
"Provider names must be unique; duplicate found",
|
||||||
|
));
|
||||||
|
return Err(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
impl Default for Config {
|
impl Default for Config {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
|||||||
@@ -116,8 +116,7 @@ fn resolve_git_username(git: &Path, name: Option<&String>) -> Result<String> {
|
|||||||
return Ok(name.to_string());
|
return Ok(name.to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
run_git_config_capture(git, &["config", "user.name"])
|
default_git_username(git)
|
||||||
.with_context(|| "unable to determine git username")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn resolve_git_email(git: &Path, email: Option<&String>) -> Result<String> {
|
fn resolve_git_email(git: &Path, email: Option<&String>) -> Result<String> {
|
||||||
@@ -126,11 +125,10 @@ fn resolve_git_email(git: &Path, email: Option<&String>) -> Result<String> {
|
|||||||
return Ok(email.to_string());
|
return Ok(email.to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
run_git_config_capture(git, &["config", "user.email"])
|
default_git_email(git)
|
||||||
.with_context(|| "unable to determine git user email")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn resolve_git(override_path: Option<&PathBuf>) -> Result<PathBuf> {
|
pub(in crate::providers) fn resolve_git(override_path: Option<&PathBuf>) -> Result<PathBuf> {
|
||||||
debug!("Resolving git executable");
|
debug!("Resolving git executable");
|
||||||
if let Some(p) = override_path {
|
if let Some(p) = override_path {
|
||||||
return Ok(p.to_path_buf());
|
return Ok(p.to_path_buf());
|
||||||
@@ -141,7 +139,19 @@ fn resolve_git(override_path: Option<&PathBuf>) -> Result<PathBuf> {
|
|||||||
Ok(PathBuf::from("git"))
|
Ok(PathBuf::from("git"))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ensure_git_available(git: &Path) -> Result<()> {
|
pub(in crate::providers) fn default_git_username(git: &Path) -> Result<String> {
|
||||||
|
debug!("Checking for default git username");
|
||||||
|
run_git_config_capture(git, &["config", "user.name"])
|
||||||
|
.with_context(|| "unable to determine git user name")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(in crate::providers) fn default_git_email(git: &Path) -> Result<String> {
|
||||||
|
debug!("Checking for default git username");
|
||||||
|
run_git_config_capture(git, &["config", "user.email"])
|
||||||
|
.with_context(|| "unable to determine git user email")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(in crate::providers) fn ensure_git_available(git: &Path) -> Result<()> {
|
||||||
let ok = Command::new(git)
|
let ok = Command::new(git)
|
||||||
.arg("--version")
|
.arg("--version")
|
||||||
.stdout(Stdio::null())
|
.stdout(Stdio::null())
|
||||||
|
|||||||
@@ -0,0 +1,190 @@
|
|||||||
|
use crate::providers::{ENV_PATH, SecretProvider};
|
||||||
|
use anyhow::{Context, Result, anyhow};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use serde_with::skip_serializing_none;
|
||||||
|
use std::io::{Read, Write};
|
||||||
|
use std::process::{Command, Stdio};
|
||||||
|
use validator::Validate;
|
||||||
|
|
||||||
|
#[skip_serializing_none]
|
||||||
|
/// Gopass-based secret provider
|
||||||
|
/// See [Gopass](https://gopass.pw/) for more information.
|
||||||
|
///
|
||||||
|
/// You must already have gopass installed and configured on your system.
|
||||||
|
///
|
||||||
|
/// This provider stores secrets in a gopass store. It requires
|
||||||
|
/// an optional store name to be specified. If no store name is
|
||||||
|
/// specified, the default store will be used.
|
||||||
|
///
|
||||||
|
/// Example
|
||||||
|
/// ```no_run
|
||||||
|
/// use gman::providers::gopass::GopassProvider;
|
||||||
|
/// use gman::providers::{SecretProvider, SupportedProvider};
|
||||||
|
/// use gman::config::Config;
|
||||||
|
///
|
||||||
|
/// let provider = GopassProvider::default();
|
||||||
|
/// let _ = provider.set_secret("MY_SECRET", "value");
|
||||||
|
/// ```
|
||||||
|
#[derive(Debug, Default, Clone, Validate, Serialize, Deserialize, PartialEq, Eq)]
|
||||||
|
#[serde(deny_unknown_fields)]
|
||||||
|
pub struct GopassProvider {
|
||||||
|
pub store: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl SecretProvider for GopassProvider {
|
||||||
|
fn name(&self) -> &'static str {
|
||||||
|
"GopassProvider"
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_secret(&self, key: &str) -> Result<String> {
|
||||||
|
ensure_gopass_installed()?;
|
||||||
|
|
||||||
|
let mut child = Command::new("gopass")
|
||||||
|
.args(["show", "-yfon", key])
|
||||||
|
.env("PATH", ENV_PATH.as_ref().expect("No ENV_PATH set"))
|
||||||
|
.stdin(Stdio::inherit())
|
||||||
|
.stdout(Stdio::piped())
|
||||||
|
.stderr(Stdio::inherit())
|
||||||
|
.spawn()
|
||||||
|
.context("Failed to spawn gopass command")?;
|
||||||
|
|
||||||
|
let mut output = String::new();
|
||||||
|
child
|
||||||
|
.stdout
|
||||||
|
.as_mut()
|
||||||
|
.expect("Failed to open gopass stdout")
|
||||||
|
.read_to_string(&mut output)
|
||||||
|
.context("Failed to read gopass output")?;
|
||||||
|
|
||||||
|
let status = child.wait().context("Failed to wait on gopass process")?;
|
||||||
|
if !status.success() {
|
||||||
|
return Err(anyhow!("gopass command failed with status: {}", status));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(output.trim_end_matches(&['\r', '\n'][..]).to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn set_secret(&self, key: &str, value: &str) -> Result<()> {
|
||||||
|
ensure_gopass_installed()?;
|
||||||
|
|
||||||
|
let mut child = Command::new("gopass")
|
||||||
|
.args(["insert", "-f", key])
|
||||||
|
.env("PATH", ENV_PATH.as_ref().expect("No ENV_PATH set"))
|
||||||
|
.stdin(Stdio::piped())
|
||||||
|
.stdout(Stdio::inherit())
|
||||||
|
.stderr(Stdio::inherit())
|
||||||
|
.spawn()
|
||||||
|
.context("Failed to spawn gopass command")?;
|
||||||
|
|
||||||
|
{
|
||||||
|
let stdin = child.stdin.as_mut().expect("Failed to open gopass stdin");
|
||||||
|
stdin
|
||||||
|
.write_all(value.as_bytes())
|
||||||
|
.context("Failed to write to gopass stdin")?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let status = child.wait().context("Failed to wait on gopass process")?;
|
||||||
|
if !status.success() {
|
||||||
|
return Err(anyhow!("gopass command failed with status: {}", status));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn update_secret(&self, key: &str, value: &str) -> Result<()> {
|
||||||
|
ensure_gopass_installed()?;
|
||||||
|
|
||||||
|
self.set_secret(key, value).await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn delete_secret(&self, key: &str) -> Result<()> {
|
||||||
|
ensure_gopass_installed()?;
|
||||||
|
|
||||||
|
let mut child = Command::new("gopass")
|
||||||
|
.args(["rm", "-f", key])
|
||||||
|
.env("PATH", ENV_PATH.as_ref().expect("No ENV_PATH set"))
|
||||||
|
.stdin(Stdio::inherit())
|
||||||
|
.stdout(Stdio::inherit())
|
||||||
|
.stderr(Stdio::inherit())
|
||||||
|
.spawn()
|
||||||
|
.context("Failed to spawn gopass command")?;
|
||||||
|
|
||||||
|
let status = child.wait().context("Failed to wait on gopass process")?;
|
||||||
|
if !status.success() {
|
||||||
|
return Err(anyhow!("gopass command failed with status: {}", status));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn list_secrets(&self) -> Result<Vec<String>> {
|
||||||
|
ensure_gopass_installed()?;
|
||||||
|
|
||||||
|
let mut child = Command::new("gopass")
|
||||||
|
.args(["ls", "-f"])
|
||||||
|
.env("PATH", ENV_PATH.as_ref().expect("No ENV_PATH set"))
|
||||||
|
.stdin(Stdio::inherit())
|
||||||
|
.stdout(Stdio::piped())
|
||||||
|
.stderr(Stdio::inherit())
|
||||||
|
.spawn()
|
||||||
|
.context("Failed to spawn gopass command")?;
|
||||||
|
|
||||||
|
let mut output = String::new();
|
||||||
|
child
|
||||||
|
.stdout
|
||||||
|
.as_mut()
|
||||||
|
.expect("Failed to open gopass stdout")
|
||||||
|
.read_to_string(&mut output)
|
||||||
|
.context("Failed to read gopass output")?;
|
||||||
|
|
||||||
|
let status = child.wait().context("Failed to wait on gopass process")?;
|
||||||
|
if !status.success() {
|
||||||
|
return Err(anyhow!("gopass command failed with status: {}", status));
|
||||||
|
}
|
||||||
|
|
||||||
|
let secrets: Vec<String> = output
|
||||||
|
.lines()
|
||||||
|
.map(|line| line.trim().to_string())
|
||||||
|
.filter(|line| !line.is_empty())
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Ok(secrets)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn sync(&mut self) -> Result<()> {
|
||||||
|
ensure_gopass_installed()?;
|
||||||
|
let mut child = Command::new("gopass");
|
||||||
|
child.arg("sync");
|
||||||
|
|
||||||
|
if let Some(store) = &self.store {
|
||||||
|
child.args(["-s", store]);
|
||||||
|
}
|
||||||
|
|
||||||
|
let status = child
|
||||||
|
.env("PATH", ENV_PATH.as_ref().expect("No ENV_PATH set"))
|
||||||
|
.stdin(Stdio::inherit())
|
||||||
|
.stdout(Stdio::inherit())
|
||||||
|
.stderr(Stdio::inherit())
|
||||||
|
.spawn()
|
||||||
|
.context("Failed to spawn gopass command")?
|
||||||
|
.wait()
|
||||||
|
.context("Failed to wait on gopass process")?;
|
||||||
|
|
||||||
|
if !status.success() {
|
||||||
|
return Err(anyhow!("gopass command failed with status: {}", status));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ensure_gopass_installed() -> Result<()> {
|
||||||
|
if which::which("gopass").is_err() {
|
||||||
|
Err(anyhow!(
|
||||||
|
"Gopass is not installed or not found in PATH. Please install Gopass from https://gopass.pw/"
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
+194
-7
@@ -5,9 +5,12 @@ use std::path::{Path, PathBuf};
|
|||||||
use std::{env, fs};
|
use std::{env, fs};
|
||||||
use zeroize::Zeroize;
|
use zeroize::Zeroize;
|
||||||
|
|
||||||
use crate::config::Config;
|
use crate::config::{Config, get_config_file_path, load_config};
|
||||||
use crate::providers::SecretProvider;
|
use crate::providers::git_sync::{
|
||||||
use crate::providers::git_sync::{SyncOpts, repo_name_from_url, sync_and_push};
|
SyncOpts, default_git_email, default_git_username, ensure_git_available, repo_name_from_url,
|
||||||
|
resolve_git, sync_and_push,
|
||||||
|
};
|
||||||
|
use crate::providers::{SecretProvider, SupportedProvider};
|
||||||
use crate::{
|
use crate::{
|
||||||
ARGON_M_COST_KIB, ARGON_P, ARGON_T_COST, HEADER, KDF, KEY_LEN, NONCE_LEN, SALT_LEN, VERSION,
|
ARGON_M_COST_KIB, ARGON_P, ARGON_T_COST, HEADER, KDF, KEY_LEN, NONCE_LEN, SALT_LEN, VERSION,
|
||||||
};
|
};
|
||||||
@@ -54,6 +57,8 @@ pub struct LocalProvider {
|
|||||||
#[validate(email)]
|
#[validate(email)]
|
||||||
pub git_user_email: Option<String>,
|
pub git_user_email: Option<String>,
|
||||||
pub git_executable: Option<PathBuf>,
|
pub git_executable: Option<PathBuf>,
|
||||||
|
#[serde(skip)]
|
||||||
|
pub runtime_provider_name: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for LocalProvider {
|
impl Default for LocalProvider {
|
||||||
@@ -65,6 +70,7 @@ impl Default for LocalProvider {
|
|||||||
git_user_name: None,
|
git_user_name: None,
|
||||||
git_user_email: None,
|
git_user_email: None,
|
||||||
git_executable: None,
|
git_executable: None,
|
||||||
|
runtime_provider_name: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -153,6 +159,8 @@ impl SecretProvider for LocalProvider {
|
|||||||
|
|
||||||
async fn sync(&mut self) -> Result<()> {
|
async fn sync(&mut self) -> Result<()> {
|
||||||
let mut config_changed = false;
|
let mut config_changed = false;
|
||||||
|
let git = resolve_git(self.git_executable.as_ref())?;
|
||||||
|
ensure_git_available(&git)?;
|
||||||
|
|
||||||
if self.git_branch.is_none() {
|
if self.git_branch.is_none() {
|
||||||
config_changed = true;
|
config_changed = true;
|
||||||
@@ -169,7 +177,9 @@ impl SecretProvider for LocalProvider {
|
|||||||
config_changed = true;
|
config_changed = true;
|
||||||
debug!("Prompting user to set git_remote in config for sync");
|
debug!("Prompting user to set git_remote in config for sync");
|
||||||
let remote: String = Input::with_theme(&ColorfulTheme::default())
|
let remote: String = Input::with_theme(&ColorfulTheme::default())
|
||||||
.with_prompt("Enter remote git URL to sync with")
|
.with_prompt(
|
||||||
|
"Enter remote git URL to sync with (e.g. 'git@github.com:user/repo.git')",
|
||||||
|
)
|
||||||
.validate_with(|s: &String| {
|
.validate_with(|s: &String| {
|
||||||
LocalProvider {
|
LocalProvider {
|
||||||
git_remote_url: Some(s.clone()),
|
git_remote_url: Some(s.clone()),
|
||||||
@@ -184,10 +194,41 @@ impl SecretProvider for LocalProvider {
|
|||||||
self.git_remote_url = Some(remote);
|
self.git_remote_url = Some(remote);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if self.git_user_name.is_none() {
|
||||||
|
config_changed = true;
|
||||||
|
debug!("Prompting user git user name");
|
||||||
|
let default_user_name = default_git_username(&git)?.trim().to_string();
|
||||||
|
let branch: String = Input::with_theme(&ColorfulTheme::default())
|
||||||
|
.with_prompt("Enter git user name")
|
||||||
|
.default(default_user_name)
|
||||||
|
.interact_text()?;
|
||||||
|
|
||||||
|
self.git_user_name = Some(branch);
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.git_user_email.is_none() {
|
||||||
|
config_changed = true;
|
||||||
|
debug!("Prompting user git email");
|
||||||
|
let default_user_name = default_git_email(&git)?.trim().to_string();
|
||||||
|
let branch: String = Input::with_theme(&ColorfulTheme::default())
|
||||||
|
.with_prompt("Enter git user email")
|
||||||
|
.validate_with({
|
||||||
|
|s: &String| {
|
||||||
|
if s.contains('@') {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err("not a valid email address".to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.default(default_user_name)
|
||||||
|
.interact_text()?;
|
||||||
|
|
||||||
|
self.git_user_email = Some(branch);
|
||||||
|
}
|
||||||
|
|
||||||
if config_changed {
|
if config_changed {
|
||||||
debug!("Saving updated config");
|
self.persist_git_settings_to_config()?;
|
||||||
confy::store("gman", "config", &self)
|
|
||||||
.with_context(|| "failed to save updated config")?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let sync_opts = SyncOpts {
|
let sync_opts = SyncOpts {
|
||||||
@@ -203,6 +244,55 @@ impl SecretProvider for LocalProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl LocalProvider {
|
impl LocalProvider {
|
||||||
|
fn persist_git_settings_to_config(&self) -> Result<()> {
|
||||||
|
debug!("Saving updated config (only current local provider)");
|
||||||
|
|
||||||
|
let mut cfg = load_config().with_context(|| "failed to load existing config")?;
|
||||||
|
|
||||||
|
let target_name = self.runtime_provider_name.clone();
|
||||||
|
let mut updated = false;
|
||||||
|
for pc in cfg.providers.iter_mut() {
|
||||||
|
if let SupportedProvider::Local { provider_def } = &mut pc.provider_type {
|
||||||
|
let matches_name = match (&pc.name, &target_name) {
|
||||||
|
(Some(n), Some(t)) => n == t,
|
||||||
|
(Some(_), None) => false,
|
||||||
|
_ => false,
|
||||||
|
};
|
||||||
|
if matches_name || target_name.is_none() {
|
||||||
|
provider_def.git_branch = self.git_branch.clone();
|
||||||
|
provider_def.git_remote_url = self.git_remote_url.clone();
|
||||||
|
provider_def.git_user_name = self.git_user_name.clone();
|
||||||
|
provider_def.git_user_email = self.git_user_email.clone();
|
||||||
|
provider_def.git_executable = self.git_executable.clone();
|
||||||
|
|
||||||
|
updated = true;
|
||||||
|
if matches_name {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !updated {
|
||||||
|
bail!("unable to find matching local provider in config to update");
|
||||||
|
}
|
||||||
|
|
||||||
|
let path = get_config_file_path()?;
|
||||||
|
let ext = path.extension().and_then(|s| s.to_str()).unwrap_or("");
|
||||||
|
if ext.eq_ignore_ascii_case("yml") || ext.eq_ignore_ascii_case("yaml") {
|
||||||
|
if let Some(parent) = path.parent() {
|
||||||
|
fs::create_dir_all(parent)?;
|
||||||
|
}
|
||||||
|
let s = serde_yaml::to_string(&cfg)?;
|
||||||
|
fs::write(&path, s).with_context(|| format!("failed to write {}", path.display()))?;
|
||||||
|
} else {
|
||||||
|
confy::store("gman", "config", &cfg)
|
||||||
|
.with_context(|| "failed to save updated config via confy")?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn repo_dir_for_config(&self) -> Result<Option<PathBuf>> {
|
fn repo_dir_for_config(&self) -> Result<Option<PathBuf>> {
|
||||||
if let Some(remote) = &self.git_remote_url {
|
if let Some(remote) = &self.git_remote_url {
|
||||||
let name = repo_name_from_url(remote);
|
let name = repo_name_from_url(remote);
|
||||||
@@ -424,6 +514,7 @@ mod tests {
|
|||||||
use super::*;
|
use super::*;
|
||||||
use pretty_assertions::assert_eq;
|
use pretty_assertions::assert_eq;
|
||||||
use secrecy::{ExposeSecret, SecretString};
|
use secrecy::{ExposeSecret, SecretString};
|
||||||
|
use std::env as std_env;
|
||||||
use tempfile::tempdir;
|
use tempfile::tempdir;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -458,9 +549,105 @@ mod tests {
|
|||||||
fs::write(&file, "secretpw\n").unwrap();
|
fs::write(&file, "secretpw\n").unwrap();
|
||||||
let provider = LocalProvider {
|
let provider = LocalProvider {
|
||||||
password_file: Some(file),
|
password_file: Some(file),
|
||||||
|
runtime_provider_name: None,
|
||||||
..LocalProvider::default()
|
..LocalProvider::default()
|
||||||
};
|
};
|
||||||
let pw = provider.get_password().unwrap();
|
let pw = provider.get_password().unwrap();
|
||||||
assert_eq!(pw.expose_secret(), "secretpw");
|
assert_eq!(pw.expose_secret(), "secretpw");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn persist_only_target_local_provider_git_settings() {
|
||||||
|
let td = tempdir().unwrap();
|
||||||
|
let xdg = td.path().join("xdg");
|
||||||
|
let app_dir = xdg.join("gman");
|
||||||
|
fs::create_dir_all(&app_dir).unwrap();
|
||||||
|
unsafe {
|
||||||
|
std_env::set_var("XDG_CONFIG_HOME", &xdg);
|
||||||
|
}
|
||||||
|
|
||||||
|
let initial_yaml = indoc::indoc! {
|
||||||
|
"---
|
||||||
|
default_provider: local
|
||||||
|
providers:
|
||||||
|
- name: local
|
||||||
|
type: local
|
||||||
|
password_file: /tmp/.gman_pass
|
||||||
|
git_branch: main
|
||||||
|
git_remote_url: null
|
||||||
|
git_user_name: null
|
||||||
|
git_user_email: null
|
||||||
|
git_executable: null
|
||||||
|
- name: other
|
||||||
|
type: local
|
||||||
|
git_branch: main
|
||||||
|
git_remote_url: git@github.com:someone/else.git
|
||||||
|
run_configs:
|
||||||
|
- name: echo
|
||||||
|
secrets: [API_KEY]
|
||||||
|
"
|
||||||
|
};
|
||||||
|
let cfg_path = app_dir.join("config.yml");
|
||||||
|
fs::write(&cfg_path, initial_yaml).unwrap();
|
||||||
|
|
||||||
|
let provider = LocalProvider {
|
||||||
|
password_file: None,
|
||||||
|
git_branch: Some("dev".into()),
|
||||||
|
git_remote_url: Some("git@github.com:user/repo.git".into()),
|
||||||
|
git_user_name: Some("Test User".into()),
|
||||||
|
git_user_email: Some("test@example.com".into()),
|
||||||
|
git_executable: Some(PathBuf::from("/usr/bin/git")),
|
||||||
|
runtime_provider_name: Some("local".into()),
|
||||||
|
};
|
||||||
|
|
||||||
|
provider
|
||||||
|
.persist_git_settings_to_config()
|
||||||
|
.expect("persist ok");
|
||||||
|
|
||||||
|
let content = fs::read_to_string(&cfg_path).unwrap();
|
||||||
|
let cfg: Config = serde_yaml::from_str(&content).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(cfg.default_provider.as_deref(), Some("local"));
|
||||||
|
assert!(cfg.run_configs.is_some());
|
||||||
|
assert_eq!(cfg.run_configs.as_ref().unwrap().len(), 1);
|
||||||
|
|
||||||
|
let p0 = &cfg.providers[0];
|
||||||
|
assert_eq!(p0.name.as_deref(), Some("local"));
|
||||||
|
match &p0.provider_type {
|
||||||
|
SupportedProvider::Local { provider_def } => {
|
||||||
|
assert_eq!(provider_def.git_branch.as_deref(), Some("dev"));
|
||||||
|
assert_eq!(
|
||||||
|
provider_def.git_remote_url.as_deref(),
|
||||||
|
Some("git@github.com:user/repo.git")
|
||||||
|
);
|
||||||
|
assert_eq!(provider_def.git_user_name.as_deref(), Some("Test User"));
|
||||||
|
assert_eq!(
|
||||||
|
provider_def.git_user_email.as_deref(),
|
||||||
|
Some("test@example.com")
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
provider_def.git_executable.as_ref(),
|
||||||
|
Some(&PathBuf::from("/usr/bin/git"))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_ => panic!("expected local provider"),
|
||||||
|
}
|
||||||
|
|
||||||
|
let p1 = &cfg.providers[1];
|
||||||
|
assert_eq!(p1.name.as_deref(), Some("other"));
|
||||||
|
match &p1.provider_type {
|
||||||
|
SupportedProvider::Local { provider_def } => {
|
||||||
|
assert_eq!(provider_def.git_branch.as_deref(), Some("main"));
|
||||||
|
assert_eq!(
|
||||||
|
provider_def.git_remote_url.as_deref(),
|
||||||
|
Some("git@github.com:someone/else.git")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_ => panic!("expected local provider"),
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
std_env::remove_var("XDG_CONFIG_HOME");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+16
-3
@@ -6,17 +6,24 @@ pub mod aws_secrets_manager;
|
|||||||
pub mod azure_key_vault;
|
pub mod azure_key_vault;
|
||||||
pub mod gcp_secret_manager;
|
pub mod gcp_secret_manager;
|
||||||
mod git_sync;
|
mod git_sync;
|
||||||
|
pub mod gopass;
|
||||||
pub mod local;
|
pub mod local;
|
||||||
|
|
||||||
|
use crate::providers::gopass::GopassProvider;
|
||||||
use crate::providers::local::LocalProvider;
|
use crate::providers::local::LocalProvider;
|
||||||
use anyhow::{Result, anyhow};
|
use anyhow::{Context, Result, anyhow};
|
||||||
use aws_secrets_manager::AwsSecretsManagerProvider;
|
use aws_secrets_manager::AwsSecretsManagerProvider;
|
||||||
|
use azure_key_vault::AzureKeyVaultProvider;
|
||||||
use gcp_secret_manager::GcpSecretManagerProvider;
|
use gcp_secret_manager::GcpSecretManagerProvider;
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::fmt;
|
|
||||||
use std::fmt::{Display, Formatter};
|
use std::fmt::{Display, Formatter};
|
||||||
|
use std::{env, fmt};
|
||||||
use validator::{Validate, ValidationErrors};
|
use validator::{Validate, ValidationErrors};
|
||||||
|
|
||||||
|
pub(in crate::providers) static ENV_PATH: Lazy<Result<String>> =
|
||||||
|
Lazy::new(|| env::var("PATH").context("No PATH environment variable"));
|
||||||
|
|
||||||
/// A secret storage backend capable of CRUD, with optional
|
/// A secret storage backend capable of CRUD, with optional
|
||||||
/// update, listing, and sync support.
|
/// update, listing, and sync support.
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
@@ -63,7 +70,11 @@ pub enum SupportedProvider {
|
|||||||
},
|
},
|
||||||
AzureKeyVault {
|
AzureKeyVault {
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
provider_def: azure_key_vault::AzureKeyVaultProvider,
|
provider_def: AzureKeyVaultProvider,
|
||||||
|
},
|
||||||
|
Gopass {
|
||||||
|
#[serde(flatten)]
|
||||||
|
provider_def: GopassProvider,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,6 +85,7 @@ impl Validate for SupportedProvider {
|
|||||||
SupportedProvider::AwsSecretsManager { provider_def } => provider_def.validate(),
|
SupportedProvider::AwsSecretsManager { provider_def } => provider_def.validate(),
|
||||||
SupportedProvider::GcpSecretManager { provider_def } => provider_def.validate(),
|
SupportedProvider::GcpSecretManager { provider_def } => provider_def.validate(),
|
||||||
SupportedProvider::AzureKeyVault { provider_def } => provider_def.validate(),
|
SupportedProvider::AzureKeyVault { provider_def } => provider_def.validate(),
|
||||||
|
SupportedProvider::Gopass { provider_def } => provider_def.validate(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -93,6 +105,7 @@ impl Display for SupportedProvider {
|
|||||||
SupportedProvider::AwsSecretsManager { .. } => write!(f, "aws_secrets_manager"),
|
SupportedProvider::AwsSecretsManager { .. } => write!(f, "aws_secrets_manager"),
|
||||||
SupportedProvider::GcpSecretManager { .. } => write!(f, "gcp_secret_manager"),
|
SupportedProvider::GcpSecretManager { .. } => write!(f, "gcp_secret_manager"),
|
||||||
SupportedProvider::AzureKeyVault { .. } => write!(f, "azure_key_vault"),
|
SupportedProvider::AzureKeyVault { .. } => write!(f, "azure_key_vault"),
|
||||||
|
SupportedProvider::Gopass { .. } => write!(f, "gopass"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
use assert_cmd::prelude::*;
|
use assert_cmd::prelude::*;
|
||||||
use predicates::prelude::*;
|
use predicates::prelude::*;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
|
#[cfg(unix)]
|
||||||
|
use std::os::unix::fs::PermissionsExt;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::process::{Command, Stdio};
|
use std::process::{Command, Stdio};
|
||||||
use tempfile::TempDir;
|
use tempfile::TempDir;
|
||||||
@@ -49,6 +51,73 @@ providers:
|
|||||||
fs::write(app_dir.join("config.yaml"), &cfg).unwrap();
|
fs::write(app_dir.join("config.yaml"), &cfg).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(unix)]
|
||||||
|
fn cli_config_no_changes() {
|
||||||
|
let (td, xdg_cfg, xdg_cache) = setup_env();
|
||||||
|
let pw_file = td.path().join("pw.txt");
|
||||||
|
fs::write(&pw_file, b"pw\n").unwrap();
|
||||||
|
write_yaml_config(&xdg_cfg, &pw_file, None);
|
||||||
|
|
||||||
|
// Create a no-op editor script that exits successfully without modifying the file
|
||||||
|
let editor = td.path().join("noop-editor.sh");
|
||||||
|
fs::write(&editor, b"#!/bin/sh\nexit 0\n").unwrap();
|
||||||
|
let mut perms = fs::metadata(&editor).unwrap().permissions();
|
||||||
|
perms.set_mode(0o755);
|
||||||
|
fs::set_permissions(&editor, perms).unwrap();
|
||||||
|
|
||||||
|
let mut cmd = Command::cargo_bin("gman").unwrap();
|
||||||
|
cmd.env("XDG_CONFIG_HOME", &xdg_cfg)
|
||||||
|
.env("XDG_CACHE_HOME", &xdg_cache)
|
||||||
|
.env("EDITOR", &editor)
|
||||||
|
.arg("config");
|
||||||
|
|
||||||
|
cmd.assert()
|
||||||
|
.success()
|
||||||
|
.stdout(predicate::str::contains("No changes made to configuration"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(unix)]
|
||||||
|
fn cli_config_updates_and_persists() {
|
||||||
|
let (td, xdg_cfg, xdg_cache) = setup_env();
|
||||||
|
let pw_file = td.path().join("pw.txt");
|
||||||
|
fs::write(&pw_file, b"pw\n").unwrap();
|
||||||
|
write_yaml_config(&xdg_cfg, &pw_file, None);
|
||||||
|
|
||||||
|
// Editor script appends a valid run_configs section to the YAML file
|
||||||
|
let editor = td.path().join("append-run-config.sh");
|
||||||
|
let script = r#"#!/bin/sh
|
||||||
|
FILE="$1"
|
||||||
|
cat >> "$FILE" <<'EOF'
|
||||||
|
run_configs:
|
||||||
|
- name: echo
|
||||||
|
secrets: ["api_key"]
|
||||||
|
EOF
|
||||||
|
exit 0
|
||||||
|
"#;
|
||||||
|
fs::write(&editor, script.as_bytes()).unwrap();
|
||||||
|
let mut perms = fs::metadata(&editor).unwrap().permissions();
|
||||||
|
perms.set_mode(0o755);
|
||||||
|
fs::set_permissions(&editor, perms).unwrap();
|
||||||
|
|
||||||
|
let mut cmd = Command::cargo_bin("gman").unwrap();
|
||||||
|
cmd.env("XDG_CONFIG_HOME", &xdg_cfg)
|
||||||
|
.env("XDG_CACHE_HOME", &xdg_cache)
|
||||||
|
.env("EDITOR", &editor)
|
||||||
|
.arg("config");
|
||||||
|
|
||||||
|
cmd.assert().success().stdout(predicate::str::contains(
|
||||||
|
"Configuration updated successfully",
|
||||||
|
));
|
||||||
|
|
||||||
|
// Verify that the config file now contains the run_configs key
|
||||||
|
let cfg_path = xdg_cfg.join("gman").join("config.yml");
|
||||||
|
let written = fs::read_to_string(&cfg_path).expect("config file readable");
|
||||||
|
assert!(written.contains("run_configs:"));
|
||||||
|
assert!(written.contains("name: echo"));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn cli_shows_help() {
|
fn cli_shows_help() {
|
||||||
let (_td, cfg, cache) = setup_env();
|
let (_td, cfg, cache) = setup_env();
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ mod tests {
|
|||||||
fn test_run_config_valid() {
|
fn test_run_config_valid() {
|
||||||
let run_config = RunConfig {
|
let run_config = RunConfig {
|
||||||
name: Some("test".to_string()),
|
name: Some("test".to_string()),
|
||||||
|
provider: None,
|
||||||
secrets: Some(vec!["secret1".to_string()]),
|
secrets: Some(vec!["secret1".to_string()]),
|
||||||
flag: None,
|
flag: None,
|
||||||
flag_position: None,
|
flag_position: None,
|
||||||
@@ -23,6 +24,7 @@ mod tests {
|
|||||||
fn test_run_config_missing_name() {
|
fn test_run_config_missing_name() {
|
||||||
let run_config = RunConfig {
|
let run_config = RunConfig {
|
||||||
name: None,
|
name: None,
|
||||||
|
provider: None,
|
||||||
secrets: Some(vec!["secret1".to_string()]),
|
secrets: Some(vec!["secret1".to_string()]),
|
||||||
flag: None,
|
flag: None,
|
||||||
flag_position: None,
|
flag_position: None,
|
||||||
@@ -37,6 +39,7 @@ mod tests {
|
|||||||
fn test_run_config_missing_secrets() {
|
fn test_run_config_missing_secrets() {
|
||||||
let run_config = RunConfig {
|
let run_config = RunConfig {
|
||||||
name: Some("test".to_string()),
|
name: Some("test".to_string()),
|
||||||
|
provider: None,
|
||||||
secrets: None,
|
secrets: None,
|
||||||
flag: None,
|
flag: None,
|
||||||
flag_position: None,
|
flag_position: None,
|
||||||
@@ -51,6 +54,7 @@ mod tests {
|
|||||||
fn test_run_config_invalid_flag_position() {
|
fn test_run_config_invalid_flag_position() {
|
||||||
let run_config = RunConfig {
|
let run_config = RunConfig {
|
||||||
name: Some("test".to_string()),
|
name: Some("test".to_string()),
|
||||||
|
provider: None,
|
||||||
secrets: Some(vec!["secret1".to_string()]),
|
secrets: Some(vec!["secret1".to_string()]),
|
||||||
flag: Some("--test-flag".to_string()),
|
flag: Some("--test-flag".to_string()),
|
||||||
flag_position: Some(0),
|
flag_position: Some(0),
|
||||||
@@ -65,6 +69,7 @@ mod tests {
|
|||||||
fn test_run_config_flags_or_none_all_some() {
|
fn test_run_config_flags_or_none_all_some() {
|
||||||
let run_config = RunConfig {
|
let run_config = RunConfig {
|
||||||
name: Some("test".to_string()),
|
name: Some("test".to_string()),
|
||||||
|
provider: None,
|
||||||
secrets: Some(vec!["secret1".to_string()]),
|
secrets: Some(vec!["secret1".to_string()]),
|
||||||
flag: Some("--test-flag".to_string()),
|
flag: Some("--test-flag".to_string()),
|
||||||
flag_position: Some(1),
|
flag_position: Some(1),
|
||||||
@@ -79,6 +84,7 @@ mod tests {
|
|||||||
fn test_run_config_flags_or_none_all_none() {
|
fn test_run_config_flags_or_none_all_none() {
|
||||||
let run_config = RunConfig {
|
let run_config = RunConfig {
|
||||||
name: Some("test".to_string()),
|
name: Some("test".to_string()),
|
||||||
|
provider: None,
|
||||||
secrets: Some(vec!["secret1".to_string()]),
|
secrets: Some(vec!["secret1".to_string()]),
|
||||||
flag: None,
|
flag: None,
|
||||||
flag_position: None,
|
flag_position: None,
|
||||||
@@ -93,6 +99,7 @@ mod tests {
|
|||||||
fn test_run_config_flags_or_none_partial_some() {
|
fn test_run_config_flags_or_none_partial_some() {
|
||||||
let run_config = RunConfig {
|
let run_config = RunConfig {
|
||||||
name: Some("test".to_string()),
|
name: Some("test".to_string()),
|
||||||
|
provider: None,
|
||||||
secrets: Some(vec!["secret1".to_string()]),
|
secrets: Some(vec!["secret1".to_string()]),
|
||||||
flag: Some("--test-flag".to_string()),
|
flag: Some("--test-flag".to_string()),
|
||||||
flag_position: None,
|
flag_position: None,
|
||||||
@@ -107,6 +114,7 @@ mod tests {
|
|||||||
fn test_run_config_flags_or_none_missing_placeholder() {
|
fn test_run_config_flags_or_none_missing_placeholder() {
|
||||||
let run_config = RunConfig {
|
let run_config = RunConfig {
|
||||||
name: Some("test".to_string()),
|
name: Some("test".to_string()),
|
||||||
|
provider: None,
|
||||||
secrets: Some(vec!["secret1".to_string()]),
|
secrets: Some(vec!["secret1".to_string()]),
|
||||||
flag: Some("--test-flag".to_string()),
|
flag: Some("--test-flag".to_string()),
|
||||||
flag_position: Some(1),
|
flag_position: Some(1),
|
||||||
@@ -121,6 +129,7 @@ mod tests {
|
|||||||
fn test_run_config_flags_or_files_all_none() {
|
fn test_run_config_flags_or_files_all_none() {
|
||||||
let run_config = RunConfig {
|
let run_config = RunConfig {
|
||||||
name: Some("test".to_string()),
|
name: Some("test".to_string()),
|
||||||
|
provider: None,
|
||||||
secrets: Some(vec!["secret1".to_string()]),
|
secrets: Some(vec!["secret1".to_string()]),
|
||||||
flag: None,
|
flag: None,
|
||||||
flag_position: None,
|
flag_position: None,
|
||||||
@@ -135,6 +144,7 @@ mod tests {
|
|||||||
fn test_run_config_flags_or_files_files_is_some() {
|
fn test_run_config_flags_or_files_files_is_some() {
|
||||||
let run_config = RunConfig {
|
let run_config = RunConfig {
|
||||||
name: Some("test".to_string()),
|
name: Some("test".to_string()),
|
||||||
|
provider: None,
|
||||||
secrets: Some(vec!["secret1".to_string()]),
|
secrets: Some(vec!["secret1".to_string()]),
|
||||||
flag: None,
|
flag: None,
|
||||||
flag_position: None,
|
flag_position: None,
|
||||||
@@ -149,6 +159,7 @@ mod tests {
|
|||||||
fn test_run_config_flags_or_files_all_some() {
|
fn test_run_config_flags_or_files_all_some() {
|
||||||
let run_config = RunConfig {
|
let run_config = RunConfig {
|
||||||
name: Some("test".to_string()),
|
name: Some("test".to_string()),
|
||||||
|
provider: None,
|
||||||
secrets: Some(vec!["secret1".to_string()]),
|
secrets: Some(vec!["secret1".to_string()]),
|
||||||
flag: Some("--test-flag".to_string()),
|
flag: Some("--test-flag".to_string()),
|
||||||
flag_position: Some(1),
|
flag_position: Some(1),
|
||||||
@@ -252,4 +263,25 @@ mod tests {
|
|||||||
assert_eq!(path, None);
|
assert_eq!(path, None);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_config_duplicate_provider_names_is_invalid() {
|
||||||
|
let name = Some("dup".into());
|
||||||
|
let p1 = ProviderConfig {
|
||||||
|
name: name.clone(),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
let p2 = ProviderConfig {
|
||||||
|
name,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let cfg = Config {
|
||||||
|
default_provider: Some("dup".into()),
|
||||||
|
providers: vec![p1, p2],
|
||||||
|
run_configs: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
assert!(cfg.validate().is_err());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,53 @@
|
|||||||
|
use gman::config::{Config, ProviderConfig};
|
||||||
|
use gman::providers::{SecretProvider, SupportedProvider};
|
||||||
|
use pretty_assertions::{assert_eq, assert_str_eq};
|
||||||
|
use validator::Validate;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_gopass_supported_provider_display_and_validate_from_yaml() {
|
||||||
|
// Build a SupportedProvider via YAML to avoid direct type import
|
||||||
|
let yaml = r#"---
|
||||||
|
type: gopass
|
||||||
|
store: personal
|
||||||
|
"#;
|
||||||
|
|
||||||
|
let sp: SupportedProvider = serde_yaml::from_str(yaml).expect("valid supported provider yaml");
|
||||||
|
// Validate delegates to inner provider (no required fields)
|
||||||
|
assert!(sp.validate().is_ok());
|
||||||
|
// Display formatting for the enum variant
|
||||||
|
assert_eq!(sp.to_string(), "gopass");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_provider_config_with_gopass_deserialize_and_extract() {
|
||||||
|
// Minimal ProviderConfig YAML using the gopass variant
|
||||||
|
let yaml = r#"---
|
||||||
|
name: gopass
|
||||||
|
type: gopass
|
||||||
|
"#;
|
||||||
|
|
||||||
|
let pc: ProviderConfig = serde_yaml::from_str(yaml).expect("valid provider config yaml");
|
||||||
|
// Gopass has no required fields, so validation should pass
|
||||||
|
assert!(pc.validate().is_ok());
|
||||||
|
|
||||||
|
// Extract the provider and inspect its name via the trait
|
||||||
|
let mut pc_owned = pc.clone();
|
||||||
|
let provider: &mut dyn SecretProvider = pc_owned.extract_provider();
|
||||||
|
assert_str_eq!(provider.name(), "GopassProvider");
|
||||||
|
|
||||||
|
// Round-trip through Config with default_provider
|
||||||
|
let cfg_yaml = r#"---
|
||||||
|
default_provider: gopass
|
||||||
|
providers:
|
||||||
|
- name: gopass
|
||||||
|
type: gopass
|
||||||
|
store: personal
|
||||||
|
"#;
|
||||||
|
let cfg: Config = serde_yaml::from_str(cfg_yaml).expect("valid config yaml");
|
||||||
|
assert!(cfg.validate().is_ok());
|
||||||
|
|
||||||
|
let extracted = cfg
|
||||||
|
.extract_provider_config(None)
|
||||||
|
.expect("should find default provider");
|
||||||
|
assert_eq!(extracted.name.as_deref(), Some("gopass"));
|
||||||
|
}
|
||||||
@@ -34,6 +34,7 @@ fn test_local_provider_valid() {
|
|||||||
git_user_name: None,
|
git_user_name: None,
|
||||||
git_user_email: Some("test@example.com".to_string()),
|
git_user_email: Some("test@example.com".to_string()),
|
||||||
git_executable: None,
|
git_executable: None,
|
||||||
|
runtime_provider_name: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
assert!(provider.validate().is_ok());
|
assert!(provider.validate().is_ok());
|
||||||
@@ -48,6 +49,7 @@ fn test_local_provider_invalid_email() {
|
|||||||
git_user_name: None,
|
git_user_name: None,
|
||||||
git_user_email: Some("test".to_string()),
|
git_user_email: Some("test".to_string()),
|
||||||
git_executable: None,
|
git_executable: None,
|
||||||
|
runtime_provider_name: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
assert!(config.validate().is_err());
|
assert!(config.validate().is_err());
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
mod aws_secrets_manager_tests;
|
mod aws_secrets_manager_tests;
|
||||||
mod azure_key_vault_tests;
|
mod azure_key_vault_tests;
|
||||||
mod gcp_secret_manager_tests;
|
mod gcp_secret_manager_tests;
|
||||||
|
mod gopass_tests;
|
||||||
mod local_tests;
|
mod local_tests;
|
||||||
mod provider_tests;
|
mod provider_tests;
|
||||||
|
|||||||
Reference in New Issue
Block a user