Compare commits
26 Commits
183a27c8e1
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 68f0d2efc9 | |||
| e6b4fdec1a | |||
| b908f3f1f0 | |||
|
|
4de8cc79f2 | ||
| 1cbbe22dc1 | |||
| 42c2dd933c | |||
|
|
b90a6a0746 | ||
| c83a4f115a | |||
| ca8160568b | |||
| 1d66fc2edc | |||
| c078feceac | |||
| a155a48aaa | |||
| d2f8eaf090 | |||
| 4178864d33 | |||
| 4d7ab9138d | |||
| 19f03f2c4c | |||
| 663b830dc4 | |||
| 89bc15a4a9 | |||
| 62b1e4c6ef | |||
| 5723140bce | |||
| 6325017ba7 | |||
| 65bbb79396 | |||
| 374b4e48bf | |||
| a40a96e662 | |||
| f2be3898ff | |||
| a97dbbc285 |
30
.gitea/workflows/build.yml
Normal file
30
.gitea/workflows/build.yml
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
name: Build Docker Image
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
paths:
|
||||||
|
- Dockerfile
|
||||||
|
- entrypoint.sh
|
||||||
|
- .gitea/workflows/build.yml
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
|
- name: Build Docker image
|
||||||
|
run: |
|
||||||
|
docker build -t skoszewski/azure-cli:latest .
|
||||||
|
|
||||||
|
- name: Log in to Docker Hub
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
username: ${{ vars.DOCKER_HUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}
|
||||||
|
|
||||||
|
- name: Push Docker image to Docker Hub
|
||||||
|
run: |
|
||||||
|
docker push skoszewski/azure-cli:latest
|
||||||
8
.vscode/settings.json
vendored
Normal file
8
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"files.associations": {
|
||||||
|
"./build": "shellscript",
|
||||||
|
"./azure-cli": "shellscript",
|
||||||
|
"./link-to-bin": "shellscript",
|
||||||
|
"./entrypoint.sh": "shellscript"
|
||||||
|
}
|
||||||
|
}
|
||||||
25
Dockerfile
25
Dockerfile
@@ -18,23 +18,22 @@ RUN curl -sL -o /tmp/packages-microsoft-prod.deb https://packages.microsoft.com/
|
|||||||
RUN echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/microsoft-prod.gpg] https://packages.microsoft.com/repos/azure-cli/ noble main" > /etc/apt/sources.list.d/azure-cli.list
|
RUN echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/microsoft-prod.gpg] https://packages.microsoft.com/repos/azure-cli/ noble main" > /etc/apt/sources.list.d/azure-cli.list
|
||||||
RUN apt-get update && apt-get install -y azure-cli
|
RUN apt-get update && apt-get install -y azure-cli
|
||||||
|
|
||||||
# Configure Bash
|
|
||||||
RUN cat <<'EOF' >> /etc/bash.bashrc
|
|
||||||
if [ -f /usr/lib/git-core/git-sh-prompt ]; then
|
|
||||||
source /usr/lib/git-core/git-sh-prompt
|
|
||||||
|
|
||||||
# Then PS1 can include __git_ps1 which is optimized and shows branch + state:
|
|
||||||
PS1='\[\e[32m\]\u@\h\[\e[0m\]:\[\e[34m\]\w\[\e[0m\]\[\e[33m\]$(__git_ps1 " (%s)")\[\e[0m\]\$ '
|
|
||||||
fi
|
|
||||||
|
|
||||||
export PATH=$HOME/bin:$PATH
|
|
||||||
EOF
|
|
||||||
|
|
||||||
# Add more packages (keep it last to optimize layer caching)
|
# Add more packages (keep it last to optimize layer caching)
|
||||||
RUN apt-get install -y zip unzip tree wget nano neovim \
|
RUN apt-get install -y zip unzip tree wget nano neovim \
|
||||||
python3 python3-venv python3-pip
|
python3 python3-venv python3-pip
|
||||||
|
|
||||||
|
COPY enable-git-bash-prompt /usr/local/bin/enable-git-bash-prompt
|
||||||
|
|
||||||
|
# Patch the default .bashrc to enable git bash prompt
|
||||||
|
RUN cat <<'EOF' >> /etc/skel/.bashrc
|
||||||
|
|
||||||
|
# Enable git bash prompt
|
||||||
|
if [ -f /usr/local/bin/enable-git-bash-prompt ]; then
|
||||||
|
source /usr/local/bin/enable-git-bash-prompt
|
||||||
|
fi
|
||||||
|
EOF
|
||||||
|
|
||||||
COPY entrypoint.sh /entrypoint.sh
|
COPY entrypoint.sh /entrypoint.sh
|
||||||
RUN chmod +x /entrypoint.sh
|
RUN chmod +x /entrypoint.sh
|
||||||
ENTRYPOINT ["/entrypoint.sh"]
|
|
||||||
|
|
||||||
|
ENTRYPOINT ["/entrypoint.sh"]
|
||||||
|
|||||||
28
README.md
28
README.md
@@ -1,20 +1,32 @@
|
|||||||
# Azure CLI container
|
# Azure CLI container
|
||||||
|
|
||||||
|
[](https://gitea.koszewscy.waw.pl/slawek/azure-cli/actions?workflow=build.yml)
|
||||||
|
|
||||||
This repository contains a Dockerfile for building an Azure CLI container image.
|
This repository contains a Dockerfile for building an Azure CLI container image.
|
||||||
|
|
||||||
It is designed to be used as a operating environment for running Azure CLI commands,
|
It is designed to be used as a operating environment for running Azure CLI commands,
|
||||||
while providing isolation from the host system. User's home directory is mounted
|
while providing isolation from the host system. Volume is used to
|
||||||
into the container to persist configuration and credentials. Volume is used to
|
|
||||||
persist home directory data.
|
persist home directory data.
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
```bash
|
```text
|
||||||
azure-cli [--account ACCOUNT_NAME] [--user CONTAINER_USERNAME] [ container CLI arguments... ]
|
azure-cli (-l|--list)
|
||||||
```
|
```
|
||||||
|
|
||||||
- `--account ACCOUNT_NAME` - Specify the account name to use for the volume that stores the home directory data. Defaults to the current user's username.
|
List all containers created by this script.
|
||||||
- `--user CONTAINER_USERNAME` - Specify the username to use inside the container. Defaults `ubuntu`.
|
|
||||||
- `container CLI arguments...` - Any additional arguments are passed to the container CLI command (not the command inside the container).
|
|
||||||
|
|
||||||
> **Note:** Copy the `azure-cli` script to a directory in your `PATH` (e.g. `/usr/local/bin`) to use it as a regular command.
|
```text
|
||||||
|
azure-cli [(-a|--account) <account_name>]
|
||||||
|
[--user <user_name>]
|
||||||
|
[--name <container_name>]
|
||||||
|
[--volume <local_path>:<container_path>]
|
||||||
|
[--]
|
||||||
|
[ other arguments passed as a command to execute inside the container ]
|
||||||
|
```
|
||||||
|
|
||||||
|
- `--account <account_name>`: Specifies the account name to be set as an environment variable inside the container. The volume name will be derived from this account name.
|
||||||
|
- `--user <user_name>`: Specifies the user name to be used inside the container. Default is 'ubuntu'.
|
||||||
|
- `--name <container_name>`: Specifies the name of the container. Default is sanitized account name.
|
||||||
|
- `--volume <local_path>:<container_path>`: Binds additional local path to a path inside the container.
|
||||||
|
- `--`: Indicates the end of options for the script. Any arguments following this will be passed as a command to execute inside the container.
|
||||||
|
|||||||
143
azure-cli
143
azure-cli
@@ -2,10 +2,23 @@
|
|||||||
|
|
||||||
# Set default values
|
# Set default values
|
||||||
ACCOUNT_NAME="$(id -un)"
|
ACCOUNT_NAME="$(id -un)"
|
||||||
USER_NAME="ubuntu"
|
USER_NAME="${USER_NAME:-ubuntu}"
|
||||||
|
EXTRA_ARGS=()
|
||||||
|
LIST=""
|
||||||
|
VOLUME_NAME=""
|
||||||
|
HOME_DIR="/home/${USER_NAME}"
|
||||||
|
|
||||||
|
# Parse command line arguments
|
||||||
while [ $# -gt 0 ]; do
|
while [ $# -gt 0 ]; do
|
||||||
case $1 in
|
case $1 in
|
||||||
|
--list|-l)
|
||||||
|
LIST=true
|
||||||
|
break
|
||||||
|
;;
|
||||||
|
--name|-n)
|
||||||
|
VOLUME_NAME="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
--account|-a)
|
--account|-a)
|
||||||
ACCOUNT_NAME="$2"
|
ACCOUNT_NAME="$2"
|
||||||
shift 2
|
shift 2
|
||||||
@@ -14,6 +27,37 @@ while [ $# -gt 0 ]; do
|
|||||||
USER_NAME="$2"
|
USER_NAME="$2"
|
||||||
shift 2
|
shift 2
|
||||||
;;
|
;;
|
||||||
|
--volume|-v)
|
||||||
|
read VOL_SRC VOL_DST < <(echo "$2" | tr ':' ' ')
|
||||||
|
EXTRA_ARGS+=("--mount" "type=bind,source=${VOL_SRC},target=${VOL_DST}")
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--root)
|
||||||
|
USER_NAME="root"
|
||||||
|
HOME_DIR="/root"
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--)
|
||||||
|
# Stop parsing arguments
|
||||||
|
shift
|
||||||
|
break
|
||||||
|
;;
|
||||||
|
--help|-h)
|
||||||
|
cat <<EOF
|
||||||
|
Usage: $(basename "$0")
|
||||||
|
[--list|-l]
|
||||||
|
|
||||||
|
or
|
||||||
|
|
||||||
|
[--account|-a ACCOUNT_NAME]
|
||||||
|
[--name|-n VOLUME_NAME]
|
||||||
|
[--user|-u USER_NAME]
|
||||||
|
[--volume|-v HOST_PATH:CONTAINER_PATH]
|
||||||
|
[--]
|
||||||
|
[additional args to pass to container]
|
||||||
|
EOF
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
*)
|
*)
|
||||||
break
|
break
|
||||||
;;
|
;;
|
||||||
@@ -22,44 +66,83 @@ done
|
|||||||
|
|
||||||
IMAGE_NAME="skoszewski/azure-cli:latest"
|
IMAGE_NAME="skoszewski/azure-cli:latest"
|
||||||
|
|
||||||
# Normalize account name for use in volume name
|
# Check if a custom volume name is provided
|
||||||
ACCOUNT_NAME="${ACCOUNT_NAME/@/_at_}"
|
if [ -z "$VOLUME_NAME" ]; then
|
||||||
ACCOUNT_NAME="${ACCOUNT_NAME//[-.]/_}"
|
# Normalize account name for use in volume name
|
||||||
|
VOLUME_NAME="account_${ACCOUNT_NAME/@/_at_}"
|
||||||
|
VOLUME_NAME="${VOLUME_NAME//[-.]/_}"
|
||||||
|
fi
|
||||||
|
|
||||||
# Find container runtime
|
# Find container runtime
|
||||||
if command -v podman &> /dev/null; then
|
if command -v container &> /dev/null; then
|
||||||
CMD="podman"
|
|
||||||
HOSTNAME_ARG="--hostname $(hostname -s)"
|
|
||||||
elif command -v docker &> /dev/null; then
|
|
||||||
CMD="docker"
|
|
||||||
HOSTNAME_ARG="--hostname $(hostname -s)"
|
|
||||||
elif command -v container &> /dev/null; then
|
|
||||||
# Apple container command line tool
|
# Apple container command line tool
|
||||||
CMD="container"
|
CMD="container"
|
||||||
HOSTNAME_ARG=""
|
elif command -v docker &> /dev/null; then
|
||||||
|
CMD="docker"
|
||||||
|
EXTRA_ARGS+=("--hostname $(hostname -s)")
|
||||||
else
|
else
|
||||||
echo "Error: No usable container runtime was found." >&2
|
echo "Error: No usable container runtime was found." >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Check, if the account volume exists, if not create it
|
if [ -n "$LIST" ]; then
|
||||||
# This ensures persistent storage for the Azure CLI configuration
|
echo "Available accounts:"
|
||||||
if ! $CMD volume inspect "account_$ACCOUNT_NAME" &> /dev/null; then
|
# Unfortunately 'container' and 'docker' CLIs are not compatible
|
||||||
echo "Creating volume account_$ACCOUNT_NAME for Azure CLI configuration."
|
if [ "container" = "$CMD" ]; then
|
||||||
if ! $CMD volume create "account_$ACCOUNT_NAME" &> /dev/null; then
|
$CMD volume ls --format json | jq -r '.[] | select(.labels | has("account")) | .labels.account'
|
||||||
echo "Error: Failed to create volume account_$ACCOUNT_NAME." >&2
|
else
|
||||||
exit 1
|
# Docker has settled to provide labels as one string, that's inconvinient
|
||||||
|
$CMD volume ls --format=json | jq -sr '.[] | .Account = (.Labels | match("account=([a-zA-Z0-9._%@+-]+)"; "x") | .captures[0].string) | .Account'
|
||||||
fi
|
fi
|
||||||
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Run the container
|
if [ "container" = "$CMD" ]; then
|
||||||
$CMD run \
|
_FOUND="$($CMD volume ls --format json | jq -r --arg name "$VOLUME_NAME" --arg account "$ACCOUNT_NAME" '.[] | select(.name == $name or .labels.account == $account) | "FOUND"')"
|
||||||
--rm \
|
else
|
||||||
-it \
|
_FOUND="$($CMD volume ls --format=json | jq -sr --arg name "$VOLUME_NAME" --arg account "$ACCOUNT_NAME" '.[] | .Account = (.Labels | match("account=([a-zA-Z0-9._%@+-]+)"; "x") // {} | .captures[0].string) | select(.Name == $name or .Account == $account) | "FOUND"')"
|
||||||
--mount type=volume,source="account_$ACCOUNT_NAME",target=/home/${USER_NAME} \
|
fi
|
||||||
--mount type=bind,source=$(pwd),target=/workdir \
|
|
||||||
--name "azure-cli" \
|
# Check, if the account volume exists, if not create it
|
||||||
$HOSTNAME_ARG \
|
# This ensures persistent storage for the Azure CLI configuration
|
||||||
--workdir /workdir \
|
if [ ! "FOUND" = "$_FOUND" ]; then
|
||||||
"$@" \
|
echo "A volume for account '$ACCOUNT_NAME' does not exist."
|
||||||
$IMAGE_NAME --user "$USER_NAME"
|
read -p "Would you like to create one? (y/n) " -r RESPONSE
|
||||||
|
if [[ ! "$RESPONSE" =~ ^[Yy]$ ]]; then
|
||||||
|
echo "Aborting."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Creating volume $VOLUME_NAME for Azure CLI configuration."
|
||||||
|
if ! $CMD volume create "$VOLUME_NAME" --label "account=$ACCOUNT_NAME" &> /dev/null; then
|
||||||
|
echo "Error: Failed to create volume $VOLUME_NAME." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Volume created, initialize it
|
||||||
|
echo "Initializing volume $VOLUME_NAME."
|
||||||
|
if ! $CMD run --rm -it --mount "type=volume,source=$VOLUME_NAME,target=$HOME_DIR" $IMAGE_NAME; then
|
||||||
|
echo "Error: Failed to initialize volume $VOLUME_NAME." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "Volume $VOLUME_NAME initialized successfully."
|
||||||
|
fi
|
||||||
|
|
||||||
|
EXTRA_ARGS+=(
|
||||||
|
"--mount" "type=volume,source=$VOLUME_NAME,target=/home/${USER_NAME}"
|
||||||
|
"--mount" "type=bind,source=$(pwd),target=/workdir"
|
||||||
|
"--env" "ACCOUNT_NAME=$ACCOUNT_NAME"
|
||||||
|
"--env" "USER_NAME=$USER_NAME"
|
||||||
|
"--env" "HOME_DIR=$HOME_DIR"
|
||||||
|
"--name" "azure-cli-$VOLUME_NAME"
|
||||||
|
"--workdir" "/workdir"
|
||||||
|
)
|
||||||
|
|
||||||
|
if [ "$USER_NAME" != "root" ]; then
|
||||||
|
EXTRA_ARGS+=(
|
||||||
|
"--user" "$USER_NAME"
|
||||||
|
)
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Run the container as the specified user
|
||||||
|
$CMD run --rm -it ${EXTRA_ARGS[@]} $IMAGE_NAME "$@"
|
||||||
|
|||||||
12
build
12
build
@@ -1,17 +1,13 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
# Find container runtime
|
# Find container runtime
|
||||||
if command -v podman &> /dev/null; then
|
if command -v docker &> /dev/null; then
|
||||||
CMD="podman"
|
echo "Using Dokcer as container runtime."
|
||||||
elif command -v docker &> /dev/null; then
|
docker buildx build -t skoszewski/azure-cli:latest .
|
||||||
CMD="docker"
|
|
||||||
elif command -v container &> /dev/null; then
|
elif command -v container &> /dev/null; then
|
||||||
# Apple container command line tool
|
# Apple container command line tool
|
||||||
CMD="container"
|
container build -t skoszewski/azure-cli:latest .
|
||||||
else
|
else
|
||||||
echo "Error: No usable container runtime was found." >&2
|
echo "Error: No usable container runtime was found." >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "Using container runtime: $CMD"
|
|
||||||
$CMD build -t skoszewski/azure-cli:latest .
|
|
||||||
|
|||||||
7
enable-git-bash-prompt
Normal file
7
enable-git-bash-prompt
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
# Include Git prompt helpers if available
|
||||||
|
if [ -f /usr/lib/git-core/git-sh-prompt ]; then
|
||||||
|
source /usr/lib/git-core/git-sh-prompt
|
||||||
|
|
||||||
|
# Then PS1 can include __git_ps1 which is optimized and shows branch + state:
|
||||||
|
PS1='\[\e[32m\]AzureCLI (\[\e[35m\]${ACCOUNT_NAME}\[\e[0m\])\n\[\e[0m\]\[\e[34m\]\w\[\e[0m\]\[\e[33m\]$(__git_ps1 " (%s)")\[\e[0m\]\$ '
|
||||||
|
fi
|
||||||
@@ -2,42 +2,41 @@
|
|||||||
|
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
# Let's set some defaults
|
# Setup default values
|
||||||
SU_USER="ubuntu"
|
USER_NAME="${USER_NAME:-ubuntu}"
|
||||||
|
HOME_DIR="${HOMED_DIR:-/home/${USER_NAME}}"
|
||||||
|
|
||||||
# Parse command line arguments
|
# Check, if we are running as root
|
||||||
while [ $# -gt 0 ]; do
|
if [ "$(id -u)" -eq 0 ]; then
|
||||||
case "$1" in
|
# Check, if the home directory exists for the specified user
|
||||||
-u|--user)
|
if [ ! -d "$HOME_DIR" ]; then
|
||||||
SU_USER="$2"
|
echo "Error: Home directory for user '${USER_NAME}' does not exist." >&2
|
||||||
shift 2
|
exit 1
|
||||||
;;
|
fi
|
||||||
*)
|
|
||||||
break
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
done
|
|
||||||
|
|
||||||
# Check if the user exists
|
echo "Preparing home directory for user '${USER_NAME}' at '${HOME_DIR}'."
|
||||||
if ! id -u "$SU_USER" >/dev/null 2>&1; then
|
|
||||||
useradd -m -s /usr/bin/bash "$SU_USER"
|
# Check, ownership of the home directory
|
||||||
|
if [ "$(stat -c '%u' "$HOME_DIR")" -eq 0 ]; then
|
||||||
|
# The home directory is a fresh volume owned by root, change ownership
|
||||||
|
echo "Changing ownership of home directory to user '${USER_NAME}'."
|
||||||
|
chown "${USER_NAME}:${USER_NAME}" "$HOME_DIR"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$USER_NAME " != "root" ]; then
|
||||||
|
# Re-initialize the contents of the home directory
|
||||||
|
su - "${USER_NAME}" -c "cp -a /etc/skel/. $HOME_DIR"
|
||||||
|
|
||||||
|
# We are done as root, quit. The container will be re-run as the specified user.
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Check if the home directory exists
|
# Verify that we are running as the user owning the home directory
|
||||||
if [ ! -d "/home/$SU_USER" ]; then
|
if [ "$(id -un)" != "${USER_NAME}" ]; then
|
||||||
echo "A home directory for $SU_USER does not exist. Exiting."
|
echo "Error: The script must be run as user '${USER_NAME}'." >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Check user ownership of the home directory
|
# Run shell
|
||||||
if [ "$(stat -c '%u' /home/$SU_USER)" -ne "$(id -u $SU_USER)" ]; then
|
exec /usr/bin/bash "$@"
|
||||||
# Change ownership of the home directory
|
|
||||||
chown $SU_USER:$SU_USER /home/$SU_USER
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Switch to the specified user
|
|
||||||
if [ $# -gt 0 ]; then
|
|
||||||
exec su $SU_USER -c "$@"
|
|
||||||
else
|
|
||||||
exec su $SU_USER
|
|
||||||
fi
|
|
||||||
|
|||||||
17
link-to-bin
Executable file
17
link-to-bin
Executable file
@@ -0,0 +1,17 @@
|
|||||||
|
#!/usr/bin/bash
|
||||||
|
#
|
||||||
|
# Make a symbolic link from the 'azure-cli' executable script
|
||||||
|
# to $HOME/bin.
|
||||||
|
#
|
||||||
|
|
||||||
|
if [ ! -d "$HOME/bin" ]; then
|
||||||
|
mkdir -p "$HOME/bin"
|
||||||
|
cat <<'EOF' >> $HOME/.bashrc
|
||||||
|
|
||||||
|
# Add $HOME/bin to the PATH variable
|
||||||
|
export PATH=$HOME/bin:$PATH
|
||||||
|
EOF
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Make a link
|
||||||
|
ln -s "$(cd $(dirname ${BASH_SOURCE[0]}); pwd)/azure-cli" "$HOME/bin/azure-cli"
|
||||||
Reference in New Issue
Block a user