mirror of
https://github.com/lxc/incus
synced 2026-08-02 21:38:38 +00:00
Compare commits
No commits in common. "main" and "v7.0.0" have entirely different histories.
@ -97,7 +97,7 @@ RUN go install -v github.com/google/go-licenses@latest && \
|
||||
go install -v golang.org/x/tools/cmd/goimports@latest && \
|
||||
go install -v golang.org/x/vuln/cmd/govulncheck@latest && \
|
||||
go install -v mvdan.cc/gofumpt@latest && \
|
||||
curl -sSfL https://golangci-lint.run/install.sh | sh -s -- -b $(go env GOPATH)/bin
|
||||
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin
|
||||
|
||||
# Make dependencies
|
||||
COPY Makefile /home/vscode
|
||||
|
||||
30
.github/packaging/macos/build.sh
vendored
30
.github/packaging/macos/build.sh
vendored
@ -1,30 +0,0 @@
|
||||
#!/bin/bash
|
||||
# Build a universal Incus client PKG installer.
|
||||
set -eu
|
||||
|
||||
VERSION="${VERSION:?VERSION must be set}"
|
||||
here="$(dirname "$0")"
|
||||
out="installers"
|
||||
root="pkgroot"
|
||||
res="pkgresources"
|
||||
|
||||
mkdir -p "${out}" "${root}/usr/local/bin" "${res}"
|
||||
cp COPYING "${res}/LICENSE.txt"
|
||||
|
||||
CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -o incus.amd64 ./cmd/incus
|
||||
CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 go build -o incus.arm64 ./cmd/incus
|
||||
lipo -create -output "${root}/usr/local/bin/incus" incus.amd64 incus.arm64
|
||||
chmod 0755 "${root}/usr/local/bin/incus"
|
||||
|
||||
pkgbuild --root "${root}" \
|
||||
--identifier org.linuxcontainers.incus \
|
||||
--version "${VERSION}" \
|
||||
--install-location / \
|
||||
incus-component.pkg
|
||||
|
||||
productbuild --distribution "${here}/distribution.xml" \
|
||||
--package-path . \
|
||||
--resources "${res}" \
|
||||
"${out}/incus.macos.pkg"
|
||||
|
||||
rm -rf incus.amd64 incus.arm64 incus-component.pkg "${root}" "${res}"
|
||||
16
.github/packaging/macos/distribution.xml
vendored
16
.github/packaging/macos/distribution.xml
vendored
@ -1,16 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<installer-gui-script minSpecVersion="2">
|
||||
<title>Incus</title>
|
||||
<license file="LICENSE.txt" />
|
||||
<options customize="never" require-scripts="false" hostArchitectures="x86_64,arm64" />
|
||||
<choices-outline>
|
||||
<line choice="default">
|
||||
<line choice="org.linuxcontainers.incus" />
|
||||
</line>
|
||||
</choices-outline>
|
||||
<choice id="default" />
|
||||
<choice id="org.linuxcontainers.incus" visible="false">
|
||||
<pkg-ref id="org.linuxcontainers.incus" />
|
||||
</choice>
|
||||
<pkg-ref id="org.linuxcontainers.incus" version="0" onConclusion="none">incus-component.pkg</pkg-ref>
|
||||
</installer-gui-script>
|
||||
30
.github/packaging/windows/build.sh
vendored
30
.github/packaging/windows/build.sh
vendored
@ -1,30 +0,0 @@
|
||||
#!/bin/bash
|
||||
# Build per-architecture Incus client MSI installers.
|
||||
set -eu
|
||||
|
||||
VERSION="${VERSION:?VERSION must be set}"
|
||||
here="$(dirname "$0")"
|
||||
out="installers"
|
||||
|
||||
mkdir -p "${out}"
|
||||
bash "${here}/make-license-rtf.sh" COPYING "${out}/license.rtf"
|
||||
|
||||
build_one() {
|
||||
goarch="$1"
|
||||
wixarch="$2"
|
||||
name="$3"
|
||||
|
||||
CGO_ENABLED=0 GOOS=windows GOARCH="${goarch}" go build -o "${out}/incus.exe" ./cmd/incus
|
||||
wix build -arch "${wixarch}" \
|
||||
-ext WixToolset.UI.wixext \
|
||||
-d Version="${VERSION}" \
|
||||
-d BinPath="${out}/incus.exe" \
|
||||
-d LicenseRtf="${out}/license.rtf" \
|
||||
-o "${out}/incus.windows.${name}.msi" \
|
||||
"${here}/incus.wxs"
|
||||
}
|
||||
|
||||
build_one amd64 x64 x86_64
|
||||
build_one arm64 arm64 aarch64
|
||||
|
||||
rm -f "${out}/incus.exe" "${out}/license.rtf"
|
||||
33
.github/packaging/windows/incus.wxs
vendored
33
.github/packaging/windows/incus.wxs
vendored
@ -1,33 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"
|
||||
xmlns:ui="http://wixtoolset.org/schemas/v4/wxs/ui">
|
||||
<Package Name="Incus"
|
||||
Manufacturer="Linux Containers"
|
||||
Version="$(var.Version)"
|
||||
UpgradeCode="7E2B5C1D-4A3F-4B8E-9C6D-1F0A2B3C4D5E"
|
||||
Scope="perMachine">
|
||||
<MajorUpgrade DowngradeErrorMessage="A newer version of Incus is already installed." />
|
||||
<MediaTemplate EmbedCab="yes" />
|
||||
|
||||
<StandardDirectory Id="ProgramFiles6432Folder">
|
||||
<Directory Id="INSTALLFOLDER" Name="Incus">
|
||||
<Directory Id="BINFOLDER" Name="bin" />
|
||||
</Directory>
|
||||
</StandardDirectory>
|
||||
|
||||
<ComponentGroup Id="ProductComponents" Directory="BINFOLDER">
|
||||
<Component Id="IncusExe" Guid="*">
|
||||
<File Id="IncusExe" Name="incus.exe" Source="$(var.BinPath)" KeyPath="yes" />
|
||||
<Environment Id="UpdatePath" Name="PATH" Value="[BINFOLDER]"
|
||||
Permanent="no" Part="last" Action="set" System="yes" />
|
||||
</Component>
|
||||
</ComponentGroup>
|
||||
|
||||
<Feature Id="Main">
|
||||
<ComponentGroupRef Id="ProductComponents" />
|
||||
</Feature>
|
||||
|
||||
<ui:WixUI Id="WixUI_Minimal" />
|
||||
<WixVariable Id="WixUILicenseRtf" Value="$(var.LicenseRtf)" />
|
||||
</Package>
|
||||
</Wix>
|
||||
12
.github/packaging/windows/make-license-rtf.sh
vendored
12
.github/packaging/windows/make-license-rtf.sh
vendored
@ -1,12 +0,0 @@
|
||||
#!/bin/bash
|
||||
# Wrap a plain-text license into a minimal RTF for the WiX license dialog.
|
||||
set -eu
|
||||
|
||||
src="$1"
|
||||
dst="$2"
|
||||
|
||||
{
|
||||
printf '{\\rtf1\\ansi\\deff0{\\fonttbl{\\f0 Courier New;}}\\fs16\n'
|
||||
sed -e 's/\\/\\\\/g' -e 's/{/\\{/g' -e 's/}/\\}/g' -e 's/$/\\par/' "$src"
|
||||
printf '}\n'
|
||||
} > "$dst"
|
||||
93
.github/workflows/build.yml
vendored
93
.github/workflows/build.yml
vendored
@ -1,93 +0,0 @@
|
||||
name: Build
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- stable-*
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build (${{ matrix.architecture }})
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
architecture:
|
||||
- amd64
|
||||
- arm64
|
||||
runs-on:
|
||||
- self-hosted
|
||||
- lxc-incus-build
|
||||
- arch-${{ matrix.architecture }}
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v7
|
||||
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v7
|
||||
with:
|
||||
go-version: stable
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install --no-install-recommends -y \
|
||||
autoconf \
|
||||
automake \
|
||||
curl \
|
||||
git \
|
||||
libacl1-dev \
|
||||
libcap-dev \
|
||||
libdbus-1-dev \
|
||||
liblz4-dev \
|
||||
libseccomp-dev \
|
||||
libselinux-dev \
|
||||
libsqlite3-dev \
|
||||
libtool \
|
||||
libudev-dev \
|
||||
libuv1-dev \
|
||||
lxc-dev \
|
||||
make \
|
||||
pkg-config \
|
||||
zip
|
||||
|
||||
- name: Download go dependencies
|
||||
run: |
|
||||
go mod download
|
||||
|
||||
- name: Build cowsql and raft
|
||||
run: |
|
||||
set -x
|
||||
make deps
|
||||
|
||||
raft_path="$(go env GOPATH)/deps/raft"
|
||||
cowsql_path="$(go env GOPATH)/deps/cowsql"
|
||||
{
|
||||
echo "CGO_CFLAGS=-I${raft_path}/include/ -I${cowsql_path}/include/"
|
||||
echo "CGO_LDFLAGS=-L${raft_path}/.libs -L${cowsql_path}/.libs/"
|
||||
echo "LD_LIBRARY_PATH=${raft_path}/.libs/:${cowsql_path}/.libs/"
|
||||
echo "CGO_LDFLAGS_ALLOW=(-Wl,-wrap,pthread_create)|(-Wl,-z,now)"
|
||||
} >> "$GITHUB_ENV"
|
||||
|
||||
- name: Build incusd and incus
|
||||
run: |
|
||||
make
|
||||
|
||||
- name: Collect binaries
|
||||
run: |
|
||||
mkdir -p build
|
||||
cp "$(go env GOPATH)/bin/incusd" build/
|
||||
cp "$(go env GOPATH)/bin/incus" build/
|
||||
|
||||
- name: Upload build artifacts
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: build.${{ matrix.architecture }}
|
||||
path: build/
|
||||
38
.github/workflows/commits.yml
vendored
38
.github/workflows/commits.yml
vendored
@ -12,38 +12,16 @@ jobs:
|
||||
name: Signed-off-by (DCO)
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Check that all commits are signed-off
|
||||
uses: KineticCafe/actions-dco@v3.2.0
|
||||
|
||||
llm-commit-policy:
|
||||
permissions:
|
||||
contents: none
|
||||
name: LLM commit policy
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v7
|
||||
- name: Get PR Commits
|
||||
id: 'get-pr-commits'
|
||||
uses: tim-actions/get-pr-commits@master
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
fetch-depth: 0
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Check LLM commit policy
|
||||
run: |
|
||||
set -eu
|
||||
|
||||
# Inspired by https://github.com/yaml/go-yaml/pull/340
|
||||
agents="(aider|anthropic|claude|codex|copilot|devin|gemini|grok|openai)"
|
||||
|
||||
commits=$(git rev-list ${{ github.event.pull_request.base.sha }}..${{ github.event.pull_request.head.sha }})
|
||||
for commit in $commits; do
|
||||
if git log -n 1 "$commit" | tr '[:upper:]' '[:lower:]' | grep -qE "(author|assisted-by|co-authored-by|signed-off-by):.*$agents"; then
|
||||
echo "Error: The following commit appears to violate this repo's LLM/AI contribution policy:"
|
||||
echo ""
|
||||
git log -n 1 "$commit"
|
||||
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
- name: Check that all commits are signed-off
|
||||
uses: tim-actions/dco@master
|
||||
with:
|
||||
commits: ${{ steps.get-pr-commits.outputs.commits }}
|
||||
|
||||
target-branch:
|
||||
permissions:
|
||||
|
||||
116
.github/workflows/release.yml
vendored
116
.github/workflows/release.yml
vendored
@ -11,117 +11,34 @@ permissions:
|
||||
attestations: write
|
||||
|
||||
jobs:
|
||||
version:
|
||||
name: Format version
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
display: ${{ steps.version.outputs.display }}
|
||||
full: ${{ steps.version.outputs.full }}
|
||||
steps:
|
||||
- name: Format version
|
||||
id: version
|
||||
run: |
|
||||
raw="${GITHUB_REF_NAME#v}"
|
||||
IFS='.' read -r major minor patch <<< "$raw"
|
||||
echo "full=${major}.${minor}.${patch}" >> $GITHUB_OUTPUT
|
||||
if [ "${patch}" = "0" ]; then
|
||||
echo "display=${major}.${minor}" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "display=${major}.${minor}.${patch}" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
build-msi:
|
||||
name: Build Windows installer
|
||||
runs-on: windows-latest
|
||||
needs: version
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v7
|
||||
with:
|
||||
go-version: stable
|
||||
|
||||
# Pinned to v5, the last release before WiX v6/v7 moved to the Open
|
||||
# Source Maintenance Fee license requiring EULA acceptance.
|
||||
- name: Install WiX
|
||||
shell: pwsh
|
||||
run: |
|
||||
dotnet tool install --global wix --version 5.0.2
|
||||
echo "$env:USERPROFILE\.dotnet\tools" | Out-File -FilePath $env:GITHUB_PATH -Append
|
||||
|
||||
- name: Add WiX extensions
|
||||
shell: pwsh
|
||||
run: wix extension add -g WixToolset.UI.wixext/5.0.2
|
||||
|
||||
- name: Build installers
|
||||
shell: bash
|
||||
env:
|
||||
VERSION: ${{ needs.version.outputs.full }}
|
||||
run: bash .github/packaging/windows/build.sh
|
||||
|
||||
- name: Upload installers
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: installers-windows
|
||||
path: installers/*.msi
|
||||
if-no-files-found: error
|
||||
|
||||
build-pkg:
|
||||
name: Build macOS installer
|
||||
runs-on: macos-latest
|
||||
needs: version
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v7
|
||||
with:
|
||||
go-version: stable
|
||||
|
||||
- name: Build installer
|
||||
env:
|
||||
VERSION: ${{ needs.version.outputs.full }}
|
||||
run: bash .github/packaging/macos/build.sh
|
||||
|
||||
- name: Upload installer
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: installers-macos
|
||||
path: installers/*.pkg
|
||||
if-no-files-found: error
|
||||
|
||||
goreleaser:
|
||||
name: Release
|
||||
runs-on: ubuntu-latest
|
||||
needs: [version, build-msi, build-pkg]
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v7
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v7
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: stable
|
||||
|
||||
- name: Install syft
|
||||
uses: anchore/sbom-action/download-syft@v0
|
||||
|
||||
- name: Download installers
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
pattern: installers-*
|
||||
path: installers
|
||||
merge-multiple: true
|
||||
- name: Format version
|
||||
id: version
|
||||
run: |
|
||||
raw="${GITHUB_REF_NAME#v}"
|
||||
IFS='.' read -r major minor patch <<< "$raw"
|
||||
if [ "${patch}" = "0" ]; then
|
||||
echo "value=${major}.${minor}" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "value=${major}.${minor}.${patch}" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Run GoReleaser
|
||||
uses: goreleaser/goreleaser-action@v7
|
||||
@ -131,16 +48,9 @@ jobs:
|
||||
args: release --clean
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
INCUS_VERSION: ${{ needs.version.outputs.display }}
|
||||
INCUS_VERSION: ${{ steps.version.outputs.value }}
|
||||
|
||||
- name: Handle attestation
|
||||
uses: actions/attest@v4
|
||||
with:
|
||||
subject-checksums: ./dist/checksums.txt
|
||||
|
||||
- name: Handle installer attestation
|
||||
uses: actions/attest@v4
|
||||
with:
|
||||
subject-path: |
|
||||
installers/*.msi
|
||||
installers/*.pkg
|
||||
|
||||
52
.github/workflows/tests.yml
vendored
52
.github/workflows/tests.yml
vendored
@ -26,13 +26,13 @@ jobs:
|
||||
- tip
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v7
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
# Differential ShellCheck requires full git history
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Dependency Review
|
||||
uses: actions/dependency-review-action@v5
|
||||
uses: actions/dependency-review-action@v4
|
||||
if: github.event_name == 'pull_request'
|
||||
with:
|
||||
allow-ghsas: GHSA-4p9m-8gc4-rw2h
|
||||
@ -55,13 +55,13 @@ jobs:
|
||||
if: github.event_name == 'pull_request' && matrix.go == 'stable'
|
||||
|
||||
- name: Install Go (${{ matrix.go }})
|
||||
uses: actions/setup-go@v7
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: ${{ matrix.go }}
|
||||
if: matrix.go != 'tip'
|
||||
|
||||
- name: Install Go (stable)
|
||||
uses: actions/setup-go@v7
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: stable
|
||||
if: matrix.go == 'tip'
|
||||
@ -241,23 +241,17 @@ jobs:
|
||||
sudo ip link delete docker0
|
||||
sudo nft flush ruleset
|
||||
|
||||
- name: Remove pre-installed Java
|
||||
run: |
|
||||
set -eux
|
||||
sudo apt-get update
|
||||
sudo apt-get remove --yes --purge temurin.*
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v7
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Install Go (${{ matrix.go }})
|
||||
uses: actions/setup-go@v7
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: ${{ matrix.go }}
|
||||
if: matrix.go != 'tip'
|
||||
|
||||
- name: Install Go (stable)
|
||||
uses: actions/setup-go@v7
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: stable
|
||||
if: matrix.go == 'tip'
|
||||
@ -271,8 +265,6 @@ jobs:
|
||||
if: matrix.go == 'tip'
|
||||
|
||||
- name: Install dependencies
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
set -x
|
||||
|
||||
@ -351,21 +343,13 @@ jobs:
|
||||
# Reclaim some space
|
||||
sudo apt-get clean
|
||||
|
||||
github_api_curl() {
|
||||
curl -sSfL \
|
||||
-H "Accept: application/vnd.github+json" \
|
||||
-H "Authorization: Bearer ${GITHUB_TOKEN}" \
|
||||
-H "X-GitHub-Api-Version: 2026-03-10" \
|
||||
"$@"
|
||||
}
|
||||
|
||||
# Download latest release of openfga server.
|
||||
mkdir -p "$(go env GOPATH)/bin/"
|
||||
github_api_curl https://api.github.com/repos/openfga/openfga/releases/latest | jq -r ".assets | .[] | .browser_download_url | select(. | test(\"_linux_$(dpkg --print-architecture).tar.gz$\"))" | xargs -I {} curl -sSfL {} -o openfga.tar.gz
|
||||
curl -sSfL https://api.github.com/repos/openfga/openfga/releases/latest | jq -r ".assets | .[] | .browser_download_url | select(. | test(\"_linux_$(dpkg --print-architecture).tar.gz$\"))" | xargs -I {} curl -sSfL {} -o openfga.tar.gz
|
||||
tar -xzf openfga.tar.gz -C "$(go env GOPATH)/bin/"
|
||||
|
||||
# Download latest release of openfga cli.
|
||||
github_api_curl https://api.github.com/repos/openfga/cli/releases/latest | jq -r ".assets | .[] | .browser_download_url | select(. | test(\"_linux_$(dpkg --print-architecture).tar.gz$\"))" | xargs -I {} curl -sSfL {} -o fga.tar.gz
|
||||
curl -sSfL https://api.github.com/repos/openfga/cli/releases/latest | jq -r ".assets | .[] | .browser_download_url | select(. | test(\"_linux_$(dpkg --print-architecture).tar.gz$\"))" | xargs -I {} curl -sSfL {} -o fga.tar.gz
|
||||
tar -xzf fga.tar.gz -C "$(go env GOPATH)/bin/"
|
||||
|
||||
- name: Download go dependencies
|
||||
@ -514,7 +498,7 @@ jobs:
|
||||
chmod +x ~
|
||||
echo "root:1000000:1000000000" | sudo tee /etc/subuid /etc/subgid
|
||||
cd test
|
||||
sudo --preserve-env=PATH,GOPATH,GITHUB_ACTIONS,INCUS_VERBOSE,INCUS_BACKEND,INCUS_CEPH_CLUSTER,INCUS_CEPH_CEPHFS,INCUS_CEPH_CEPHOBJECT_RADOSGW,INCUS_LINSTOR_LOCAL_SATELLITE,INCUS_LINSTOR_CLUSTER,INCUS_OFFLINE,INCUS_SKIP_TESTS,INCUS_REQUIRED_TESTS, INCUS_BACKEND=${{ matrix.backend }} env LD_LIBRARY_PATH=${LD_LIBRARY_PATH} JAVA_HOME= ./main.sh ${{ matrix.suite }}
|
||||
sudo --preserve-env=PATH,GOPATH,GITHUB_ACTIONS,INCUS_VERBOSE,INCUS_BACKEND,INCUS_CEPH_CLUSTER,INCUS_CEPH_CEPHFS,INCUS_CEPH_CEPHOBJECT_RADOSGW,INCUS_LINSTOR_LOCAL_SATELLITE,INCUS_LINSTOR_CLUSTER,INCUS_OFFLINE,INCUS_SKIP_TESTS,INCUS_REQUIRED_TESTS, INCUS_BACKEND=${{ matrix.backend }} env LD_LIBRARY_PATH=${LD_LIBRARY_PATH} ./main.sh ${{ matrix.suite }}
|
||||
|
||||
client:
|
||||
name: Client
|
||||
@ -532,10 +516,10 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v7
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v7
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: ${{ matrix.go }}
|
||||
|
||||
@ -579,6 +563,14 @@ jobs:
|
||||
GOARCH=amd64 go build -o bin/incus-migrate.x86_64 ./cmd/incus-migrate
|
||||
GOARCH=arm64 go build -o bin/incus-migrate.aarch64 ./cmd/incus-migrate
|
||||
|
||||
- name: Build static lxd-to-incus
|
||||
if: runner.os == 'Linux'
|
||||
env:
|
||||
CGO_ENABLED: 0
|
||||
run: |
|
||||
GOARCH=amd64 go build -o bin/lxd-to-incus.x86_64 ./cmd/lxd-to-incus
|
||||
GOARCH=arm64 go build -o bin/lxd-to-incus.aarch64 ./cmd/lxd-to-incus
|
||||
|
||||
- name: Unit tests (client)
|
||||
env:
|
||||
CGO_ENABLED: 0
|
||||
@ -607,10 +599,10 @@ jobs:
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v7
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v7
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: stable
|
||||
|
||||
|
||||
2
.github/workflows/triage.yml
vendored
2
.github/workflows/triage.yml
vendored
@ -13,7 +13,7 @@ jobs:
|
||||
name: PR labels
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/labeler@v7
|
||||
- uses: actions/labeler@v6
|
||||
with:
|
||||
repo-token: "${{ secrets.GITHUB_TOKEN }}"
|
||||
sync-labels: true
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@ -2,7 +2,6 @@
|
||||
po/*.mo
|
||||
po/*.po~
|
||||
incus-*.tar.xz
|
||||
installers
|
||||
.vagrant
|
||||
*~
|
||||
tags
|
||||
@ -11,6 +10,7 @@ tags
|
||||
cmd/fuidshift/fuidshift
|
||||
cmd/incus/incus
|
||||
cmd/lxc-to-incus/lxc-to-incus
|
||||
cmd/lxd-to-incus/lxd-to-incus
|
||||
cmd/incus-agent/incus-agent
|
||||
cmd/incus-benchmark/incus-benchmark
|
||||
cmd/incus-migrate/incus-migrate
|
||||
|
||||
@ -79,10 +79,6 @@ linters:
|
||||
text: "avoid meaningless package names"
|
||||
- path: internal/server/util/
|
||||
text: "avoid meaningless package names"
|
||||
- path: shared/uefi/guid.go
|
||||
linters:
|
||||
- revive
|
||||
text: '^(var-naming|exported):'
|
||||
paths:
|
||||
- third_party$
|
||||
- builtin$
|
||||
|
||||
@ -66,6 +66,19 @@ archives:
|
||||
{{- else if eq .Arch "arm64" }}aarch64
|
||||
{{- else }}{{ .Arch }}{{ end }}
|
||||
|
||||
- id: lxd-to-incus
|
||||
ids:
|
||||
- lxd-to-incus
|
||||
formats:
|
||||
- binary
|
||||
name_template: >-
|
||||
bin.
|
||||
{{- if eq .Os "darwin" }}macos
|
||||
{{- else }}{{ .Os }}{{ end }}.lxd-to-incus.
|
||||
{{- if eq .Arch "amd64" }}x86_64
|
||||
{{- else if eq .Arch "arm64" }}aarch64
|
||||
{{- else }}{{ .Arch }}{{ end }}
|
||||
|
||||
before:
|
||||
hooks:
|
||||
- go mod download
|
||||
@ -139,6 +152,16 @@ builds:
|
||||
- amd64
|
||||
- arm64
|
||||
|
||||
- id: lxd-to-incus
|
||||
main: ./cmd/lxd-to-incus
|
||||
env:
|
||||
- CGO_ENABLED=0
|
||||
goos:
|
||||
- linux
|
||||
goarch:
|
||||
- amd64
|
||||
- arm64
|
||||
|
||||
changelog:
|
||||
use: "github-native"
|
||||
|
||||
@ -162,9 +185,6 @@ release:
|
||||
owner: lxc
|
||||
name: incus
|
||||
name_template: "Incus {{ .Env.INCUS_VERSION }}"
|
||||
extra_files:
|
||||
- glob: ./installers/*.msi
|
||||
- glob: ./installers/*.pkg
|
||||
|
||||
sboms:
|
||||
- id: archive
|
||||
@ -175,6 +195,7 @@ sboms:
|
||||
- incus-benchmark
|
||||
- incus-migrate
|
||||
- incus-simplestreams
|
||||
- lxd-to-incus
|
||||
- id: source
|
||||
artifacts: source
|
||||
|
||||
|
||||
20
AGENTS.md
20
AGENTS.md
@ -1,20 +0,0 @@
|
||||
# Legal
|
||||
|
||||
- All contributions to this repository must be compatible with the Apache 2.0 license.
|
||||
- Specifically (but not limited to), contributions cannot include code licensed under the terms of the GPL, AGPL or LGPL licenses.
|
||||
- Only human beings are allowed to sign the Developer Certificate of Ownership (DCO / Signed-off-by).
|
||||
- Only human beings can ever be credited within commit messages.
|
||||
|
||||
# Formatting
|
||||
|
||||
- Code comments should be no longer than one line, unless they are required to cover complex unintuitive logic.
|
||||
- Commit messages should similarly be kept as short and to the point as possible, no need to summarize the whole issue.
|
||||
- We don't use the define and test one line `if` syntax, instead splitting defintion and testing across two lines.
|
||||
|
||||
# Testing / validation
|
||||
|
||||
- The commit structure described in `CONTRIBUTING.md` should generally be followed.
|
||||
- All branches are expected to pass `make static-analysis` and `go test -v ./...`.
|
||||
- Excessive unit tests are generally discouraged.
|
||||
- When possible, existing system tests should be extended to cover new features.
|
||||
- A full local system test run isn't required prior to contribution, all tests get run in our CI.
|
||||
@ -18,83 +18,21 @@ By default, any contribution to this project is made under the Apache
|
||||
The author of a change remains the copyright holder of their code
|
||||
(no copyright assignment).
|
||||
|
||||
## Policy on the use of Large Language Models (LLMs) and AI tooling
|
||||
### For issue reporting
|
||||
## No Large Language Models (LLMs) or similar AI tools
|
||||
|
||||
We do NOT allow direct filing of issues by LLMs.
|
||||
All contributions to this project are expected to be done by human
|
||||
beings or through standard predictable tooling (e.g. scripts, formatters, ...).
|
||||
|
||||
We REQUIRE a human being to go through our issue reporting form on
|
||||
Github and accurately describe their issue and provide all needed
|
||||
information.
|
||||
We expect all contributors to be able to reason about the code that they
|
||||
contribute and explain why they're taking a particular approach.
|
||||
|
||||
The more concise and to the point the issue is, the more likely it is to
|
||||
be understood, tracked down and resolved quickly.
|
||||
LLMs and similar predictive tools have the annoying tendency of
|
||||
producing large amount of low quality code with subtle issues which end
|
||||
up taking the maintainers more time to debug than it would have taken to
|
||||
write the code by hand in the first place.
|
||||
|
||||
Long winded AI written essays can easily look overwhelming and cause our
|
||||
maintainers and other contributors to just entirely skip the issue to
|
||||
focus their energy on something else.
|
||||
|
||||
We also don't benefit from AI generated root cause analysis or proposed
|
||||
fixes in those issues. If you yourself understand the code base well
|
||||
enough to go through that content and suggested fix, then turn it into a
|
||||
pull request and submit it yourself. Otherwise, please limit your report
|
||||
to describing the issue at hand and we'll take it from there.
|
||||
|
||||
### For contributions
|
||||
|
||||
We REQUIRE all contributions to Incus to be submitted by human beings who
|
||||
can assert full copyright ownership of their contribution or have been
|
||||
allowed by their employer to contribute. This is what the DCO (see below)
|
||||
requires of all contributors.
|
||||
|
||||
AI tools can sometimes be beneficial, particularly when it comes to
|
||||
finding patterns among a large data set (entire code base), performing
|
||||
tedious repetitive changes or large refactoring/re-organization.
|
||||
|
||||
While we now tolerate the use of such tools, they must abide by our
|
||||
instructions (`AGENTS.md`) and their operators cannot override those
|
||||
instructions.
|
||||
|
||||
We expect everyone contributing to Incus to fully own their
|
||||
contribution, be able to reason about it, be able to explain why things
|
||||
were done a particular way and act as the full owner of that code. AI
|
||||
tools are treated the same as traditional tooling like `sed`, `awk` or
|
||||
`coccinelle`.
|
||||
|
||||
For the purpose of this project, AI tools CANNOT be treated as author,
|
||||
co-author or be credited in any way that would suggest any ownership
|
||||
over the contribution.
|
||||
|
||||
The contributor should have done all the thinking, planning and
|
||||
understanding of the changes needed to resolve an issue or implement a
|
||||
new feature prior to using automated tooling to perform the grunt work.
|
||||
|
||||
Unguided use of those tools or the inability to prove understanding of
|
||||
the code contributed will result in a loss of trust in that contributor
|
||||
by project maintainers which can then lead to exclusion from any further
|
||||
contribution to the project.
|
||||
|
||||
It's also worth pointing out that while those tools are good at
|
||||
implementing the more boring/repetitive/grunt work. We've generally
|
||||
found that you only really understand the project and its structure by
|
||||
having done such work yourself a few times.
|
||||
|
||||
### For anyone with write access to the repository
|
||||
|
||||
Anyone with write access to this repository must ensure to NEVER run an
|
||||
AI agent or similar tool on a system which holds repository credentials
|
||||
(SSH key, GPG key, web browser cookies, ...).
|
||||
|
||||
Any use of AI tooling should be done inside of a clean VM/container that
|
||||
itself cannot directly push to or alter this repository in any way.
|
||||
|
||||
The safest approach is to SSH into that environment and then extract the
|
||||
changes using `git format-patch`, then review and apply them to your
|
||||
actual tree, tweak them as needed, sign them off and then push and open
|
||||
the pull request.
|
||||
|
||||
Any potential credential compromise or loss of control should be
|
||||
immediately reported to `security@linuxcontainers.org`.
|
||||
Any attempt at hiding the use of LLMs or similar tools in Incus contributions
|
||||
will result in a revert of the affected changes and a ban from the project.
|
||||
|
||||
## Pull requests
|
||||
|
||||
|
||||
11
Makefile
11
Makefile
@ -142,7 +142,7 @@ ifneq "$(INCUS_OFFLINE)" ""
|
||||
exit 1
|
||||
endif
|
||||
$(GO) get -t -v -u ./...
|
||||
$(GO) mod tidy --go=1.25.12
|
||||
$(GO) mod tidy --go=1.25.6
|
||||
$(GO) get toolchain@none
|
||||
|
||||
@echo "Dependencies updated"
|
||||
@ -233,7 +233,7 @@ doc-setup: client
|
||||
# doc: Build the documentation
|
||||
doc-incremental:
|
||||
@echo "Build the documentation"
|
||||
. $(SPHINXENV) ; NO_COLOR=1 sphinx-build -c doc/ -b dirhtml doc/ doc/html/ -d doc/.sphinx/.doctrees -w doc/.sphinx/warnings.txt
|
||||
. $(SPHINXENV) ; sphinx-build -c doc/ -b dirhtml doc/ doc/html/ -d doc/.sphinx/.doctrees -w doc/.sphinx/warnings.txt
|
||||
|
||||
.PHONY: doc-serve
|
||||
# doc: Serve the documentation on localhost:8001
|
||||
@ -245,11 +245,6 @@ doc-serve:
|
||||
doc-spellcheck: doc
|
||||
. $(SPHINXENV) ; python3 -m pyspelling -c doc/.sphinx/spellingcheck.yaml
|
||||
|
||||
.PHONY: doc-spellcheck-incremental
|
||||
# doc: Check spelling errors on the documentation, building the documentation only
|
||||
doc-spellcheck-incremental: doc-incremental
|
||||
. $(SPHINXENV) ; python3 -m pyspelling -c doc/.sphinx/spellingcheck.yaml
|
||||
|
||||
.PHONY: doc-linkcheck
|
||||
# doc: Check broken links on the documentation
|
||||
doc-linkcheck: doc-setup
|
||||
@ -404,4 +399,4 @@ dist: doc
|
||||
# doc: Show this help
|
||||
help:
|
||||
@echo The following targets are supported:
|
||||
@sed -En 's/^#\s*section\(([^)]*)\):\s*(.*)$$/\n\x1b[1m\1:\x1b[0m \2/p;/^\.PHONY:/{N;N;s/^\.PHONY:\s*([^[:space:]]+)\n#\s*doc(:\s*(.*)\n\1:\s*$$|!\s*(.*)\n\1:[^\n]*)/ \1!\3\4/p;s/^\.PHONY:\s*([^[:space:]]+)\s*\n#\s*doc:\s*(.*)\n\1:\s*(.+)$$/ \1!\2 (runs \3)/p}' Makefile | awk -F! '{if(NF<2)print$$1;else{s=$$1;if(length(s)%2)s=s" ";while(length(s)<28)s=s" .";print s" "$$2}}'
|
||||
@sed -En 's/^#\s*section\(([^)]*)\):\s*(.*)$$/\n\x1b[1m\1:\x1b[0m \2/p;/^\.PHONY:/{N;N;s/^\.PHONY:\s*([^[:space:]]+)\n#\s*doc(:\s*(.*)\n\1:\s*$$|!\s*(.*)\n\1:[^\n]*)/ \1!\3\4/p;s/^\.PHONY:\s*([^[:space:]]+)\s*\n#\s*doc:\s*(.*)\n\1:\s*(.+)$$/ \1!\2 (runs \3)/p}' Makefile | awk -F! '{printf "%-20s%s\n", $$1, $$2}'
|
||||
|
||||
@ -25,6 +25,8 @@ Incus is a true open source community project, free of any [CLA](https://en.wiki
|
||||
remains released under the [Apache 2.0 license](https://www.apache.org/licenses/LICENSE-2.0).
|
||||
It's maintained by the same team of developers that first created LXD.
|
||||
|
||||
LXD users wishing to migrate to Incus can easily do so through a migration tool called [`lxd-to-incus`](https://linuxcontainers.org/incus/docs/main/howto/server_migrate_lxd/).
|
||||
|
||||
## Get started
|
||||
|
||||
See [Getting started](https://linuxcontainers.org/incus/docs/main/tutorial/first_steps/) in the Incus documentation for installation instructions and first steps.
|
||||
|
||||
@ -327,7 +327,7 @@ func (r *ProtocolIncus) rawQuery(method string, url string, data any, ETag strin
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
defer logger.WarnOnError(resp.Body.Close, "Failed to close response body")
|
||||
defer func() { _ = resp.Body.Close() }()
|
||||
|
||||
return incusParseResponse(resp)
|
||||
}
|
||||
|
||||
@ -17,7 +17,6 @@ import (
|
||||
"github.com/lxc/incus/v7/shared/api"
|
||||
"github.com/lxc/incus/v7/shared/cancel"
|
||||
"github.com/lxc/incus/v7/shared/ioprogress"
|
||||
"github.com/lxc/incus/v7/shared/logger"
|
||||
localtls "github.com/lxc/incus/v7/shared/tls"
|
||||
"github.com/lxc/incus/v7/shared/units"
|
||||
"github.com/lxc/incus/v7/shared/util"
|
||||
@ -235,7 +234,7 @@ func incusDownloadImage(fingerprint string, uri string, userAgent string, do fun
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer logger.WarnOnError(response.Body.Close, "Failed to close response body")
|
||||
defer func() { _ = response.Body.Close() }()
|
||||
defer close(doneCh)
|
||||
|
||||
if response.StatusCode != http.StatusOK {
|
||||
@ -608,7 +607,7 @@ func (r *ProtocolIncus) CreateImage(image api.ImagesPost, args *ImageCreateArgs)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer logger.WarnOnError(resp.Body.Close, "Failed to close response body")
|
||||
defer func() { _ = resp.Body.Close() }()
|
||||
|
||||
// Handle errors
|
||||
response, _, err := incusParseResponse(resp)
|
||||
@ -838,14 +837,14 @@ func (r *ProtocolIncus) CopyImage(source ImageServer, image api.Image, args *Ima
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer logger.WarnOnError(func() error { return os.Remove(metaFile.Name()) }, "Failed to remove temporary file")
|
||||
defer func() { _ = os.Remove(metaFile.Name()) }()
|
||||
|
||||
rootfsFile, err := os.CreateTemp(r.tempPath, "incus_image_")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer logger.WarnOnError(func() error { return os.Remove(rootfsFile.Name()) }, "Failed to remove temporary file")
|
||||
defer func() { _ = os.Remove(rootfsFile.Name()) }()
|
||||
|
||||
// Import image
|
||||
req := ImageFileRequest{
|
||||
|
||||
@ -13,7 +13,6 @@ import (
|
||||
"net/url"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
@ -22,7 +21,6 @@ import (
|
||||
"github.com/lxc/incus/v7/shared/api"
|
||||
"github.com/lxc/incus/v7/shared/cancel"
|
||||
"github.com/lxc/incus/v7/shared/ioprogress"
|
||||
"github.com/lxc/incus/v7/shared/logger"
|
||||
"github.com/lxc/incus/v7/shared/tcp"
|
||||
localtls "github.com/lxc/incus/v7/shared/tls"
|
||||
"github.com/lxc/incus/v7/shared/units"
|
||||
@ -585,7 +583,7 @@ func (r *ProtocolIncus) CreateInstanceFromBackup(args InstanceBackupArgs) (Opera
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer logger.WarnOnError(resp.Body.Close, "Failed to close response body")
|
||||
defer func() { _ = resp.Body.Close() }()
|
||||
|
||||
// Handle errors
|
||||
response, _, err := incusParseResponse(resp)
|
||||
@ -878,12 +876,7 @@ func (r *ProtocolIncus) CopyInstance(source InstanceServer, instance api.Instanc
|
||||
Live: req.Source.Live,
|
||||
InstanceOnly: req.Source.InstanceOnly,
|
||||
AllowInconsistent: req.Source.AllowInconsistent,
|
||||
}
|
||||
|
||||
// When dependent volumes are supported, Devices are sent to the
|
||||
// migration source to allow overriding the per-device pools.
|
||||
if source.HasExtension("dependent") {
|
||||
sourceReq.Devices = req.Devices
|
||||
Devices: req.Devices,
|
||||
}
|
||||
|
||||
// Push mode migration
|
||||
@ -1459,8 +1452,7 @@ func (r *ProtocolIncus) GetInstanceFile(instanceName string, filePath string) (i
|
||||
if r.IsAgent() {
|
||||
requestURL, err = urlEncode(
|
||||
fmt.Sprintf("%s/1.0/files", r.httpBaseURL.String()),
|
||||
map[string]string{"path": filePath},
|
||||
)
|
||||
map[string]string{"path": filePath})
|
||||
} else {
|
||||
var path string
|
||||
|
||||
@ -1472,8 +1464,7 @@ func (r *ProtocolIncus) GetInstanceFile(instanceName string, filePath string) (i
|
||||
// Prepare the HTTP request
|
||||
requestURL, err = urlEncode(
|
||||
fmt.Sprintf("%s/1.0%s/%s/files", r.httpBaseURL.String(), path, url.PathEscape(instanceName)),
|
||||
map[string]string{"path": filePath},
|
||||
)
|
||||
map[string]string{"path": filePath})
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
@ -1662,7 +1653,7 @@ func (r *ProtocolIncus) DeleteInstanceFile(instanceName string, filePath string)
|
||||
}
|
||||
|
||||
// rawConn connects to the apiURL, upgrades to the requested protocol and returns it.
|
||||
func (r *ProtocolIncus) rawConn(method string, apiURL *url.URL, protocol string, data any) (net.Conn, error) {
|
||||
func (r *ProtocolIncus) rawConn(apiURL *url.URL, protocol string) (net.Conn, error) {
|
||||
// Get the HTTP transport.
|
||||
httpTransport, err := r.getUnderlyingHTTPTransport()
|
||||
if err != nil {
|
||||
@ -1670,7 +1661,7 @@ func (r *ProtocolIncus) rawConn(method string, apiURL *url.URL, protocol string,
|
||||
}
|
||||
|
||||
req := &http.Request{
|
||||
Method: method,
|
||||
Method: http.MethodGet,
|
||||
URL: apiURL,
|
||||
Proto: "HTTP/1.1",
|
||||
ProtoMajor: 1,
|
||||
@ -1682,33 +1673,15 @@ func (r *ProtocolIncus) rawConn(method string, apiURL *url.URL, protocol string,
|
||||
req.Header["Upgrade"] = []string{protocol}
|
||||
req.Header["Connection"] = []string{"Upgrade"}
|
||||
|
||||
// Add the request body.
|
||||
if data != nil {
|
||||
body, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req.Body = io.NopCloser(bytes.NewReader(body))
|
||||
req.ContentLength = int64(len(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
}
|
||||
|
||||
r.addClientHeaders(req)
|
||||
|
||||
// Add the default port if missing as the raw dialers don't apply it.
|
||||
addr := apiURL.Host
|
||||
if apiURL.Port() == "" {
|
||||
addr = net.JoinHostPort(apiURL.Hostname(), "443")
|
||||
}
|
||||
|
||||
// Establish the connection.
|
||||
var conn net.Conn
|
||||
|
||||
if httpTransport.TLSClientConfig != nil {
|
||||
conn, err = httpTransport.DialTLSContext(context.Background(), "tcp", addr)
|
||||
conn, err = httpTransport.DialTLSContext(context.Background(), "tcp", apiURL.Host)
|
||||
} else {
|
||||
conn, err = httpTransport.DialContext(context.Background(), "tcp", addr)
|
||||
conn, err = httpTransport.DialContext(context.Background(), "tcp", apiURL.Host)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
@ -1747,42 +1720,6 @@ func (r *ProtocolIncus) rawConn(method string, apiURL *url.URL, protocol string,
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
// GetInstanceNBDConn returns a connection to the instance's NBD endpoint exposing all of its disks.
|
||||
func (r *ProtocolIncus) GetInstanceNBDConn(instanceName string, args InstanceNBDArgs) (net.Conn, error) {
|
||||
if !r.HasExtension("instance_nbd") {
|
||||
return nil, errors.New(`The server is missing the required "instance_nbd" API extension`)
|
||||
}
|
||||
|
||||
apiURL := api.NewURL()
|
||||
apiURL.URL = r.httpBaseURL // Preload the URL with the client base URL.
|
||||
apiURL.Path("1.0", "instances", instanceName, "nbd")
|
||||
|
||||
values := apiURL.Query()
|
||||
if args.Reuse {
|
||||
values.Set("reuse", "1")
|
||||
}
|
||||
|
||||
apiURL.RawQuery = values.Encode()
|
||||
|
||||
r.setURLQueryAttributes(&apiURL.URL)
|
||||
|
||||
return r.rawConn(http.MethodGet, &apiURL.URL, "nbd", nil)
|
||||
}
|
||||
|
||||
// GetInstancePortForwardConn returns a connection to the given address and TCP port inside of the instance.
|
||||
func (r *ProtocolIncus) GetInstancePortForwardConn(instanceName string, forward api.InstancePortForwardPost) (net.Conn, error) {
|
||||
if !r.HasExtension("instance_port_forward") {
|
||||
return nil, errors.New(`The server is missing the required "instance_port_forward" API extension`)
|
||||
}
|
||||
|
||||
apiURL := api.NewURL()
|
||||
apiURL.URL = r.httpBaseURL // Preload the URL with the client base URL.
|
||||
apiURL.Path("1.0", "instances", instanceName, "port-forward")
|
||||
r.setURLQueryAttributes(&apiURL.URL)
|
||||
|
||||
return r.rawConn(http.MethodPost, &apiURL.URL, "tcp", forward)
|
||||
}
|
||||
|
||||
// GetInstanceFileSFTPConn returns a connection to the instance's SFTP endpoint.
|
||||
func (r *ProtocolIncus) GetInstanceFileSFTPConn(instanceName string) (net.Conn, error) {
|
||||
apiURL := api.NewURL()
|
||||
@ -1790,7 +1727,7 @@ func (r *ProtocolIncus) GetInstanceFileSFTPConn(instanceName string) (net.Conn,
|
||||
apiURL.Path("1.0", "instances", instanceName, "sftp")
|
||||
r.setURLQueryAttributes(&apiURL.URL)
|
||||
|
||||
return r.rawConn(http.MethodGet, &apiURL.URL, "sftp", nil)
|
||||
return r.rawConn(&apiURL.URL, "sftp")
|
||||
}
|
||||
|
||||
// GetInstanceFileSFTP returns an SFTP connection to the instance.
|
||||
@ -3036,7 +2973,7 @@ func (r *ProtocolIncus) GetInstanceBackupFile(instanceName string, name string,
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer logger.WarnOnError(response.Body.Close, "Failed to close response body")
|
||||
defer func() { _ = response.Body.Close() }()
|
||||
defer close(doneCh)
|
||||
|
||||
if response.StatusCode != http.StatusOK {
|
||||
@ -3113,7 +3050,7 @@ func (r *ProtocolIncus) CreateInstanceBackupStream(instanceName string, backup a
|
||||
return err
|
||||
}
|
||||
|
||||
defer logger.WarnOnError(response.Body.Close, "Failed to close response body")
|
||||
defer func() { _ = response.Body.Close() }()
|
||||
defer close(doneCh)
|
||||
|
||||
if response.StatusCode != http.StatusOK {
|
||||
@ -3283,232 +3220,3 @@ func (r *ProtocolIncus) CreateInstanceBitmap(name string, bitmap api.StorageVolu
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RepairInstance requests that Incus runs a low-level repair action on the instance.
|
||||
func (r *ProtocolIncus) RepairInstance(name string, repair api.InstanceDebugRepairPost) error {
|
||||
if !r.HasExtension("instances_debug_repair") {
|
||||
return errors.New("The server is missing the required \"instances_debug_repair\" API extension")
|
||||
}
|
||||
|
||||
path, _, err := r.instanceTypeToPath(api.InstanceTypeAny)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Send the request
|
||||
_, _, err = r.query("POST", fmt.Sprintf("%s/%s/debug/repair", path, url.PathEscape(name)), repair, "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *ProtocolIncus) getInstanceNVRAM(name string, guid string, varName string, accept string) (*http.Response, error) {
|
||||
path, _, err := r.instanceTypeToPath(api.InstanceTypeVM)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Prepare the HTTP request
|
||||
requestURL := fmt.Sprintf("%s/1.0%s/%s/nvram/%s/%s", r.httpBaseURL.String(), path, url.PathEscape(name), url.PathEscape(guid), url.PathEscape(varName))
|
||||
|
||||
requestURL, err = r.setQueryAttributes(requestURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("GET", requestURL, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req.Header.Set("Accept", accept)
|
||||
|
||||
// Send the request
|
||||
resp, err := r.DoHTTP(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Check the return value for a cleaner error
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
_, _, err := incusParseResponse(resp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// GetInstanceNVRAM gets OVMF variables from an instance.
|
||||
func (r *ProtocolIncus) GetInstanceNVRAM(name string) (map[string]map[string]*api.InstanceNVRAMVariable, error) {
|
||||
if !r.HasExtension("instance_nvram") {
|
||||
return nil, errors.New(`The server is missing the required "instance_nvram" API extension`)
|
||||
}
|
||||
|
||||
vars := map[string]map[string]*api.InstanceNVRAMVariable{}
|
||||
path, _, err := r.instanceTypeToPath(api.InstanceTypeVM)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Fetch the raw value.
|
||||
_, err = r.queryStruct("GET", fmt.Sprintf("%s/%s/nvram?recursion=2", path, url.PathEscape(name)), nil, "", &vars)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return vars, err
|
||||
}
|
||||
|
||||
// GetInstanceNVRAMGUID gets namespaced OVMF variables from an instance.
|
||||
func (r *ProtocolIncus) GetInstanceNVRAMGUID(name string, guid string) (map[string]*api.InstanceNVRAMVariable, error) {
|
||||
if !r.HasExtension("instance_nvram") {
|
||||
return nil, errors.New(`The server is missing the required "instance_nvram" API extension`)
|
||||
}
|
||||
|
||||
vars := map[string]*api.InstanceNVRAMVariable{}
|
||||
path, _, err := r.instanceTypeToPath(api.InstanceTypeVM)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Fetch the raw value.
|
||||
_, err = r.queryStruct("GET", fmt.Sprintf("%s/%s/nvram/%s?recursion=1", path, url.PathEscape(name), url.PathEscape(guid)), nil, "", &vars)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return vars, err
|
||||
}
|
||||
|
||||
// GetRawInstanceNVRAMGUIDVar gets raw OVMF variables from an instance.
|
||||
func (r *ProtocolIncus) GetRawInstanceNVRAMGUIDVar(name string, guid string, varName string) ([]byte, uint32, error) {
|
||||
if !r.HasExtension("instance_nvram") {
|
||||
return nil, 0, errors.New(`The server is missing the required "instance_nvram" API extension`)
|
||||
}
|
||||
|
||||
resp, err := r.getInstanceNVRAM(name, guid, varName, "application/octet-stream")
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
attributes, err := strconv.ParseUint(resp.Header.Get("X-Incus-attributes"), 10, 32)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
data, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
return data, uint32(attributes), err
|
||||
}
|
||||
|
||||
// GetInstanceNVRAMGUIDVar gets interpreted OVMF variables from an instance.
|
||||
func (r *ProtocolIncus) GetInstanceNVRAMGUIDVar(name string, guid string, varName string) (*api.InstanceNVRAMVariable, string, error) {
|
||||
if !r.HasExtension("instance_nvram") {
|
||||
return nil, "", errors.New(`The server is missing the required "instance_nvram" API extension`)
|
||||
}
|
||||
|
||||
var v *api.InstanceNVRAMVariable
|
||||
path, _, err := r.instanceTypeToPath(api.InstanceTypeVM)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
// Fetch the raw value.
|
||||
etag, err := r.queryStruct("GET", fmt.Sprintf("%s/%s/nvram/%s/%s?recursion=1", path, url.PathEscape(name), url.PathEscape(guid), url.PathEscape(varName)), nil, "", &v)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
return v, etag, err
|
||||
}
|
||||
|
||||
// DeleteInstanceNVRAMGUIDVar sets interpreted OVMF variables on an instance.
|
||||
func (r *ProtocolIncus) DeleteInstanceNVRAMGUIDVar(name string, guid string, varName string) error {
|
||||
if !r.HasExtension("instance_nvram") {
|
||||
return errors.New(`The server is missing the required "instance_nvram" API extension`)
|
||||
}
|
||||
|
||||
path, _, err := r.instanceTypeToPath(api.InstanceTypeVM)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Send the request
|
||||
_, _, err = r.query("DELETE", fmt.Sprintf("%s/%s/nvram/%s/%s", path, url.PathEscape(name), url.PathEscape(guid), url.PathEscape(varName)), nil, "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateRawInstanceNVRAMGUIDVar sets raw OVMF variables on an instance.
|
||||
func (r *ProtocolIncus) UpdateRawInstanceNVRAMGUIDVar(name string, guid string, varName string, data []byte, attributes uint32, timestamp int64) error {
|
||||
if !r.HasExtension("instance_nvram") {
|
||||
return errors.New(`The server is missing the required "instance_nvram" API extension`)
|
||||
}
|
||||
|
||||
path, _, err := r.instanceTypeToPath(api.InstanceTypeVM)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Prepare the HTTP request
|
||||
requestURL := fmt.Sprintf("%s/1.0%s/%s/nvram/%s/%s", r.httpBaseURL.String(), path, url.PathEscape(name), url.PathEscape(guid), url.PathEscape(varName))
|
||||
requestURL, err = r.setQueryAttributes(requestURL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("PUT", requestURL, bytes.NewReader(data))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", "application/octet-stream")
|
||||
req.Header.Set("X-Incus-attributes", strconv.FormatUint(uint64(attributes), 10))
|
||||
if timestamp != 0 {
|
||||
req.Header.Set("X-Incus-timestamp", strconv.FormatInt(timestamp, 10))
|
||||
}
|
||||
|
||||
// Send the request
|
||||
resp, err := r.DoHTTP(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Handle errors
|
||||
_, _, err = incusParseResponse(resp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateInstanceNVRAMGUIDVar sets interpreted OVMF variables on an instance.
|
||||
func (r *ProtocolIncus) UpdateInstanceNVRAMGUIDVar(name string, guid string, varName string, data api.InstanceNVRAMVariablePut, ETag string) error {
|
||||
if !r.HasExtension("instance_nvram") {
|
||||
return errors.New(`The server is missing the required "instance_nvram" API extension`)
|
||||
}
|
||||
|
||||
path, _, err := r.instanceTypeToPath(api.InstanceTypeVM)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Send the request
|
||||
_, _, err = r.query("PUT", fmt.Sprintf("%s/%s/nvram/%s/%s", path, url.PathEscape(name), url.PathEscape(guid), url.PathEscape(varName)), data, ETag)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -10,7 +10,6 @@ import (
|
||||
"github.com/gorilla/websocket"
|
||||
|
||||
"github.com/lxc/incus/v7/shared/api"
|
||||
"github.com/lxc/incus/v7/shared/logger"
|
||||
localtls "github.com/lxc/incus/v7/shared/tls"
|
||||
"github.com/lxc/incus/v7/shared/util"
|
||||
)
|
||||
@ -178,7 +177,7 @@ func (r *ProtocolIncus) GetMetrics() (string, error) {
|
||||
return "", err
|
||||
}
|
||||
|
||||
defer logger.WarnOnError(resp.Body.Close, "Failed to close response body")
|
||||
defer func() { _ = resp.Body.Close() }()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return "", fmt.Errorf("Bad HTTP status: %d", resp.StatusCode)
|
||||
|
||||
@ -11,7 +11,6 @@ import (
|
||||
"github.com/lxc/incus/v7/shared/api"
|
||||
"github.com/lxc/incus/v7/shared/cancel"
|
||||
"github.com/lxc/incus/v7/shared/ioprogress"
|
||||
"github.com/lxc/incus/v7/shared/logger"
|
||||
"github.com/lxc/incus/v7/shared/units"
|
||||
"github.com/lxc/incus/v7/shared/util"
|
||||
)
|
||||
@ -473,7 +472,7 @@ func (r *ProtocolIncus) GetStoragePoolBucketBackupFile(pool string, bucketName s
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer logger.WarnOnError(response.Body.Close, "Failed to close response body")
|
||||
defer func() { _ = response.Body.Close() }()
|
||||
defer close(doneCh)
|
||||
|
||||
if response.StatusCode != http.StatusOK {
|
||||
@ -545,7 +544,7 @@ func (r *ProtocolIncus) CreateStoragePoolBucketBackupStream(poolName string, buc
|
||||
return err
|
||||
}
|
||||
|
||||
defer logger.WarnOnError(response.Body.Close, "Failed to close response body")
|
||||
defer func() { _ = response.Body.Close() }()
|
||||
defer close(doneCh)
|
||||
|
||||
if response.StatusCode != http.StatusOK {
|
||||
@ -603,7 +602,7 @@ func (r *ProtocolIncus) CreateStoragePoolBucketFromBackup(pool string, args Stor
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer logger.WarnOnError(resp.Body.Close, "Failed to close response body")
|
||||
defer func() { _ = resp.Body.Close() }()
|
||||
|
||||
// Handle errors.
|
||||
response, _, err := incusParseResponse(resp)
|
||||
|
||||
@ -16,7 +16,6 @@ import (
|
||||
"github.com/lxc/incus/v7/shared/api"
|
||||
"github.com/lxc/incus/v7/shared/cancel"
|
||||
"github.com/lxc/incus/v7/shared/ioprogress"
|
||||
"github.com/lxc/incus/v7/shared/logger"
|
||||
localtls "github.com/lxc/incus/v7/shared/tls"
|
||||
"github.com/lxc/incus/v7/shared/units"
|
||||
"github.com/lxc/incus/v7/shared/util"
|
||||
@ -433,8 +432,7 @@ func (r *ProtocolIncus) DeleteStoragePoolVolumeSnapshot(pool string, volumeType
|
||||
// Send the request
|
||||
path := fmt.Sprintf(
|
||||
"/storage-pools/%s/volumes/%s/%s/snapshots/%s",
|
||||
url.PathEscape(pool), url.PathEscape(volumeType), url.PathEscape(volumeName), url.PathEscape(snapshotName),
|
||||
)
|
||||
url.PathEscape(pool), url.PathEscape(volumeType), url.PathEscape(volumeName), url.PathEscape(snapshotName))
|
||||
|
||||
op, _, err := r.queryOperation("DELETE", path, nil, "")
|
||||
if err != nil {
|
||||
@ -920,24 +918,6 @@ func (r *ProtocolIncus) DeleteStoragePoolVolume(pool string, volType string, nam
|
||||
return nil
|
||||
}
|
||||
|
||||
// RebuildStoragePoolVolume rebuilds an existing custom storage volume as empty.
|
||||
func (r *ProtocolIncus) RebuildStoragePoolVolume(pool string, volType string, name string, volume api.StorageVolumeRebuildPost) (Operation, error) {
|
||||
err := r.CheckExtension("storage_volumes_rebuild")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
path := fmt.Sprintf("/storage-pools/%s/volumes/%s/%s/rebuild", url.PathEscape(pool), url.PathEscape(volType), url.PathEscape(name))
|
||||
|
||||
// Send the request.
|
||||
op, _, err := r.queryOperation("POST", path, volume, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return op, nil
|
||||
}
|
||||
|
||||
// RenameStoragePoolVolume renames a storage volume.
|
||||
func (r *ProtocolIncus) RenameStoragePoolVolume(pool string, volType string, name string, volume api.StorageVolumePost) error {
|
||||
if !r.HasExtension("storage_api_volume_rename") {
|
||||
@ -1082,7 +1062,7 @@ func (r *ProtocolIncus) GetStorageVolumeBackupFile(pool string, volName string,
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer logger.WarnOnError(response.Body.Close, "Failed to close response body")
|
||||
defer func() { _ = response.Body.Close() }()
|
||||
defer close(doneCh)
|
||||
|
||||
if response.StatusCode != http.StatusOK {
|
||||
@ -1154,7 +1134,7 @@ func (r *ProtocolIncus) CreateStorageVolumeBackupStream(pool string, volName str
|
||||
return err
|
||||
}
|
||||
|
||||
defer logger.WarnOnError(response.Body.Close, "Failed to close response body")
|
||||
defer func() { _ = response.Body.Close() }()
|
||||
defer close(doneCh)
|
||||
|
||||
if response.StatusCode != http.StatusOK {
|
||||
@ -1228,7 +1208,7 @@ func (r *ProtocolIncus) CreateStoragePoolVolumeFromISO(pool string, args Storage
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer logger.WarnOnError(resp.Body.Close, "Failed to close response body")
|
||||
defer func() { _ = resp.Body.Close() }()
|
||||
|
||||
// Handle errors.
|
||||
response, _, err := incusParseResponse(resp)
|
||||
@ -1287,7 +1267,7 @@ func (r *ProtocolIncus) CreateStoragePoolVolumeFromBackup(pool string, args Stor
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer logger.WarnOnError(resp.Body.Close, "Failed to close response body")
|
||||
defer func() { _ = resp.Body.Close() }()
|
||||
|
||||
// Handle errors.
|
||||
response, _, err := incusParseResponse(resp)
|
||||
@ -1330,7 +1310,7 @@ func (r *ProtocolIncus) GetStoragePoolVolumeBlockNBDConn(pool string, volType st
|
||||
|
||||
r.setURLQueryAttributes(&u.URL)
|
||||
|
||||
return r.rawConn(http.MethodGet, &u.URL, "nbd", nil)
|
||||
return r.rawConn(&u.URL, "nbd")
|
||||
}
|
||||
|
||||
// GetStoragePoolVolumeFileSFTPConn returns a connection to the volume's SFTP endpoint.
|
||||
@ -1344,7 +1324,7 @@ func (r *ProtocolIncus) GetStoragePoolVolumeFileSFTPConn(pool string, volType st
|
||||
u.Path("1.0", "storage-pools", pool, "volumes", volType, volName, "sftp")
|
||||
r.setURLQueryAttributes(&u.URL)
|
||||
|
||||
return r.rawConn(http.MethodGet, &u.URL, "sftp", nil)
|
||||
return r.rawConn(&u.URL, "sftp")
|
||||
}
|
||||
|
||||
// GetStoragePoolVolumeFileSFTP returns an SFTP connection to the volume.
|
||||
@ -1535,8 +1515,7 @@ func (r *ProtocolIncus) GetStorageVolumeBitmapNames(pool string, volumeType stri
|
||||
urls := []string{}
|
||||
baseURL := fmt.Sprintf(
|
||||
"/storage-pools/%s/volumes/%s/%s/bitmaps",
|
||||
url.PathEscape(pool), url.PathEscape(volumeType), url.PathEscape(volumeName),
|
||||
)
|
||||
url.PathEscape(pool), url.PathEscape(volumeType), url.PathEscape(volumeName))
|
||||
_, err := r.queryStruct("GET", baseURL, nil, "", &urls)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -1566,26 +1545,6 @@ func (r *ProtocolIncus) GetStorageVolumeBitmaps(pool string, volumeType string,
|
||||
return bitmaps, nil
|
||||
}
|
||||
|
||||
// GetStorageVolumeBitmap returns information about a volume bitmap.
|
||||
func (r *ProtocolIncus) GetStorageVolumeBitmap(pool string, volumeType string, volumeName string, bitmapName string) (*api.StorageVolumeBitmap, error) {
|
||||
if !r.HasExtension("storage_volume_nbd") {
|
||||
return nil, errors.New("The server is missing the required \"storage_volume_nbd\" API extension")
|
||||
}
|
||||
|
||||
bitmap := api.StorageVolumeBitmap{}
|
||||
|
||||
path := fmt.Sprintf(
|
||||
"/storage-pools/%s/volumes/%s/%s/bitmaps/%s",
|
||||
url.PathEscape(pool), url.PathEscape(volumeType), url.PathEscape(volumeName), url.PathEscape(bitmapName),
|
||||
)
|
||||
_, err := r.queryStruct("GET", path, nil, "", &bitmap)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &bitmap, nil
|
||||
}
|
||||
|
||||
// CreateStorageVolumeBitmap creates a new volume bitmap.
|
||||
func (r *ProtocolIncus) CreateStorageVolumeBitmap(pool string, volumeType string, volumeName string, bitmap api.StorageVolumeBitmapsPost) error {
|
||||
if !r.HasExtension("storage_volume_nbd") {
|
||||
@ -1614,8 +1573,7 @@ func (r *ProtocolIncus) DeleteStorageVolumeBitmap(pool string, volumeType string
|
||||
|
||||
path := fmt.Sprintf(
|
||||
"/storage-pools/%s/volumes/%s/%s/bitmaps/%s",
|
||||
url.PathEscape(pool), url.PathEscape(volumeType), url.PathEscape(volumeName), url.PathEscape(bitmapName),
|
||||
)
|
||||
url.PathEscape(pool), url.PathEscape(volumeType), url.PathEscape(volumeName), url.PathEscape(bitmapName))
|
||||
|
||||
_, _, err := r.query("DELETE", path, nil, "")
|
||||
if err != nil {
|
||||
|
||||
@ -136,10 +136,6 @@ type InstanceServer interface {
|
||||
GetInstanceFileSFTPConn(instanceName string) (net.Conn, error)
|
||||
GetInstanceFileSFTP(instanceName string) (*sftp.Client, error)
|
||||
|
||||
GetInstanceNBDConn(instanceName string, args InstanceNBDArgs) (net.Conn, error)
|
||||
|
||||
GetInstancePortForwardConn(instanceName string, forward api.InstancePortForwardPost) (net.Conn, error)
|
||||
|
||||
GetInstanceSnapshotNames(instanceName string) (names []string, err error)
|
||||
GetInstanceSnapshots(instanceName string) (snapshots []api.InstanceSnapshot, err error)
|
||||
GetInstanceSnapshot(instanceName string, name string) (snapshot *api.InstanceSnapshot, ETag string, err error)
|
||||
@ -178,14 +174,6 @@ type InstanceServer interface {
|
||||
DeleteInstanceTemplateFile(name string, templateName string) (err error)
|
||||
|
||||
GetInstanceDebugMemory(name string, format string) (rc io.ReadCloser, err error)
|
||||
RepairInstance(name string, repair api.InstanceDebugRepairPost) (err error)
|
||||
GetInstanceNVRAM(name string) (vars map[string]map[string]*api.InstanceNVRAMVariable, err error)
|
||||
GetInstanceNVRAMGUID(name string, guid string) (vars map[string]*api.InstanceNVRAMVariable, err error)
|
||||
GetRawInstanceNVRAMGUIDVar(name string, guid string, varName string) (resp []byte, attributes uint32, err error)
|
||||
GetInstanceNVRAMGUIDVar(name string, guid string, varName string) (resp *api.InstanceNVRAMVariable, ETag string, err error)
|
||||
DeleteInstanceNVRAMGUIDVar(name string, guid string, varName string) error
|
||||
UpdateRawInstanceNVRAMGUIDVar(name string, guid string, varName string, data []byte, attributes uint32, timestamp int64) error
|
||||
UpdateInstanceNVRAMGUIDVar(name string, guid string, varName string, data api.InstanceNVRAMVariablePut, ETag string) error
|
||||
|
||||
// Event handling functions
|
||||
GetEvents() (listener *EventListener, err error)
|
||||
@ -394,9 +382,6 @@ type InstanceServer interface {
|
||||
MoveStoragePoolVolume(pool string, source InstanceServer, sourcePool string, volume api.StorageVolume, args *StoragePoolVolumeMoveArgs) (op RemoteOperation, err error)
|
||||
MigrateStoragePoolVolume(pool string, volume api.StorageVolumePost) (op Operation, err error)
|
||||
|
||||
// Storage volume rebuild ("storage_volumes_rebuild" API extension)
|
||||
RebuildStoragePoolVolume(pool string, volType string, name string, volume api.StorageVolumeRebuildPost) (op Operation, err error)
|
||||
|
||||
// Storage volume snapshot functions ("storage_api_volume_snapshots" API extension)
|
||||
CreateStoragePoolVolumeSnapshot(pool string, volumeType string, volumeName string, snapshot api.StorageVolumeSnapshotsPost) (op Operation, err error)
|
||||
DeleteStoragePoolVolumeSnapshot(pool string, volumeType string, volumeName string, snapshotName string) (op Operation, err error)
|
||||
@ -420,7 +405,6 @@ type InstanceServer interface {
|
||||
// Storage volume bitmaps manipulations functions ("storage_volume_nbd" API extension)
|
||||
GetStorageVolumeBitmapNames(pool string, volumeType string, volumeName string) ([]string, error)
|
||||
GetStorageVolumeBitmaps(pool string, volumeType string, volumeName string) ([]api.StorageVolumeBitmap, error)
|
||||
GetStorageVolumeBitmap(pool string, volumeType string, volumeName string, bitmapName string) (bitmap *api.StorageVolumeBitmap, err error)
|
||||
CreateStorageVolumeBitmap(pool string, volumeType string, volumeName string, bitmap api.StorageVolumeBitmapsPost) error
|
||||
DeleteStorageVolumeBitmap(pool string, volumeType string, volumeName string, bitmapName string) error
|
||||
|
||||
@ -734,13 +718,6 @@ type InstanceFileArgs struct {
|
||||
WriteMode string
|
||||
}
|
||||
|
||||
// The InstanceNBDArgs struct is used when connecting to an instance's disks over NBD.
|
||||
// API extension: instance_nbd.
|
||||
type InstanceNBDArgs struct {
|
||||
// Whether to connect to an already running NBD session
|
||||
Reuse bool
|
||||
}
|
||||
|
||||
// The InstanceFileResponse struct is used as part of the response for a instance file download.
|
||||
type InstanceFileResponse struct {
|
||||
// User id that owns the file
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
package incus
|
||||
|
||||
import (
|
||||
"compress/gzip"
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
@ -16,10 +17,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/klauspost/pgzip"
|
||||
|
||||
"github.com/lxc/incus/v7/shared/api"
|
||||
"github.com/lxc/incus/v7/shared/archive"
|
||||
"github.com/lxc/incus/v7/shared/ioprogress"
|
||||
"github.com/lxc/incus/v7/shared/logger"
|
||||
"github.com/lxc/incus/v7/shared/osarch"
|
||||
@ -164,7 +162,7 @@ func (r *ProtocolOCI) GetImageFile(fingerprint string, req ImageFileRequest) (*I
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer logger.WarnOnError(func() error { return os.RemoveAll(ociPath) }, "Failed to remove temporary directory")
|
||||
defer func() { _ = os.RemoveAll(ociPath) }()
|
||||
|
||||
err = os.Mkdir(filepath.Join(ociPath, "oci"), 0o700)
|
||||
if err != nil {
|
||||
@ -186,8 +184,7 @@ func (r *ProtocolOCI) GetImageFile(fingerprint string, req ImageFileRequest) (*I
|
||||
stdout, err := r.runSkopeo(
|
||||
"copy", info.Alias,
|
||||
"--remove-signatures",
|
||||
fmt.Sprintf("oci:%s:%s", filepath.Join(ociPath, "oci"), imageTag),
|
||||
)
|
||||
fmt.Sprintf("oci:%s:%s", filepath.Join(ociPath, "oci"), imageTag))
|
||||
if err != nil {
|
||||
logger.Debug("Error copying remote image to local", logger.Ctx{"image": info.Alias, "stdout": stdout, "stderr": err})
|
||||
return nil, err
|
||||
@ -236,8 +233,8 @@ func (r *ProtocolOCI) GetImageFile(fingerprint string, req ImageFileRequest) (*I
|
||||
|
||||
// Push the metadata tarball.
|
||||
pipeRead, pipeWrite = io.Pipe()
|
||||
defer logger.WarnOnError(pipeRead.Close, "Failed to close pipe reader")
|
||||
defer logger.WarnOnError(pipeWrite.Close, "Failed to close pipe writer")
|
||||
defer pipeRead.Close()
|
||||
defer pipeWrite.Close()
|
||||
|
||||
if req.ProgressHandler != nil {
|
||||
pipeRead = &ioprogress.ProgressReader{
|
||||
@ -250,12 +247,7 @@ func (r *ProtocolOCI) GetImageFile(fingerprint string, req ImageFileRequest) (*I
|
||||
}
|
||||
}
|
||||
|
||||
compressWrite := pgzip.NewWriter(pipeWrite)
|
||||
err = compressWrite.SetConcurrency(1<<20, archive.CompressionThreads())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
compressWrite := gzip.NewWriter(pipeWrite)
|
||||
metadataProcess := subprocess.NewProcessWithFds("tar", []string{"-cf", "-", "-C", filepath.Join(ociPath, "image"), "config.json", "metadata.yaml"}, nil, compressWrite, os.Stderr)
|
||||
err = metadataProcess.Start(ctx)
|
||||
if err != nil {
|
||||
@ -264,8 +256,8 @@ func (r *ProtocolOCI) GetImageFile(fingerprint string, req ImageFileRequest) (*I
|
||||
|
||||
go func() {
|
||||
_, _ = metadataProcess.Wait(ctx)
|
||||
_ = compressWrite.Close()
|
||||
_ = pipeWrite.Close()
|
||||
compressWrite.Close()
|
||||
pipeWrite.Close()
|
||||
}()
|
||||
|
||||
size, err := util.SafeCopy(req.MetaFile, pipeRead)
|
||||
@ -277,8 +269,8 @@ func (r *ProtocolOCI) GetImageFile(fingerprint string, req ImageFileRequest) (*I
|
||||
|
||||
// Push the rootfs tarball.
|
||||
pipeRead, pipeWrite = io.Pipe()
|
||||
defer logger.WarnOnError(pipeRead.Close, "Failed to close pipe reader")
|
||||
defer logger.WarnOnError(pipeWrite.Close, "Failed to close pipe writer")
|
||||
defer pipeRead.Close()
|
||||
defer pipeWrite.Close()
|
||||
|
||||
if req.ProgressHandler != nil {
|
||||
pipeRead = &ioprogress.ProgressReader{
|
||||
@ -291,12 +283,7 @@ func (r *ProtocolOCI) GetImageFile(fingerprint string, req ImageFileRequest) (*I
|
||||
}
|
||||
}
|
||||
|
||||
compressWrite = pgzip.NewWriter(pipeWrite)
|
||||
err = compressWrite.SetConcurrency(1<<20, archive.CompressionThreads())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
compressWrite = gzip.NewWriter(pipeWrite)
|
||||
rootfsProcess := subprocess.NewProcessWithFds("tar", []string{"-cf", "-", "-C", filepath.Join(ociPath, "image", "rootfs"), "."}, nil, compressWrite, nil)
|
||||
err = rootfsProcess.Start(ctx)
|
||||
if err != nil {
|
||||
@ -305,8 +292,8 @@ func (r *ProtocolOCI) GetImageFile(fingerprint string, req ImageFileRequest) (*I
|
||||
|
||||
go func() {
|
||||
_, _ = rootfsProcess.Wait(ctx)
|
||||
_ = compressWrite.Close()
|
||||
_ = pipeWrite.Close()
|
||||
compressWrite.Close()
|
||||
pipeWrite.Close()
|
||||
}()
|
||||
|
||||
size, err = util.SafeCopy(req.RootfsFile, pipeRead)
|
||||
@ -383,7 +370,7 @@ func (r *ProtocolOCI) runSkopeo(action string, image string, args ...string) (st
|
||||
return "", err
|
||||
}
|
||||
|
||||
defer logger.WarnOnError(authFile.Close, "Failed to close auth file")
|
||||
defer authFile.Close()
|
||||
defer os.Remove(authFile.Name())
|
||||
|
||||
err = authFile.Chmod(0o600)
|
||||
@ -411,8 +398,7 @@ func (r *ProtocolOCI) runSkopeo(action string, image string, args ...string) (st
|
||||
env,
|
||||
nil,
|
||||
"skopeo",
|
||||
args...,
|
||||
)
|
||||
args...)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@ -430,7 +416,7 @@ func (r *ProtocolOCI) GetImageAlias(name string) (*api.ImageAliasesEntry, string
|
||||
}
|
||||
|
||||
// Get the image information from skopeo.
|
||||
stdout, err := r.runSkopeo("inspect", name, "--no-tags")
|
||||
stdout, err := r.runSkopeo("inspect", name)
|
||||
if err != nil {
|
||||
logger.Debug("Error getting image alias", logger.Ctx{"name": name, "stdout": stdout, "stderr": err})
|
||||
r.errors[name] = err
|
||||
|
||||
@ -58,7 +58,7 @@ func unpackOCIImage(imagePath string, imageTag string, bundlePath string) error
|
||||
}
|
||||
|
||||
engineExt := casext.NewEngine(engine)
|
||||
defer logger.WarnOnError(engine.Close, "Failed to close CAS engine")
|
||||
defer func() { _ = engine.Close() }()
|
||||
|
||||
return umoci.Unpack(engineExt, imageTag, bundlePath, unpackOptions)
|
||||
}
|
||||
|
||||
@ -22,15 +22,7 @@ type operation struct {
|
||||
handlerLock sync.Mutex
|
||||
skipListener bool
|
||||
|
||||
chActive chan bool
|
||||
chActiveOnce sync.Once
|
||||
}
|
||||
|
||||
// closeChActive closes the chActive channel exactly once.
|
||||
func (op *operation) closeChActive() {
|
||||
op.chActiveOnce.Do(func() {
|
||||
close(op.chActive)
|
||||
})
|
||||
chActive chan bool
|
||||
}
|
||||
|
||||
// AddHandler adds a function to be called whenever an event is received.
|
||||
@ -170,18 +162,6 @@ func (op *operation) WaitContext(ctx context.Context) error {
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
// Tear down the listener, cancel the server-side operation and unblock the monitor.
|
||||
op.handlerLock.Lock()
|
||||
if op.listener != nil {
|
||||
op.listener.Disconnect()
|
||||
op.listener = nil
|
||||
}
|
||||
|
||||
op.handlerLock.Unlock()
|
||||
|
||||
_ = op.Cancel()
|
||||
op.closeChActive()
|
||||
|
||||
return ctx.Err()
|
||||
case <-op.chActive:
|
||||
}
|
||||
@ -251,14 +231,14 @@ func (op *operation) setupListener() error {
|
||||
if op.StatusCode.IsFinal() {
|
||||
op.listener.Disconnect()
|
||||
op.listener = nil
|
||||
op.closeChActive()
|
||||
close(op.chActive)
|
||||
return
|
||||
}
|
||||
})
|
||||
if err != nil {
|
||||
op.listener.Disconnect()
|
||||
op.listener = nil
|
||||
op.closeChActive()
|
||||
close(op.chActive)
|
||||
close(chReady)
|
||||
|
||||
return err
|
||||
@ -286,7 +266,7 @@ func (op *operation) setupListener() error {
|
||||
op.handlerLock.Lock()
|
||||
if op.listener != nil {
|
||||
op.Err = listener.err.Error()
|
||||
op.closeChActive()
|
||||
close(op.chActive)
|
||||
}
|
||||
|
||||
op.handlerLock.Unlock()
|
||||
@ -300,7 +280,7 @@ func (op *operation) setupListener() error {
|
||||
if err != nil {
|
||||
op.listener.Disconnect()
|
||||
op.listener = nil
|
||||
op.closeChActive()
|
||||
close(op.chActive)
|
||||
close(chReady)
|
||||
|
||||
return err
|
||||
@ -310,7 +290,7 @@ func (op *operation) setupListener() error {
|
||||
if op.StatusCode.IsFinal() {
|
||||
op.listener.Disconnect()
|
||||
op.listener = nil
|
||||
op.closeChActive()
|
||||
close(op.chActive)
|
||||
close(chReady)
|
||||
|
||||
if op.Err != "" {
|
||||
|
||||
@ -117,7 +117,7 @@ func (r *ProtocolSimpleStreams) GetImageFile(fingerprint string, req ImageFileRe
|
||||
// Download function
|
||||
download := func(path string, filename string, hash string, target io.WriteSeeker) (int64, error) {
|
||||
// Try over http
|
||||
uri, err := urlJoinPathAbsolute(fmt.Sprintf("http://%s", strings.TrimPrefix(r.httpHost, "https://")), path)
|
||||
uri, err := url.JoinPath(fmt.Sprintf("http://%s", strings.TrimPrefix(r.httpHost, "https://")), path)
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
@ -130,7 +130,7 @@ func (r *ProtocolSimpleStreams) GetImageFile(fingerprint string, req ImageFileRe
|
||||
}
|
||||
|
||||
// Try over https
|
||||
uri, err := urlJoinPathAbsolute(r.httpHost, path)
|
||||
uri, err := url.JoinPath(r.httpHost, path)
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
@ -176,9 +176,9 @@ func (r *ProtocolSimpleStreams) GetImageFile(fingerprint string, req ImageFileRe
|
||||
return -1, err
|
||||
}
|
||||
|
||||
defer logger.WarnOnError(deltaFile.Close, "Failed to close temporary file")
|
||||
defer func() { _ = deltaFile.Close() }()
|
||||
|
||||
defer logger.WarnOnError(func() error { return os.Remove(deltaFile.Name()) }, "Failed to remove temporary file")
|
||||
defer func() { _ = os.Remove(deltaFile.Name()) }()
|
||||
|
||||
// Download the delta
|
||||
_, err = download(file.Path, "rootfs delta", file.Sha256, deltaFile)
|
||||
@ -192,9 +192,9 @@ func (r *ProtocolSimpleStreams) GetImageFile(fingerprint string, req ImageFileRe
|
||||
return -1, err
|
||||
}
|
||||
|
||||
defer logger.WarnOnError(patchedFile.Close, "Failed to close temporary file")
|
||||
defer func() { _ = patchedFile.Close() }()
|
||||
|
||||
defer logger.WarnOnError(func() error { return os.Remove(patchedFile.Name()) }, "Failed to remove temporary file")
|
||||
defer func() { _ = os.Remove(patchedFile.Name()) }()
|
||||
|
||||
// Apply it
|
||||
_, err = subprocess.RunCommand("xdelta3", "-f", "-d", "-s", srcPath, deltaFile.Name(), patchedFile.Name())
|
||||
@ -374,20 +374,3 @@ func (r *ProtocolSimpleStreams) GetImageAliasArchitectures(imageType string, nam
|
||||
func (r *ProtocolSimpleStreams) ExportImage(_ string, _ api.ImageExportPost) (Operation, error) {
|
||||
return nil, errors.New("Exporting images is not supported by the simplestreams protocol")
|
||||
}
|
||||
|
||||
func urlJoinPathAbsolute(baseHost string, path string) (result string, err error) {
|
||||
if strings.HasPrefix("/", path) {
|
||||
// absolute path
|
||||
baseHostURL, err := url.ParseRequestURI(baseHost)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
baseHostURL.Path = path
|
||||
|
||||
return baseHostURL.String(), nil
|
||||
}
|
||||
|
||||
// relative path
|
||||
return url.JoinPath(baseHost, path)
|
||||
}
|
||||
|
||||
@ -57,7 +57,7 @@ func tlsHTTPClient(client *http.Client, tlsClientCert string, tlsClientKey strin
|
||||
}
|
||||
|
||||
// Setup TLS
|
||||
if resetName || config.ServerName == "" {
|
||||
if resetName {
|
||||
hostName, _, err := net.SplitHostPort(addr)
|
||||
if err != nil {
|
||||
hostName = addr
|
||||
@ -118,19 +118,8 @@ func tlsHTTPClient(client *http.Client, tlsClientCert string, tlsClientKey strin
|
||||
|
||||
conn, err := tlsDial(network, addr, transport.TLSClientConfig, false)
|
||||
if err != nil {
|
||||
// On certificate verification failure, we may have gotten redirected to a
|
||||
// non-Incus machine, retry with the dialed address as the server name.
|
||||
var certVerifyErr *tls.CertificateVerificationError
|
||||
hostnameErr := x509.HostnameError{}
|
||||
if errors.As(err, &certVerifyErr) || errors.As(err, &hostnameErr) {
|
||||
conn, retryErr := tlsDial(network, addr, transport.TLSClientConfig, true)
|
||||
if retryErr == nil {
|
||||
return conn, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Return the initial error as the retry error may be misleading.
|
||||
return nil, err
|
||||
// We may have gotten redirected to a non-Incus machine
|
||||
return tlsDial(network, addr, transport.TLSClientConfig, true)
|
||||
}
|
||||
|
||||
return conn, nil
|
||||
|
||||
@ -118,7 +118,6 @@ Tag | Description
|
||||
`leftjoin=<table.column>` | Applies a `LEFT JOIN` of the same form as a `JOIN`.
|
||||
`joinon=<table>.<column>` | Overrides the default `JOIN ON` clause with the given table and column, replacing `<table>.<joinTable_id>` above.
|
||||
`jointo=<column>` | Overrides the default target column `id` with the given column, replacing the `id` in `<joinTable.id>` above. This is intended for "loose" foreign keys, not using the ID column. Therefore, this is intended to be used in conjunction with `joinon` and `omit=create,update` to get the expected behavior.
|
||||
`joinas=<alias>` | Sets an alias for the joined table name, in case it clashes with another table.
|
||||
`primary=yes` | Assigns column associated with the field to be sufficient for returning a row from the table. Will default to `Name` if unspecified. Fields with this key will be included in the default 'ORDER BY' clause.
|
||||
`omit=<Stmt Types>` | Omits a given field from consideration for the comma separated list of statement types (`create`, `objects-by-Name`, `update`).
|
||||
`ignore` | Outright ignore the struct field as though it does not exist. `ignore` needs to be the only tag value in order to be recognized.
|
||||
@ -196,11 +195,6 @@ including a comma separated list to `references=<OtherEntity>` in the code gener
|
||||
A struct that contains a field named `ReferenceID` will be parsed this way.
|
||||
`generate-database` will use this struct to generate more abstract SQL statements and functions of the form `<parent_table>_<this_table>`.
|
||||
|
||||
The associated `Filter` struct may include a `ReferenceID []int` field.
|
||||
This generates an `IN` clause matching on the parent column, with the integer values inlined into the query to avoid query parameter count limits.
|
||||
A nil slice leaves the filter unset while an empty (non-nil) slice matches nothing.
|
||||
When the field is present, the generated per-parent helpers and nested reference fetches are automatically scoped to the relevant parent IDs rather than fetching the whole table.
|
||||
|
||||
Real world invocation of these statements and functions should be done through an `EntityTable` `method` call with the tag `references=<ThisStruct>`. This `EntityTable` will replace the `<parent_table>` above.
|
||||
|
||||
Example:
|
||||
|
||||
@ -9,5 +9,4 @@ var Imports = []string{
|
||||
"fmt",
|
||||
"strings",
|
||||
"github.com/mattn/go-sqlite3",
|
||||
"github.com/google/uuid",
|
||||
}
|
||||
|
||||
@ -361,11 +361,6 @@ func (f *Field) SelectColumn(mapping *Mapping, primaryTable string) (string, err
|
||||
var column string
|
||||
join := f.joinConfig()
|
||||
if join != "" {
|
||||
joinAs := f.Config.Get("joinas")
|
||||
if joinAs != "" {
|
||||
join = joinAs + "." + strings.Split(join, ".")[1]
|
||||
}
|
||||
|
||||
column = join
|
||||
} else {
|
||||
column = fmt.Sprintf("%s.%s", tableName, columnName)
|
||||
@ -464,14 +459,7 @@ func (f *Field) JoinClause(mapping *Mapping, table string) (string, error) {
|
||||
joinTo = f.Config.Get("jointo")
|
||||
}
|
||||
|
||||
joinAs := f.Config.Get("joinas")
|
||||
if joinAs == "" {
|
||||
joinAs = joinTable
|
||||
} else {
|
||||
joinTable = joinTable + " " + joinAs
|
||||
}
|
||||
|
||||
return fmt.Sprintf(joinTemplate, joinTable, joinOn, joinAs, joinTo), nil
|
||||
return fmt.Sprintf(joinTemplate, joinTable, joinOn, joinTable, joinTo), nil
|
||||
}
|
||||
|
||||
// InsertColumn returns a column name and parameter value suitable for an 'INSERT', 'UPDATE', or 'DELETE' statement.
|
||||
|
||||
@ -5,7 +5,6 @@ package db
|
||||
import (
|
||||
"fmt"
|
||||
"go/types"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/tools/go/packages"
|
||||
@ -166,7 +165,7 @@ func (m *Method) getNames(buf *file.Buffer) error {
|
||||
}
|
||||
|
||||
buf.L("%s %s {", branch, activeCriteria(filter, ignoredFilters[i]))
|
||||
var args strings.Builder
|
||||
var args string
|
||||
for _, name := range filter {
|
||||
for _, field := range mapping.Fields {
|
||||
if name == field.Name && util.IsNeitherFalseNorEmpty(field.Config.Get("marshal")) {
|
||||
@ -177,14 +176,14 @@ func (m *Method) getNames(buf *file.Buffer) error {
|
||||
|
||||
buf.L("marshaledFilter%s, err := %s(filter.%s)", name, marshalFunc, name)
|
||||
m.ifErrNotNil(buf, true, "nil", "err")
|
||||
fmt.Fprintf(&args, "marshaledFilter%s,", name)
|
||||
args += fmt.Sprintf("marshaledFilter%s,", name)
|
||||
} else if name == field.Name {
|
||||
fmt.Fprintf(&args, "filter.%s,", name)
|
||||
args += fmt.Sprintf("filter.%s,", name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
buf.L("args = append(args, []any{%s}...)", args.String())
|
||||
buf.L("args = append(args, []any{%s}...)", args)
|
||||
buf.L("if len(filters) == 1 {")
|
||||
buf.L("sqlStmt, err = Stmt(db, %s)", stmtCodeVar(m.entity, "names", filter...))
|
||||
|
||||
@ -330,29 +329,6 @@ func (m *Method) getMany(buf *file.Buffer) error {
|
||||
continue
|
||||
}
|
||||
|
||||
if filter.Name == "ReferenceID" {
|
||||
if filter.Type.Name != "[]int" {
|
||||
return fmt.Errorf("ReferenceID filter for entity %q must be of type []int", m.entity)
|
||||
}
|
||||
|
||||
// Filter on the parent column, inlining the integer values to avoid
|
||||
// query parameter count limits. An empty (non-nil) list matches nothing.
|
||||
buf.L("if filter.%s != nil {", filter.Name)
|
||||
buf.L("values := make([]string, 0, len(filter.%s))", filter.Name)
|
||||
buf.L("for _, v := range filter.%s {", filter.Name)
|
||||
buf.L("values = append(values, fmt.Sprintf(\"%%d\", v))")
|
||||
buf.L("}")
|
||||
buf.N()
|
||||
buf.L("if len(values) == 0 {")
|
||||
buf.L("values = append(values, \"NULL\")")
|
||||
buf.L("}")
|
||||
buf.N()
|
||||
buf.L("entries = append(entries, fmt.Sprintf(\"%%s_id IN (%%s)\", parentColumnPrefix, strings.Join(values, \",\")))")
|
||||
buf.L("}")
|
||||
buf.N()
|
||||
continue
|
||||
}
|
||||
|
||||
buf.L("if filter.%s != nil {", filter.Name)
|
||||
buf.L("entries = append(entries, \"%s = ?\")", lex.SnakeCase(filter.Name))
|
||||
buf.L("args = append(args, filter.%s)", filter.Name)
|
||||
@ -398,7 +374,7 @@ func (m *Method) getMany(buf *file.Buffer) error {
|
||||
}
|
||||
|
||||
buf.L("%s %s {", branch, activeCriteria(filter, ignoredFilters[i]))
|
||||
var args strings.Builder
|
||||
var args string
|
||||
for _, name := range filter {
|
||||
for _, field := range mapping.Fields {
|
||||
if name == field.Name && util.IsNeitherFalseNorEmpty(field.Config.Get("marshal")) {
|
||||
@ -409,14 +385,14 @@ func (m *Method) getMany(buf *file.Buffer) error {
|
||||
|
||||
buf.L("marshaledFilter%s, err := %s(filter.%s)", name, marshalFunc, name)
|
||||
m.ifErrNotNil(buf, true, "nil", "err")
|
||||
fmt.Fprintf(&args, "marshaledFilter%s,", name)
|
||||
args += fmt.Sprintf("marshaledFilter%s,", name)
|
||||
} else if name == field.Name {
|
||||
fmt.Fprintf(&args, "filter.%s,", name)
|
||||
args += fmt.Sprintf("filter.%s,", name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
buf.L("args = append(args, []any{%s}...)", args.String())
|
||||
buf.L("args = append(args, []any{%s}...)", args)
|
||||
buf.L("if len(filters) == 1 {")
|
||||
buf.L("sqlStmt, err = Stmt(db, %s)", stmtCodeVar(m.entity, "objects", filter...))
|
||||
|
||||
@ -557,46 +533,13 @@ func (m *Method) getMany(buf *file.Buffer) error {
|
||||
buf.L("}")
|
||||
buf.L("}")
|
||||
buf.N()
|
||||
|
||||
scopeByParent := slices.Contains(FieldNames(refMapping.Filters), "ReferenceID") && mapping.FieldByName("ID") != nil
|
||||
if scopeByParent {
|
||||
// Restrict the reference fetch to the retrieved objects when filtered.
|
||||
buf.L("%s := map[int]map[string]string{}", refSlice)
|
||||
buf.L("if len(objects) > 0 {")
|
||||
buf.L("if len(filters) > 0 {")
|
||||
buf.L("referenceIDs := make([]int, 0, len(objects))")
|
||||
buf.L("for _, object := range objects {")
|
||||
buf.L("referenceIDs = append(referenceIDs, object.ID)")
|
||||
buf.L("}")
|
||||
buf.N()
|
||||
buf.L("if len(%sFilters) == 0 {", refVar)
|
||||
buf.L("%sFilters = append(%sFilters, %s{})", refVar, refVar, entityFilter(refStruct))
|
||||
buf.L("}")
|
||||
buf.N()
|
||||
buf.L("for i := range %sFilters {", refVar)
|
||||
buf.L("%sFilters[i].ReferenceID = referenceIDs", refVar)
|
||||
buf.L("}")
|
||||
buf.L("}")
|
||||
buf.N()
|
||||
}
|
||||
|
||||
assign := ":="
|
||||
if scopeByParent {
|
||||
assign = "="
|
||||
}
|
||||
|
||||
if mapping.Type == ReferenceTable {
|
||||
// A reference table should let its child reference know about its parent.
|
||||
buf.L("%s, err %s Get%s(ctx, db, parentTablePrefix+\"_%s\", parentColumnPrefix+\"_%s\", %sFilters...)", refSlice, assign, lex.Plural(refStruct), lex.Plural(m.entity), m.entity, refVar)
|
||||
m.ifErrNotNil(buf, !scopeByParent, "nil", "err")
|
||||
buf.L("%s, err := Get%s(ctx, db, parentTablePrefix+\"_%s\", parentColumnPrefix+\"_%s\", %sFilters...)", refSlice, lex.Plural(refStruct), lex.Plural(m.entity), m.entity, refVar)
|
||||
m.ifErrNotNil(buf, true, "nil", "err")
|
||||
} else {
|
||||
buf.L("%s, err %s Get%s(ctx, db, \"%s\", %sFilters...)", refSlice, assign, lex.Plural(refStruct), m.entity, refVar)
|
||||
m.ifErrNotNil(buf, !scopeByParent, "nil", "err")
|
||||
}
|
||||
|
||||
if scopeByParent {
|
||||
buf.L("}")
|
||||
buf.N()
|
||||
buf.L("%s, err := Get%s(ctx, db, \"%s\", %sFilters...)", refSlice, lex.Plural(refStruct), m.entity, refVar)
|
||||
m.ifErrNotNil(buf, true, "nil", "err")
|
||||
}
|
||||
|
||||
buf.L("for i := range objects {")
|
||||
@ -678,18 +621,6 @@ func (m *Method) getRefs(buf *file.Buffer, parentTable string, refMapping *Mappi
|
||||
refParent := lex.CamelCase(m.entity)
|
||||
refParentList := refParent + lex.PascalCase(refList)
|
||||
|
||||
if slices.Contains(FieldNames(refMapping.Filters), "ReferenceID") {
|
||||
// Restrict the fetch to the parent object.
|
||||
buf.L("if len(filters) == 0 {")
|
||||
buf.L("filters = append(filters, %s{})", entityFilter(refStruct))
|
||||
buf.L("}")
|
||||
buf.N()
|
||||
buf.L("for i := range filters {")
|
||||
buf.L("filters[i].ReferenceID = []int{%sID}", refParent)
|
||||
buf.L("}")
|
||||
buf.N()
|
||||
}
|
||||
|
||||
switch refMapping.Type {
|
||||
case ReferenceTable:
|
||||
buf.L("%s, err := Get%s(ctx, db, \"%s\", \"%s\", filters...)", refParentList, lex.Plural(refStruct), parentTable, lex.SnakeCase(m.entity))
|
||||
@ -919,25 +850,25 @@ func (m *Method) create(buf *file.Buffer, replace bool) error {
|
||||
buf.L("}")
|
||||
buf.N()
|
||||
buf.L("queryStr := fmt.Sprintf(%s, fillParent...)", stmtLocal)
|
||||
var createParams strings.Builder
|
||||
createParams := ""
|
||||
columnFields := mapping.ColumnFields("ID")
|
||||
if mapping.Type == ReferenceTable {
|
||||
buf.L("for _, object := range objects {")
|
||||
}
|
||||
|
||||
for i, field := range columnFields {
|
||||
fmt.Fprintf(&createParams, "object.%s", field.Name)
|
||||
createParams += fmt.Sprintf("object.%s", field.Name)
|
||||
if i < len(columnFields) {
|
||||
createParams.WriteString(", ")
|
||||
createParams += ", "
|
||||
}
|
||||
}
|
||||
|
||||
refFields := mapping.RefFields()
|
||||
if len(refFields) == 0 {
|
||||
buf.L("_, err := db.ExecContext(ctx, queryStr, %s)", createParams.String())
|
||||
buf.L("_, err := db.ExecContext(ctx, queryStr, %s)", createParams)
|
||||
m.ifErrNotNil(buf, true, fmt.Sprintf(`fmt.Errorf("Insert failed for \"%%s_%s\" table: %%w", parentTablePrefix, err)`, lex.Plural(m.entity)))
|
||||
} else {
|
||||
buf.L("result, err := db.ExecContext(ctx, queryStr, %s)", createParams.String())
|
||||
buf.L("result, err := db.ExecContext(ctx, queryStr, %s)", createParams)
|
||||
m.ifErrNotNil(buf, true, fmt.Sprintf(`fmt.Errorf("Insert failed for \"%%s_%s\" table: %%w", parentTablePrefix, err)`, lex.Plural(m.entity)))
|
||||
buf.L("id, err := result.LastInsertId()")
|
||||
m.ifErrNotNil(buf, true, "fmt.Errorf(\"Failed to fetch ID: %w\", err)")
|
||||
|
||||
@ -578,7 +578,6 @@ func validateFieldConfig(config url.Values) error {
|
||||
"join",
|
||||
"leftjoin",
|
||||
"joinon",
|
||||
"joinas",
|
||||
"omit":
|
||||
|
||||
_, err := exactlyOneValue(tag, values)
|
||||
|
||||
@ -62,7 +62,7 @@ func NewStmt(localPath string, parsedPkgs []*packages.Package, entity, kind stri
|
||||
|
||||
// Generate plumbing and wiring code for the desired statement.
|
||||
func (s *Stmt) Generate(buf *file.Buffer) error {
|
||||
kind, _, _ := strings.Cut(s.kind, "-by-")
|
||||
kind := strings.Split(s.kind, "-by-")[0]
|
||||
|
||||
switch kind {
|
||||
case "objects":
|
||||
|
||||
@ -8,7 +8,6 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/lxc/incus/v7/cmd/generate-database/lex"
|
||||
"github.com/lxc/incus/v7/shared/logger"
|
||||
)
|
||||
|
||||
const codeGeneratedByLine = `// Code generated by generate-database from the incus project - DO NOT EDIT.`
|
||||
@ -54,8 +53,7 @@ func Reset(path string, imports []string, buildComment string, iface bool) error
|
||||
}
|
||||
}
|
||||
|
||||
var content strings.Builder
|
||||
fmt.Fprintf(&content, `%s%s
|
||||
content := fmt.Sprintf(`%s%s
|
||||
|
||||
package %s
|
||||
|
||||
@ -63,19 +61,19 @@ import (
|
||||
`, buildComment, codeGeneratedByLine, os.Getenv("GOPACKAGE"))
|
||||
|
||||
for _, uri := range imports {
|
||||
fmt.Fprintf(&content, "\t%q\n", uri)
|
||||
content += fmt.Sprintf("\t%q\n", uri)
|
||||
}
|
||||
|
||||
content.WriteString(")\n\n")
|
||||
content += ")\n\n"
|
||||
|
||||
bytes := []byte(content.String())
|
||||
bytes := []byte(content)
|
||||
|
||||
var err error
|
||||
|
||||
if path == "-" {
|
||||
_, err = os.Stdout.Write(bytes)
|
||||
} else {
|
||||
err = os.WriteFile(path, []byte(content.String()), 0o644)
|
||||
err = os.WriteFile(path, []byte(content), 0o644)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
@ -124,7 +122,7 @@ func Append(entity string, path string, snippet Snippet, iface bool) error {
|
||||
return fmt.Errorf("Open target source code file %q: %w", path, err)
|
||||
}
|
||||
|
||||
defer logger.WarnOnError(file.Close, "Failed to close file")
|
||||
defer func() { _ = file.Close() }()
|
||||
}
|
||||
|
||||
bytes, err := buffer.code()
|
||||
@ -169,7 +167,7 @@ func appendInterface(entity string, path string, snippet Snippet) error {
|
||||
return fmt.Errorf("Open target source code file %q: %w", interfacePath, err)
|
||||
}
|
||||
|
||||
defer logger.WarnOnError(file.Close, "Failed to close file")
|
||||
defer func() { _ = file.Close() }()
|
||||
|
||||
err = snippet.GenerateSignature(buffer)
|
||||
if err != nil {
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
package lex
|
||||
|
||||
import (
|
||||
"slices"
|
||||
"strings"
|
||||
)
|
||||
|
||||
@ -17,7 +16,7 @@ func Plural(s string) string {
|
||||
return s + "es"
|
||||
}
|
||||
|
||||
if strings.HasSuffix(s, "y") && !slices.Contains([]string{"a", "e", "i", "o", "u"}, string(s[len(s)-2])) {
|
||||
if strings.HasSuffix(s, "y") {
|
||||
return s[:len(s)-1] + "ies"
|
||||
}
|
||||
|
||||
|
||||
@ -33,7 +33,6 @@ var api10 = []APIEndpoint{
|
||||
operationCmd,
|
||||
operationWebsocket,
|
||||
operationWait,
|
||||
portForwardCmd,
|
||||
sftpCmd,
|
||||
stateCmd,
|
||||
}
|
||||
|
||||
@ -56,7 +56,7 @@ func eventsSocket(d *Daemon, r *http.Request, w http.ResponseWriter) error {
|
||||
return err
|
||||
}
|
||||
|
||||
defer logger.WarnOnError(conn.Close, "Failed to close connection") // Ensure listener below ends when this function ends.
|
||||
defer func() { _ = conn.Close() }() // Ensure listener below ends when this function ends.
|
||||
|
||||
listenerConnection = events.NewWebsocketListenerConnection(conn)
|
||||
} else {
|
||||
@ -70,7 +70,7 @@ func eventsSocket(d *Daemon, r *http.Request, w http.ResponseWriter) error {
|
||||
return err
|
||||
}
|
||||
|
||||
defer logger.WarnOnError(conn.Close, "Failed to close connection") // Ensure listener below ends when this function ends.
|
||||
defer func() { _ = conn.Close() }() // Ensure listener below ends when this function ends.
|
||||
|
||||
listenerConnection, err = events.NewStreamListenerConnection(conn)
|
||||
if err != nil {
|
||||
|
||||
@ -321,7 +321,10 @@ func (s *execWs) Do(op *operations.Operation) error {
|
||||
l := logger.AddContext(logger.Ctx{"PID": cmd.Process.Pid, "interactive": s.interactive})
|
||||
l.Debug("Instance process started")
|
||||
|
||||
wgEOF.Go(func() {
|
||||
wgEOF.Add(1)
|
||||
go func() {
|
||||
defer wgEOF.Done()
|
||||
|
||||
l.Debug("Exec control handler started")
|
||||
defer l.Debug("Exec control handler finished")
|
||||
|
||||
@ -369,10 +372,13 @@ func (s *execWs) Do(op *operations.Operation) error {
|
||||
|
||||
osHandleExecControl(control, s, ptys[0], cmd, l)
|
||||
}
|
||||
})
|
||||
}()
|
||||
|
||||
if s.interactive {
|
||||
wgEOF.Go(func() {
|
||||
wgEOF.Add(1)
|
||||
go func() {
|
||||
defer wgEOF.Done()
|
||||
|
||||
l.Debug("Exec mirror websocket started", logger.Ctx{"number": 0})
|
||||
defer l.Debug("Exec mirror websocket finished", logger.Ctx{"number": 0})
|
||||
|
||||
@ -385,7 +391,7 @@ func (s *execWs) Do(op *operations.Operation) error {
|
||||
<-readDone
|
||||
<-writeDone
|
||||
_ = conn.Close()
|
||||
})
|
||||
}()
|
||||
} else {
|
||||
wgEOF.Add(len(ttys) - 1)
|
||||
for i := range ttys {
|
||||
|
||||
@ -8,7 +8,6 @@ import (
|
||||
"os"
|
||||
"os/signal"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@ -123,11 +122,9 @@ func (c *cmdAgent) run(cmd *cobra.Command, args []string) error {
|
||||
// Load the kernel driver.
|
||||
err = osLoadModules()
|
||||
if err != nil {
|
||||
logger.Error("Failed to check for agent server compatibility", logger.Ctx{"error": err})
|
||||
return err
|
||||
}
|
||||
|
||||
canStartServer := err == nil
|
||||
|
||||
d := newDaemon(c.global.flagLogDebug, c.global.flagLogVerbose, c.global.flagSecretsLocation)
|
||||
|
||||
// Load the agent configuration.
|
||||
@ -141,34 +138,32 @@ func (c *cmdAgent) run(cmd *cobra.Command, args []string) error {
|
||||
c.mountHostShares()
|
||||
}
|
||||
|
||||
if canStartServer {
|
||||
// Start the server.
|
||||
err = startHTTPServer(d, c.global.flagLogDebug)
|
||||
// Start the server.
|
||||
err = startHTTPServer(d, c.global.flagLogDebug)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to start HTTP server: %w", err)
|
||||
}
|
||||
|
||||
// Check whether we should start the DevIncus server in the early setup. This way, /dev/incus/sock
|
||||
// will be available for any systemd services starting after the agent.
|
||||
if util.PathExists("agent.conf") {
|
||||
f, err := os.Open("agent.conf")
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to start HTTP server: %w", err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Check whether we should start the DevIncus server in the early setup. This way, /dev/incus/sock
|
||||
// will be available for any systemd services starting after the agent.
|
||||
if util.PathExists("agent.conf") {
|
||||
f, err := os.Open("agent.conf")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = setConnectionInfo(d, f)
|
||||
if err != nil {
|
||||
_ = f.Close()
|
||||
return err
|
||||
}
|
||||
|
||||
err = setConnectionInfo(d, f)
|
||||
if err != nil {
|
||||
_ = f.Close()
|
||||
return err
|
||||
}
|
||||
|
||||
if d.DevIncusEnabled {
|
||||
err = startDevIncusServer(d)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_ = f.Close()
|
||||
|
||||
if d.DevIncusEnabled {
|
||||
err = startDevIncusServer(d)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -214,24 +209,8 @@ func (c *cmdAgent) run(cmd *cobra.Command, args []string) error {
|
||||
// startStatusNotifier sends status of agent to vserial ring buffer every 10s or when context is done.
|
||||
// Returns a function that can be used to update the running status to STOPPED in the ring buffer.
|
||||
func (c *cmdAgent) startStatusNotifier(ctx context.Context, chConnected <-chan struct{}) context.CancelFunc {
|
||||
msgWithState := func(status string) []string {
|
||||
msg := []string{status}
|
||||
|
||||
// If there's no server running, send state data through the ring-buffer.
|
||||
_, ok := servers["http"]
|
||||
if !ok {
|
||||
b, err := json.Marshal(renderState())
|
||||
if err == nil {
|
||||
logger.Error("Including VM state in status message")
|
||||
msg = append(msg, string(b))
|
||||
}
|
||||
}
|
||||
|
||||
return msg
|
||||
}
|
||||
|
||||
// Write initial started status.
|
||||
_ = c.writeStatus(msgWithState("STARTED")...)
|
||||
_ = c.writeStatus("STARTED")
|
||||
|
||||
wg := sync.WaitGroup{}
|
||||
exitCtx, exit := context.WithCancel(ctx) // Allows manual synchronous cancellation via cancel function.
|
||||
@ -240,7 +219,10 @@ func (c *cmdAgent) startStatusNotifier(ctx context.Context, chConnected <-chan s
|
||||
wg.Wait() // Wait for the go routine to actually finish.
|
||||
}
|
||||
|
||||
wg.Go(func() {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done() // Signal to cancel function that we are done.
|
||||
|
||||
ticker := time.NewTicker(time.Duration(time.Second) * 5)
|
||||
defer ticker.Stop()
|
||||
|
||||
@ -249,28 +231,28 @@ func (c *cmdAgent) startStatusNotifier(ctx context.Context, chConnected <-chan s
|
||||
case <-chConnected:
|
||||
_ = c.writeStatus("CONNECTED") // Indicate we were able to connect.
|
||||
case <-ticker.C:
|
||||
_ = c.writeStatus(msgWithState("STARTED")...) // Re-populate status periodically in case the daemon restarts.
|
||||
_ = c.writeStatus("STARTED") // Re-populate status periodically in case the daemon restarts.
|
||||
case <-exitCtx.Done():
|
||||
_ = c.writeStatus("STOPPED") // Indicate we are stopping and exit go routine.
|
||||
return
|
||||
}
|
||||
}
|
||||
})
|
||||
}()
|
||||
|
||||
return cancel
|
||||
}
|
||||
|
||||
// writeStatus writes a status code to the vserial ring buffer used to detect agent status on host.
|
||||
func (c *cmdAgent) writeStatus(status ...string) error {
|
||||
func (c *cmdAgent) writeStatus(status string) error {
|
||||
if util.PathExists(osVioSerialPath) {
|
||||
vSerial, err := os.OpenFile(osVioSerialPath, os.O_RDWR, 0o600)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer logger.WarnOnError(vSerial.Close, "Failed to close vserial device")
|
||||
defer vSerial.Close()
|
||||
|
||||
_, err = vSerial.Write([]byte(strings.Join(status, "\n") + "\n"))
|
||||
_, err = vSerial.Write(fmt.Appendf(nil, "%s\n", status))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -27,6 +27,11 @@ var (
|
||||
osGuestAPISupport = false
|
||||
)
|
||||
|
||||
func osLoadModules() error {
|
||||
// No OS drivers to load by default.
|
||||
return nil
|
||||
}
|
||||
|
||||
func osGetCPUMetrics(d *Daemon) ([]metrics.CPUMetrics, error) {
|
||||
cpuTimes, err := cpu.Times(true)
|
||||
if err != nil {
|
||||
|
||||
@ -22,11 +22,6 @@ import (
|
||||
"github.com/lxc/incus/v7/shared/subprocess"
|
||||
)
|
||||
|
||||
func osLoadModules() error {
|
||||
// No OS drivers to load by default.
|
||||
return nil
|
||||
}
|
||||
|
||||
func osMountShared(src string, dst string, fstype string, opts []string) error {
|
||||
if fstype != "9p" {
|
||||
return errors.New("Only 9p shares are supported on Darwin")
|
||||
|
||||
@ -25,11 +25,6 @@ import (
|
||||
"github.com/lxc/incus/v7/shared/util"
|
||||
)
|
||||
|
||||
func osLoadModules() error {
|
||||
// No OS drivers to load by default.
|
||||
return nil
|
||||
}
|
||||
|
||||
// isMountPoint returns true if path is a mount point.
|
||||
func isMountPoint(path string) bool {
|
||||
// Get the stat details.
|
||||
|
||||
@ -22,7 +22,6 @@ import (
|
||||
"golang.org/x/sys/windows/svc"
|
||||
"golang.org/x/sys/windows/svc/debug"
|
||||
"golang.org/x/sys/windows/svc/eventlog"
|
||||
"golang.org/x/sys/windows/svc/mgr"
|
||||
|
||||
"github.com/lxc/incus/v7/internal/ports"
|
||||
"github.com/lxc/incus/v7/internal/server/metrics"
|
||||
@ -38,62 +37,6 @@ var (
|
||||
osVioSerialPath = `\\.\org.linuxcontainers.incus`
|
||||
)
|
||||
|
||||
// Check for the VirtioSocketWSP service for vsock support.
|
||||
func osLoadModules() error {
|
||||
m, err := mgr.Connect()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer m.Disconnect()
|
||||
|
||||
viosockSvc := "VirtioSocketWSP"
|
||||
s, err := m.OpenService(viosockSvc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer s.Close()
|
||||
|
||||
tryStart := func() (bool, error) {
|
||||
status, err := s.Query()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if status.State == svc.Stopped {
|
||||
err = s.Start()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
|
||||
return status.State == svc.Running, nil
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.TODO(), time.Second*5)
|
||||
defer cancel()
|
||||
|
||||
// Try for 5s to start the service.
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return fmt.Errorf("Unable to start viosock service: %w", ctx.Err())
|
||||
default:
|
||||
running, err := tryStart()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if running {
|
||||
return nil
|
||||
}
|
||||
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func osGetListener(port int64) (net.Listener, error) {
|
||||
const CIDAny uint32 = 4294967295 // Equivalent to VMADDR_CID_ANY.
|
||||
|
||||
@ -129,14 +72,11 @@ func (m *incusAgentService) Execute(args []string, r <-chan svc.ChangeRequest, c
|
||||
d := newDaemon(m.agentCmd.global.flagLogDebug, m.agentCmd.global.flagLogVerbose, m.agentCmd.global.flagSecretsLocation)
|
||||
|
||||
// Start the server.
|
||||
err := osLoadModules()
|
||||
if err == nil {
|
||||
err := startHTTPServer(d, m.agentCmd.global.flagLogDebug)
|
||||
if err != nil {
|
||||
changes <- svc.Status{State: svc.StopPending}
|
||||
elog.Error(1, fmt.Sprintf("Failed to start HTTP server: %s", err))
|
||||
return
|
||||
}
|
||||
err := startHTTPServer(d, m.agentCmd.global.flagLogDebug)
|
||||
if err != nil {
|
||||
changes <- svc.Status{State: svc.StopPending}
|
||||
elog.Error(1, fmt.Sprintf("Failed to start HTTP server: %s", err))
|
||||
return
|
||||
}
|
||||
|
||||
changes <- svc.Status{State: svc.Running, Accepts: cmdsAccepted}
|
||||
@ -181,7 +121,7 @@ func runService(name string, agentCmd *cmdAgent) error {
|
||||
}
|
||||
}
|
||||
|
||||
defer logger.WarnOnError(elog.Close, "Failed to close event log")
|
||||
defer elog.Close()
|
||||
|
||||
elog.Info(1, fmt.Sprintf("Starting %s service", name))
|
||||
run := svc.Run
|
||||
@ -264,7 +204,7 @@ func osGetOSState() *api.InstanceStateOSInfo {
|
||||
return nil
|
||||
}
|
||||
|
||||
defer logger.WarnOnError(k.Close, "Failed to close registry key")
|
||||
defer k.Close()
|
||||
|
||||
// Get local hostname.
|
||||
hostname, err := os.Hostname()
|
||||
|
||||
@ -1,54 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/lxc/incus/v7/internal/server/response"
|
||||
"github.com/lxc/incus/v7/shared/api"
|
||||
)
|
||||
|
||||
var portForwardCmd = APIEndpoint{
|
||||
Name: "port-forward",
|
||||
Path: "port-forward",
|
||||
|
||||
Post: APIEndpointAction{Handler: portForwardHandler},
|
||||
}
|
||||
|
||||
func portForwardHandler(d *Daemon, r *http.Request) response.Response {
|
||||
if d.Features != nil && !d.Features["port-forward"] {
|
||||
return response.Forbidden(errors.New("Port forwarding is disabled by configuration"))
|
||||
}
|
||||
|
||||
if r.Header.Get("Upgrade") != "tcp" {
|
||||
return response.BadRequest(errors.New("Missing or invalid upgrade header"))
|
||||
}
|
||||
|
||||
// Parse the request.
|
||||
req := api.InstancePortForwardPost{}
|
||||
|
||||
err := json.NewDecoder(r.Body).Decode(&req)
|
||||
if err != nil {
|
||||
return response.BadRequest(err)
|
||||
}
|
||||
|
||||
if req.Address == "" {
|
||||
req.Address = "127.0.0.1"
|
||||
}
|
||||
|
||||
if req.Port <= 0 || req.Port > 65535 {
|
||||
return response.BadRequest(fmt.Errorf("Invalid port %d", req.Port))
|
||||
}
|
||||
|
||||
// Connect to the target.
|
||||
conn, err := net.Dial("tcp", net.JoinHostPort(req.Address, strconv.Itoa(req.Port)))
|
||||
if err != nil {
|
||||
return response.InternalError(fmt.Errorf("Failed connecting to %q port %d: %w", req.Address, req.Port, err))
|
||||
}
|
||||
|
||||
return response.UpgradeResponse(r, conn, "tcp", nil)
|
||||
}
|
||||
@ -8,7 +8,6 @@ import (
|
||||
"github.com/pkg/sftp"
|
||||
|
||||
"github.com/lxc/incus/v7/internal/server/response"
|
||||
"github.com/lxc/incus/v7/shared/logger"
|
||||
)
|
||||
|
||||
var sftpCmd = APIEndpoint{
|
||||
@ -61,7 +60,7 @@ func (r *sftpServe) Render(w http.ResponseWriter) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
defer logger.WarnOnError(conn.Close, "Failed to close connection")
|
||||
defer func() { _ = conn.Close() }()
|
||||
|
||||
err = response.Upgrade(conn, "sftp")
|
||||
if err != nil {
|
||||
@ -71,7 +70,7 @@ func (r *sftpServe) Render(w http.ResponseWriter) error {
|
||||
}
|
||||
|
||||
// Start sftp server.
|
||||
server, err := sftp.NewServer(conn, sftp.WithAllocator(), sftp.WithServerWorkingDirectory(osBaseWorkingDirectory), sftp.WithMaxTxPacket(128*1024))
|
||||
server, err := sftp.NewServer(conn, sftp.WithAllocator(), sftp.WithServerWorkingDirectory(osBaseWorkingDirectory))
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -10,7 +10,6 @@ import (
|
||||
"go.yaml.in/yaml/v4"
|
||||
|
||||
"github.com/lxc/incus/v7/shared/api"
|
||||
"github.com/lxc/incus/v7/shared/logger"
|
||||
"github.com/lxc/incus/v7/shared/util"
|
||||
)
|
||||
|
||||
@ -115,7 +114,7 @@ func templatesApply(path string) ([]string, error) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
defer logger.WarnOnError(w.Close, "Failed to close file")
|
||||
defer func() { _ = w.Close() }()
|
||||
|
||||
// Do the copy.
|
||||
src, err := os.Open(filePath)
|
||||
@ -123,7 +122,7 @@ func templatesApply(path string) ([]string, error) {
|
||||
return err
|
||||
}
|
||||
|
||||
defer logger.WarnOnError(src.Close, "Failed to close source file")
|
||||
defer func() { _ = src.Close() }()
|
||||
|
||||
_, err = util.SafeCopy(w, src)
|
||||
if err != nil {
|
||||
|
||||
@ -33,8 +33,7 @@ func createContainer(c incus.InstanceServer, fingerprint string, name string, pr
|
||||
|
||||
func startContainer(c incus.InstanceServer, name string) error {
|
||||
op, err := c.UpdateInstanceState(
|
||||
name, api.InstanceStatePut{Action: "start", Timeout: -1}, "",
|
||||
)
|
||||
name, api.InstanceStatePut{Action: "start", Timeout: -1}, "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -44,8 +43,7 @@ func startContainer(c incus.InstanceServer, name string) error {
|
||||
|
||||
func stopContainer(c incus.InstanceServer, name string) error {
|
||||
op, err := c.UpdateInstanceState(
|
||||
name, api.InstanceStatePut{Action: "stop", Timeout: -1, Force: true}, "",
|
||||
)
|
||||
name, api.InstanceStatePut{Action: "stop", Timeout: -1, Force: true}, "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -55,8 +53,7 @@ func stopContainer(c incus.InstanceServer, name string) error {
|
||||
|
||||
func freezeContainer(c incus.InstanceServer, name string) error {
|
||||
op, err := c.UpdateInstanceState(
|
||||
name, api.InstanceStatePut{Action: "freeze", Timeout: -1}, "",
|
||||
)
|
||||
name, api.InstanceStatePut{Action: "freeze", Timeout: -1}, "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -6,8 +6,6 @@ import (
|
||||
"io"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/lxc/incus/v7/shared/logger"
|
||||
)
|
||||
|
||||
// Subset of JMeter CSV log format that are required by Jenkins performance
|
||||
@ -35,7 +33,7 @@ func (r *CSVReport) load() error {
|
||||
return err
|
||||
}
|
||||
|
||||
defer logger.WarnOnError(file.Close, "Failed to close file")
|
||||
defer func() { _ = file.Close() }()
|
||||
|
||||
reader := csv.NewReader(file)
|
||||
for line := 1; err != io.EOF; line++ {
|
||||
@ -62,7 +60,7 @@ func (r *CSVReport) write() error {
|
||||
return err
|
||||
}
|
||||
|
||||
defer logger.WarnOnError(file.Close, "Failed to close file")
|
||||
defer func() { _ = file.Close() }()
|
||||
|
||||
writer := csv.NewWriter(file)
|
||||
err = writer.WriteAll(r.records)
|
||||
|
||||
@ -24,7 +24,6 @@ import (
|
||||
"github.com/lxc/incus/v7/shared/api"
|
||||
"github.com/lxc/incus/v7/shared/archive"
|
||||
"github.com/lxc/incus/v7/shared/ask"
|
||||
"github.com/lxc/incus/v7/shared/logger"
|
||||
localtls "github.com/lxc/incus/v7/shared/tls"
|
||||
"github.com/lxc/incus/v7/shared/util"
|
||||
)
|
||||
@ -248,7 +247,7 @@ func (m *Migration) askPath(question string) (string, error) {
|
||||
return "", err
|
||||
}
|
||||
|
||||
defer logger.WarnOnError(f.Close, "Failed to close file")
|
||||
defer func() { _ = f.Close() }()
|
||||
|
||||
// Download the target.
|
||||
resp, err := http.Get(path)
|
||||
@ -256,7 +255,7 @@ func (m *Migration) askPath(question string) (string, error) {
|
||||
return "", err
|
||||
}
|
||||
|
||||
defer logger.WarnOnError(resp.Body.Close, "Failed to close response body")
|
||||
defer resp.Body.Close()
|
||||
|
||||
fmt.Printf("Downloading %q\n", path)
|
||||
|
||||
@ -504,7 +503,7 @@ func (c *cmdMigrate) run(_ *cobra.Command, _ []string) error {
|
||||
}()
|
||||
|
||||
if clientFingerprint != "" {
|
||||
defer logger.WarnOnError(func() error { return server.DeleteCertificate(clientFingerprint) }, "Failed to delete certificate")
|
||||
defer func() { _ = server.DeleteCertificate(clientFingerprint) }()
|
||||
}
|
||||
|
||||
// Provide migration type
|
||||
|
||||
@ -53,11 +53,13 @@ func (c *cmdNetcat) run(cmd *cobra.Command, args []string) error {
|
||||
|
||||
// We'll wait until we're done reading from the socket
|
||||
wg := sync.WaitGroup{}
|
||||
wg.Add(1)
|
||||
|
||||
wg.Go(func() {
|
||||
go func() {
|
||||
_, err = util.SafeCopy(eagain.Writer{Writer: os.Stdout}, eagain.Reader{Reader: conn})
|
||||
_ = conn.Close()
|
||||
})
|
||||
wg.Done()
|
||||
}()
|
||||
|
||||
go func() {
|
||||
_, _ = util.SafeCopy(eagain.Writer{Writer: conn}, eagain.Reader{Reader: os.Stdin})
|
||||
|
||||
@ -336,7 +336,7 @@ func (m *InstanceMigration) render() string {
|
||||
data.Disks[k] = v
|
||||
}
|
||||
|
||||
out, err := yaml.Dump(&data, yaml.WithV2Defaults())
|
||||
out, err := yaml.Dump(&data, yaml.V2)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
@ -16,7 +16,6 @@ import (
|
||||
"github.com/lxc/incus/v7/shared/api"
|
||||
"github.com/lxc/incus/v7/shared/archive"
|
||||
"github.com/lxc/incus/v7/shared/ask"
|
||||
"github.com/lxc/incus/v7/shared/logger"
|
||||
"github.com/lxc/incus/v7/shared/util"
|
||||
)
|
||||
|
||||
@ -227,7 +226,7 @@ func (m *OVAMigration) migrate() error {
|
||||
return err
|
||||
}
|
||||
|
||||
defer logger.WarnOnError(func() error { return os.RemoveAll(outputPath) }, "Failed to remove temporary directory")
|
||||
defer func() { _ = os.RemoveAll(outputPath) }()
|
||||
|
||||
err = m.unpackOVA(outputPath)
|
||||
if err != nil {
|
||||
@ -270,7 +269,7 @@ func (m *OVAMigration) unpackOVA(outPath string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
defer logger.WarnOnError(file.Close, "Failed to close file")
|
||||
defer file.Close()
|
||||
|
||||
tarReader := tar.NewReader(file)
|
||||
|
||||
@ -279,7 +278,7 @@ func (m *OVAMigration) unpackOVA(outPath string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
defer logger.WarnOnError(outPathRoot.Close, "Failed to close directory")
|
||||
defer func() { _ = outPathRoot.Close() }()
|
||||
|
||||
for {
|
||||
header, err := tarReader.Next()
|
||||
@ -315,7 +314,7 @@ func (m *OVAMigration) readOVA() error {
|
||||
return err
|
||||
}
|
||||
|
||||
defer logger.WarnOnError(file.Close, "Failed to close file")
|
||||
defer file.Close()
|
||||
|
||||
tarReader := tar.NewReader(file)
|
||||
|
||||
|
||||
@ -244,7 +244,7 @@ func (m *VolumeMigration) render() string {
|
||||
m.sourceFormat,
|
||||
}
|
||||
|
||||
out, err := yaml.Dump(&data, yaml.WithV2Defaults())
|
||||
out, err := yaml.Dump(&data, yaml.V2)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
@ -14,7 +14,6 @@ import (
|
||||
|
||||
"github.com/lxc/incus/v7/internal/linux"
|
||||
"github.com/lxc/incus/v7/internal/migration"
|
||||
"github.com/lxc/incus/v7/shared/logger"
|
||||
"github.com/lxc/incus/v7/shared/util"
|
||||
"github.com/lxc/incus/v7/shared/ws"
|
||||
)
|
||||
@ -27,7 +26,7 @@ func rsyncSend(ctx context.Context, conn *websocket.Conn, path string, rsyncArgs
|
||||
}
|
||||
|
||||
if dataSocket != nil {
|
||||
defer logger.WarnOnError(dataSocket.Close, "Failed to close connection")
|
||||
defer func() { _ = dataSocket.Close() }()
|
||||
}
|
||||
|
||||
readDone, writeDone := ws.Mirror(conn, dataSocket)
|
||||
|
||||
@ -22,7 +22,6 @@ import (
|
||||
internalUtil "github.com/lxc/incus/v7/internal/util"
|
||||
"github.com/lxc/incus/v7/internal/version"
|
||||
"github.com/lxc/incus/v7/shared/api"
|
||||
"github.com/lxc/incus/v7/shared/logger"
|
||||
localtls "github.com/lxc/incus/v7/shared/tls"
|
||||
"github.com/lxc/incus/v7/shared/util"
|
||||
"github.com/lxc/incus/v7/shared/ws"
|
||||
@ -127,7 +126,7 @@ func transferRootfs(ctx context.Context, op incus.Operation, rootfs string, rsyn
|
||||
return abort(err)
|
||||
}
|
||||
|
||||
defer logger.WarnOnError(f.Close, "Failed to close file")
|
||||
defer func() { _ = f.Close() }()
|
||||
|
||||
conn := ws.NewWrapper(wsFs)
|
||||
|
||||
@ -354,7 +353,7 @@ func blockDiskSizeBytes(blockDiskPath string) (int64, error) {
|
||||
return -1, err
|
||||
}
|
||||
|
||||
defer logger.WarnOnError(f.Close, "Failed to close file")
|
||||
defer func() { _ = f.Close() }()
|
||||
fd := int(f.Fd())
|
||||
|
||||
// Retrieve the block device size.
|
||||
|
||||
@ -20,7 +20,6 @@ import (
|
||||
"github.com/lxc/incus/v7/shared/api"
|
||||
"github.com/lxc/incus/v7/shared/archive"
|
||||
cli "github.com/lxc/incus/v7/shared/cmd"
|
||||
"github.com/lxc/incus/v7/shared/logger"
|
||||
"github.com/lxc/incus/v7/shared/osarch"
|
||||
"github.com/lxc/incus/v7/shared/simplestreams"
|
||||
"github.com/lxc/incus/v7/shared/util"
|
||||
@ -102,8 +101,6 @@ func (c *cmdAdd) parseImage(metaFile *os.File, dataFile *os.File) (*dataItem, er
|
||||
item.FileType = "squashfs"
|
||||
case ".qcow2":
|
||||
item.FileType = "disk-kvm.img"
|
||||
case ".tar.xz":
|
||||
item.FileType = "root.tar.xz"
|
||||
default:
|
||||
return nil, fmt.Errorf("Unsupported data type %q", item.Extension)
|
||||
}
|
||||
@ -172,7 +169,7 @@ func (c *cmdAdd) run(cmd *cobra.Command, args []string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
defer logger.WarnOnError(metaFile.Close, "Failed to close file")
|
||||
defer metaFile.Close()
|
||||
|
||||
// Read the header.
|
||||
_, _, unpacker, err := archive.DetectCompressionFile(metaFile)
|
||||
@ -269,7 +266,6 @@ func (c *cmdAdd) run(cmd *cobra.Command, args []string) error {
|
||||
}
|
||||
|
||||
var data *dataItem
|
||||
imageType := "container"
|
||||
if !isUnifiedTarball {
|
||||
// Open the data.
|
||||
dataFile, err := os.Open(args[1])
|
||||
@ -277,47 +273,13 @@ func (c *cmdAdd) run(cmd *cobra.Command, args []string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
defer logger.WarnOnError(dataFile.Close, "Failed to close file")
|
||||
defer dataFile.Close()
|
||||
|
||||
// Parse the content.
|
||||
data, err = c.parseImage(metaFile, dataFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
// Detect the image type by looking for a VM disk in the unified tarball.
|
||||
_, err = metaFile.Seek(0, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
typeTar, typeTarCancel, err := archive.CompressedTarReader(context.Background(), metaFile, unpacker, "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer typeTarCancel()
|
||||
|
||||
for {
|
||||
hdr, err = typeTar.Next()
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
if hdr.Name == "rootfs" || hdr.Name == "./rootfs" {
|
||||
imageType = "container"
|
||||
break
|
||||
}
|
||||
|
||||
if hdr.Name == "rootfs.img" || hdr.Name == "./rootfs.img" {
|
||||
imageType = "virtual-machine"
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create the paths if missing.
|
||||
@ -400,19 +362,6 @@ func (c *cmdAdd) run(cmd *cobra.Command, args []string) error {
|
||||
metaTargetPath = fmt.Sprintf("images/%s.incus_combined.tar.gz", metaSha256)
|
||||
}
|
||||
|
||||
// Prepare the metadata entry.
|
||||
metaVersionItem := simplestreams.ProductVersionItem{
|
||||
FileType: fileType,
|
||||
HashSha256: metaSha256,
|
||||
Size: metaSize,
|
||||
Path: metaTargetPath,
|
||||
}
|
||||
|
||||
if isUnifiedTarball {
|
||||
// Set combined_type for unified images.
|
||||
metaVersionItem.CombinedType = imageType
|
||||
}
|
||||
|
||||
// Check if a version already exists.
|
||||
versionName := time.Unix(metadata.CreationDate, 0).Format("200601021504")
|
||||
version, ok := product.Versions[versionName]
|
||||
@ -420,7 +369,12 @@ func (c *cmdAdd) run(cmd *cobra.Command, args []string) error {
|
||||
// Create a new version.
|
||||
version = simplestreams.ProductVersion{
|
||||
Items: map[string]simplestreams.ProductVersionItem{
|
||||
fileKey: metaVersionItem,
|
||||
fileKey: {
|
||||
FileType: fileType,
|
||||
HashSha256: metaSha256,
|
||||
Size: metaSize,
|
||||
Path: metaTargetPath,
|
||||
},
|
||||
},
|
||||
}
|
||||
} else {
|
||||
@ -428,7 +382,12 @@ func (c *cmdAdd) run(cmd *cobra.Command, args []string) error {
|
||||
_, ok := version.Items[fileKey]
|
||||
if !ok {
|
||||
// No fileKey found, add it.
|
||||
version.Items[fileKey] = metaVersionItem
|
||||
version.Items[fileKey] = simplestreams.ProductVersionItem{
|
||||
FileType: fileType,
|
||||
HashSha256: metaSha256,
|
||||
Size: metaSize,
|
||||
Path: metaTargetPath,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -462,8 +421,6 @@ func (c *cmdAdd) run(cmd *cobra.Command, args []string) error {
|
||||
metaItem.CombinedSha256SquashFs = data.combinedSha256
|
||||
case "disk-kvm.img":
|
||||
metaItem.CombinedSha256DiskKvmImg = data.combinedSha256
|
||||
case "root.tar.xz":
|
||||
metaItem.CombinedSha256RootXz = data.combinedSha256
|
||||
}
|
||||
|
||||
version.Items["incus.tar.xz"] = metaItem
|
||||
|
||||
@ -15,7 +15,6 @@ import (
|
||||
"github.com/lxc/incus/v7/shared/api"
|
||||
"github.com/lxc/incus/v7/shared/ask"
|
||||
cli "github.com/lxc/incus/v7/shared/cmd"
|
||||
"github.com/lxc/incus/v7/shared/logger"
|
||||
"github.com/lxc/incus/v7/shared/osarch"
|
||||
)
|
||||
|
||||
@ -60,7 +59,7 @@ func (c *cmdGenerateMetadata) run(cmd *cobra.Command, args []string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
defer logger.WarnOnError(metaFile.Close, "Failed to close file")
|
||||
defer metaFile.Close()
|
||||
|
||||
// Generate the metadata.
|
||||
timestamp := time.Now().UTC()
|
||||
@ -125,7 +124,7 @@ func (c *cmdGenerateMetadata) run(cmd *cobra.Command, args []string) error {
|
||||
metadata.Properties["description"] = metaDescription
|
||||
|
||||
// Generate YAML.
|
||||
body, err := yaml.Dump(&metadata, yaml.WithV2Defaults())
|
||||
body, err := yaml.Dump(&metadata, yaml.V2)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -155,7 +155,8 @@ func (c *cmdPrune) prune() error {
|
||||
|
||||
updatedVersions := map[string]simplestreams.ProductVersion{}
|
||||
iteration := 0
|
||||
for _, version := range slices.Backward(versionNames) {
|
||||
for i := len(versionNames) - 1; i >= 0; i-- {
|
||||
version := versionNames[i]
|
||||
if iteration <= c.flagRetention {
|
||||
updatedVersions[version] = product.Versions[version]
|
||||
} else if c.flagVerbose {
|
||||
|
||||
@ -96,7 +96,7 @@ func (c *cmdRemove) run(cmd *cobra.Command, args []string) error {
|
||||
delete(version.Items, "disk-kvm.img")
|
||||
metaEntry.CombinedSha256DiskKvmImg = ""
|
||||
} else if metaEntry.CombinedSha256SquashFs == image.Fingerprint {
|
||||
// Deleting a squashfs container image.
|
||||
// Deleting a container image.
|
||||
err = c.remove(version.Items["squashfs"].Path)
|
||||
if err != nil && !errors.Is(err, fs.ErrNotExist) {
|
||||
return err
|
||||
@ -104,15 +104,6 @@ func (c *cmdRemove) run(cmd *cobra.Command, args []string) error {
|
||||
|
||||
delete(version.Items, "squashfs")
|
||||
metaEntry.CombinedSha256SquashFs = ""
|
||||
} else if metaEntry.CombinedSha256RootXz == image.Fingerprint {
|
||||
// Deleting a tarball container image.
|
||||
err = c.remove(version.Items["root.tar.xz"].Path)
|
||||
if err != nil && !errors.Is(err, fs.ErrNotExist) {
|
||||
return err
|
||||
}
|
||||
|
||||
delete(version.Items, "root.tar.xz")
|
||||
metaEntry.CombinedSha256RootXz = ""
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
|
||||
@ -11,7 +11,6 @@ import (
|
||||
|
||||
"github.com/lxc/incus/v7/internal/linux"
|
||||
internalUtil "github.com/lxc/incus/v7/internal/util"
|
||||
incusLogger "github.com/lxc/incus/v7/shared/logger"
|
||||
localtls "github.com/lxc/incus/v7/shared/tls"
|
||||
"github.com/lxc/incus/v7/shared/util"
|
||||
)
|
||||
@ -104,7 +103,7 @@ func proxyConnection(conn *net.UnixConn, serverUnixPath string) {
|
||||
return
|
||||
}
|
||||
|
||||
defer incusLogger.WarnOnError(client.Close, "Failed to close connection")
|
||||
defer func() { _ = client.Close() }()
|
||||
|
||||
// Get the TLS configuration
|
||||
tlsConfig, err := tlsConfig(creds.Uid)
|
||||
|
||||
@ -163,7 +163,7 @@ func (c *cmdAction) command(action string) *cobra.Command {
|
||||
|
||||
if slices.Contains([]string{"restart", "stop"}, action) {
|
||||
cli.AddBoolFlag(cmd.Flags(), &c.flagForce, "force|f", i18n.G("Force the instance to stop"))
|
||||
cli.AddIntFlag(cmd.Flags(), &c.flagTimeout, "timeout", i18n.G("Time to wait for the instance to shutdown cleanly"), -1)
|
||||
cli.AddIntFlag(cmd.Flags(), &c.flagTimeout, "timeout", -1, i18n.G("Time to wait for the instance to shutdown cleanly"))
|
||||
}
|
||||
|
||||
return cmd
|
||||
@ -280,7 +280,6 @@ func (c *cmdAction) doAction(action string, conf *config.Config, p *u.Parsed) er
|
||||
console := cmdConsole{}
|
||||
console.global = c.global
|
||||
console.flagType = c.flagConsole
|
||||
console.withLog = c.flagConsole == "console"
|
||||
return console.console(d, instanceName)
|
||||
}
|
||||
|
||||
@ -313,7 +312,6 @@ func (c *cmdAction) doAction(action string, conf *config.Config, p *u.Parsed) er
|
||||
console := cmdConsole{}
|
||||
console.global = c.global
|
||||
console.flagType = c.flagConsole
|
||||
console.withLog = c.flagConsole == "console"
|
||||
|
||||
consoleErr := console.console(d, instanceName)
|
||||
if consoleErr != nil {
|
||||
@ -337,7 +335,7 @@ func (c *cmdAction) doAction(action string, conf *config.Config, p *u.Parsed) er
|
||||
|
||||
// It handles actions on instances (single or all) and manages error handling, console flag restrictions, and batch operations.
|
||||
func (c *cmdAction) run(cmd *cobra.Command, args []string) error {
|
||||
parsed, err := c.global.Parse(cmdActionUsage, cmd, args)
|
||||
parsed, err := cmdActionUsage.Parse(c.global.conf, cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -44,10 +44,6 @@ func (c *cmdAdmin) command() *cobra.Command {
|
||||
sqlCmd := cmdAdminSQL{global: c.global}
|
||||
cmd.AddCommand(sqlCmd.command())
|
||||
|
||||
// update-certificate sub-command
|
||||
updateCertCmd := cmdAdminUpdateCertificate{global: c.global}
|
||||
cmd.AddCommand(updateCertCmd.command())
|
||||
|
||||
// waitready sub-command
|
||||
adminWaitreadyCmd := cmdAdminWaitready{global: c.global}
|
||||
cmd.AddCommand(adminWaitreadyCmd.command())
|
||||
|
||||
@ -24,8 +24,7 @@ func (c *cmdAdminCluster) command() *cobra.Command {
|
||||
cmd.Use = cli.U("cluster")
|
||||
cmd.Short = i18n.G("Low-level cluster administration commands")
|
||||
cmd.Long = cli.FormatSection(color.DescriptionPrefix, i18n.G(
|
||||
`Low level administration tools for inspecting and recovering clusters.`,
|
||||
))
|
||||
`Low level administration tools for inspecting and recovering clusters.`))
|
||||
|
||||
cmd.Run = c.run
|
||||
return cmd
|
||||
|
||||
@ -54,17 +54,17 @@ func (c *cmdAdminInit) command() *cobra.Command {
|
||||
cli.AddBoolFlag(cmd.Flags(), &c.flagDump, "dump", i18n.G("Dump YAML config to stdout"))
|
||||
|
||||
cli.AddStringFlag(cmd.Flags(), &c.flagNetworkAddress, "network-address", "", "", i18n.G("Address to bind to (default: none)"))
|
||||
cli.AddIntFlag(cmd.Flags(), &c.flagNetworkPort, "network-port", fmt.Sprintf(i18n.G("Port to bind to (default: %d)"), ports.HTTPSDefaultPort), -1)
|
||||
cli.AddIntFlag(cmd.Flags(), &c.flagNetworkPort, "network-port", -1, fmt.Sprintf(i18n.G("Port to bind to (default: %d)"), ports.HTTPSDefaultPort))
|
||||
cli.AddStringFlag(cmd.Flags(), &c.flagStorageBackend, "storage-backend", "", "", i18n.G("Storage backend to use (btrfs, dir, lvm or zfs, default: dir)"))
|
||||
cli.AddStringFlag(cmd.Flags(), &c.flagStorageDevice, "storage-create-device", "", "", i18n.G("Setup device based storage using DEVICE"))
|
||||
cli.AddIntFlag(cmd.Flags(), &c.flagStorageLoopSize, "storage-create-loop", i18n.G("Setup loop based storage with SIZE in GiB"), -1)
|
||||
cli.AddIntFlag(cmd.Flags(), &c.flagStorageLoopSize, "storage-create-loop", -1, i18n.G("Setup loop based storage with SIZE in GiB"))
|
||||
cli.AddStringFlag(cmd.Flags(), &c.flagStoragePool, "storage-pool", "", "", i18n.G("Storage pool to use or create"))
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c *cmdAdminInit) run(cmd *cobra.Command, args []string) error {
|
||||
parsed, err := c.global.Parse(cmdAdminInitUsage, cmd, args)
|
||||
parsed, err := cmdAdminInitUsage.Parse(c.global.conf, cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -88,7 +88,7 @@ func (c *cmdAdminInit) runDump(d incus.InstanceServer) error {
|
||||
config.Projects = append(config.Projects, projectsPost)
|
||||
}
|
||||
|
||||
out, err := yaml.Dump(config, yaml.WithV2Defaults())
|
||||
out, err := yaml.Dump(config, yaml.V2)
|
||||
if err != nil {
|
||||
return fmt.Errorf(i18n.G("Failed to retrieve current server configuration: %w"), err)
|
||||
}
|
||||
|
||||
@ -83,7 +83,7 @@ func (c *cmdAdminInit) runInteractive(_ *cobra.Command, d incus.InstanceServer,
|
||||
object = *config
|
||||
}
|
||||
|
||||
out, err := yaml.Dump(object, yaml.WithV2Defaults())
|
||||
out, err := yaml.Dump(object, yaml.V2)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(i18n.G("Failed to render the config: %w"), err)
|
||||
}
|
||||
@ -169,11 +169,11 @@ func (c *cmdAdminInit) askNetworking(config *api.InitPreseed, d incus.InstanceSe
|
||||
|
||||
// IPv4
|
||||
network.Config["ipv4.address"], err = c.global.asker.AskString(i18n.G("What IPv4 address should be used?")+" (CIDR subnet notation, “auto” or “none”) [default=auto]: ", "auto", func(value string) error {
|
||||
if slices.Contains([]string{"auto", "none", ""}, value) {
|
||||
if slices.Contains([]string{"auto", "none"}, value) {
|
||||
return nil
|
||||
}
|
||||
|
||||
return validate.IsNetworkAddressCIDRV4(value, false)
|
||||
return validate.Optional(validate.IsNetworkAddressCIDRV4)(value)
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
@ -190,11 +190,11 @@ func (c *cmdAdminInit) askNetworking(config *api.InitPreseed, d incus.InstanceSe
|
||||
|
||||
// IPv6
|
||||
network.Config["ipv6.address"], err = c.global.asker.AskString(i18n.G("What IPv6 address should be used?")+" (CIDR subnet notation, “auto” or “none”) [default=auto]: ", "auto", func(value string) error {
|
||||
if slices.Contains([]string{"auto", "none", ""}, value) {
|
||||
if slices.Contains([]string{"auto", "none"}, value) {
|
||||
return nil
|
||||
}
|
||||
|
||||
return validate.IsNetworkAddressCIDRV6(value, false)
|
||||
return validate.Optional(validate.IsNetworkAddressCIDRV6)(value)
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@ -19,8 +19,7 @@ func (c *cmdAdmin) command() *cobra.Command {
|
||||
cmd.Use = cli.U("admin")
|
||||
cmd.Short = i18n.G("Manage incus daemon")
|
||||
cmd.Long = cli.FormatSection(color.DescriptionPrefix, i18n.G(
|
||||
`Manage incus daemon`,
|
||||
))
|
||||
`Manage incus daemon`))
|
||||
|
||||
// os
|
||||
adminOSCmd := cmdAdminOS{global: c.global}
|
||||
|
||||
@ -39,7 +39,7 @@ func (c *cmdAdminRecover) command() *cobra.Command {
|
||||
}
|
||||
|
||||
func (c *cmdAdminRecover) run(cmd *cobra.Command, args []string) error {
|
||||
parsed, err := c.global.Parse(cmdAdminRecoverUsage, cmd, args)
|
||||
parsed, err := cmdAdminRecoverUsage.Parse(c.global.conf, cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -40,14 +40,14 @@ func (c *cmdAdminShutdown) command() *cobra.Command {
|
||||
This can take quite a while as instances can take a long time to
|
||||
shutdown, especially if a non-standard timeout was configured for them.`))
|
||||
cmd.RunE = c.run
|
||||
cli.AddIntFlag(cmd.Flags(), &c.flagTimeout, "timeout|t", "Number of seconds to wait before giving up", 0)
|
||||
cli.AddIntFlag(cmd.Flags(), &c.flagTimeout, "timeout|t", 0, "Number of seconds to wait before giving up")
|
||||
cli.AddBoolFlag(cmd.Flags(), &c.flagForce, "force|f", "Force shutdown instead of waiting for running operations to finish")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c *cmdAdminShutdown) run(cmd *cobra.Command, args []string) error {
|
||||
_, err := c.global.Parse(cmdAdminShutdownUsage, cmd, args)
|
||||
_, err := cmdAdminShutdownUsage.Parse(c.global.conf, cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -64,7 +64,7 @@ func (c *cmdAdminSQL) command() *cobra.Command {
|
||||
}
|
||||
|
||||
func (c *cmdAdminSQL) run(cmd *cobra.Command, args []string) error {
|
||||
parsed, err := c.global.Parse(cmdAdminSQLUsage, cmd, args)
|
||||
parsed, err := cmdAdminSQLUsage.Parse(c.global.conf, cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -1,110 +0,0 @@
|
||||
//go:build linux
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
incus "github.com/lxc/incus/v7/client"
|
||||
"github.com/lxc/incus/v7/cmd/incus/color"
|
||||
u "github.com/lxc/incus/v7/cmd/incus/usage"
|
||||
"github.com/lxc/incus/v7/internal/i18n"
|
||||
cli "github.com/lxc/incus/v7/shared/cmd"
|
||||
"github.com/lxc/incus/v7/shared/util"
|
||||
)
|
||||
|
||||
type cmdAdminUpdateCertificate struct {
|
||||
global *cmdGlobal
|
||||
}
|
||||
|
||||
var cmdAdminUpdateCertificateUsage = u.Usage{u.Placeholder(i18n.G("cert.crt")), u.Placeholder(i18n.G("cert.key"))}
|
||||
|
||||
func (c *cmdAdminUpdateCertificate) command() *cobra.Command {
|
||||
cmd := &cobra.Command{}
|
||||
cmd.Use = cli.U("update-certificate", cmdAdminUpdateCertificateUsage...)
|
||||
cmd.Aliases = []string{"update-cert"}
|
||||
cmd.Short = i18n.G("Update the server certificate")
|
||||
cmd.Long = cli.FormatSection(color.DescriptionPrefix, i18n.G(`Update the server certificate
|
||||
|
||||
This is only for standalone systems, cluster users should use "incus cluster update-certificate" instead".`))
|
||||
cmd.RunE = c.run
|
||||
|
||||
cmd.ValidArgsFunction = func(_ *cobra.Command, args []string, _ string) ([]string, cobra.ShellCompDirective) {
|
||||
if len(args) < 2 {
|
||||
return nil, cobra.ShellCompDirectiveDefault
|
||||
}
|
||||
|
||||
return nil, cobra.ShellCompDirectiveNoFileComp
|
||||
}
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c *cmdAdminUpdateCertificate) run(cmd *cobra.Command, args []string) error {
|
||||
parsed, err := c.global.Parse(cmdAdminUpdateCertificateUsage, cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
certFile := parsed[0].String
|
||||
keyFile := parsed[1].String
|
||||
|
||||
if !util.PathExists(certFile) {
|
||||
return fmt.Errorf(i18n.G("Could not find certificate file path: %s"), certFile)
|
||||
}
|
||||
|
||||
if !util.PathExists(keyFile) {
|
||||
return fmt.Errorf(i18n.G("Could not find certificate key file path: %s"), keyFile)
|
||||
}
|
||||
|
||||
cert, err := os.ReadFile(certFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf(i18n.G("Could not read certificate file: %s with error: %v"), certFile, err)
|
||||
}
|
||||
|
||||
key, err := os.ReadFile(keyFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf(i18n.G("Could not read certificate key file: %s with error: %v"), keyFile, err)
|
||||
}
|
||||
|
||||
connArgs := &incus.ConnectionArgs{
|
||||
SkipGetServer: true,
|
||||
}
|
||||
|
||||
d, err := incus.ConnectIncusUnix("", connArgs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
server, _, err := d.GetServer()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if server.Environment.ServerClustered {
|
||||
return errors.New(i18n.G("This command is not supported on clusters, use \"incus cluster update-certificate\" instead"))
|
||||
}
|
||||
|
||||
req := struct {
|
||||
Certificate string `json:"certificate"`
|
||||
Key string `json:"key"`
|
||||
}{
|
||||
Certificate: string(cert),
|
||||
Key: string(key),
|
||||
}
|
||||
|
||||
_, _, err = d.RawQuery("PUT", "/internal/server-certificate", req, "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !c.global.flagQuiet {
|
||||
fmt.Println(i18n.G("Successfully updated server certificate"))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@ -34,13 +34,13 @@ func (c *cmdAdminWaitready) command() *cobra.Command {
|
||||
is done with early start tasks like re-starting previously started
|
||||
containers.`))
|
||||
cmd.RunE = c.run
|
||||
cli.AddIntFlag(cmd.Flags(), &c.flagTimeout, "timeout|t", "Number of seconds to wait before giving up", 0)
|
||||
cli.AddIntFlag(cmd.Flags(), &c.flagTimeout, "timeout|t", 0, "Number of seconds to wait before giving up")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c *cmdAdminWaitready) run(cmd *cobra.Command, args []string) error {
|
||||
_, err := c.global.Parse(cmdAdminWaitreadyUsage, cmd, args)
|
||||
_, err := cmdAdminWaitreadyUsage.Parse(c.global.conf, cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -62,14 +62,7 @@ func (c *cmdAliasAdd) command() *cobra.Command {
|
||||
cmd.Long = cli.FormatSection(color.DescriptionPrefix, i18n.G(`Add new aliases`))
|
||||
cmd.Example = cli.FormatSection("", i18n.G(
|
||||
`incus alias add list "list -c ns46S"
|
||||
Overwrite the "list" command to pass -c ns46S.
|
||||
|
||||
incus alias add volume "storage volume @ARGS@"
|
||||
Create a short command for managing volumes
|
||||
|
||||
incus alias add cat "exec @ARG1@ -- cat @ARG2@"
|
||||
Create a command for displaying file content of instances`,
|
||||
))
|
||||
Overwrite the "list" command to pass -c ns46S.`))
|
||||
|
||||
cmd.RunE = c.run
|
||||
|
||||
@ -79,7 +72,7 @@ incus alias add cat "exec @ARG1@ -- cat @ARG2@"
|
||||
func (c *cmdAliasAdd) run(cmd *cobra.Command, args []string) error {
|
||||
conf := c.global.conf
|
||||
|
||||
parsed, err := c.global.Parse(cmdAliasAddUsage, cmd, args)
|
||||
parsed, err := cmdAliasAddUsage.Parse(conf, cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -130,7 +123,7 @@ func (c *cmdAliasList) run(cmd *cobra.Command, args []string) error {
|
||||
conf := c.global.conf
|
||||
|
||||
// Quick checks.
|
||||
_, err := c.global.Parse(cmdAliasListUsage, cmd, args)
|
||||
_, err := cmdAliasListUsage.Parse(c.global.conf, cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -175,8 +168,7 @@ func (c *cmdAliasRename) command() *cobra.Command {
|
||||
cmd.Long = cli.FormatSection(color.DescriptionPrefix, i18n.G(`Rename aliases`))
|
||||
cmd.Example = cli.FormatSection("", i18n.G(
|
||||
`incus alias rename list my-list
|
||||
Rename existing alias "list" to "my-list".`,
|
||||
))
|
||||
Rename existing alias "list" to "my-list".`))
|
||||
|
||||
cmd.RunE = c.run
|
||||
|
||||
@ -186,7 +178,7 @@ func (c *cmdAliasRename) command() *cobra.Command {
|
||||
func (c *cmdAliasRename) run(cmd *cobra.Command, args []string) error {
|
||||
conf := c.global.conf
|
||||
|
||||
parsed, err := c.global.Parse(cmdAliasRenameUsage, cmd, args)
|
||||
parsed, err := cmdAliasRenameUsage.Parse(c.global.conf, cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -230,8 +222,7 @@ func (c *cmdAliasRemove) command() *cobra.Command {
|
||||
cmd.Long = cli.FormatSection(color.DescriptionPrefix, i18n.G(`Remove aliases`))
|
||||
cmd.Example = cli.FormatSection("", i18n.G(
|
||||
`incus alias remove my-list
|
||||
Remove the "my-list" alias.`,
|
||||
))
|
||||
Remove the "my-list" alias.`))
|
||||
|
||||
cmd.RunE = c.run
|
||||
|
||||
@ -242,7 +233,7 @@ func (c *cmdAliasRemove) run(cmd *cobra.Command, args []string) error {
|
||||
conf := c.global.conf
|
||||
|
||||
// Quick checks.
|
||||
parsed, err := c.global.Parse(cmdAliasRemoveUsage, cmd, args)
|
||||
parsed, err := cmdAliasRemoveUsage.Parse(c.global.conf, cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -9,7 +9,6 @@ import (
|
||||
"maps"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"reflect"
|
||||
"slices"
|
||||
@ -166,8 +165,7 @@ func (c *cmdClusterList) command() *cobra.Command {
|
||||
f - Failure Domain
|
||||
d - Description
|
||||
s - Status
|
||||
m - Message`,
|
||||
))
|
||||
m - Message`))
|
||||
|
||||
cli.AddStringFlag(cmd.Flags(), &c.flagColumns, "columns|c", defaultClusterColumns, "", i18n.G("Columns"))
|
||||
cli.AddStringFlag(cmd.Flags(), &c.flagFormat, "format|f", c.global.defaultListFormat(), "", i18n.G(`Format (csv|json|table|yaml|compact|markdown), use suffix ",noheader" to disable headers and ",header" to enable it if missing, e.g. csv,header`))
|
||||
@ -265,7 +263,7 @@ func (c *cmdClusterList) messageColumnData(cluster api.ClusterMember) string {
|
||||
}
|
||||
|
||||
func (c *cmdClusterList) run(cmd *cobra.Command, args []string) error {
|
||||
parsed, err := c.global.Parse(cmdClusterListUsage, cmd, args)
|
||||
parsed, err := cmdClusterListUsage.Parse(c.global.conf, cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -349,7 +347,7 @@ func (c *cmdClusterShow) command() *cobra.Command {
|
||||
}
|
||||
|
||||
func (c *cmdClusterShow) run(cmd *cobra.Command, args []string) error {
|
||||
parsed, err := c.global.Parse(cmdClusterShowUsage, cmd, args)
|
||||
parsed, err := cmdClusterShowUsage.Parse(c.global.conf, cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -364,7 +362,7 @@ func (c *cmdClusterShow) run(cmd *cobra.Command, args []string) error {
|
||||
}
|
||||
|
||||
// Render as YAML
|
||||
data, err := yaml.Dump(&member, yaml.WithV2Defaults())
|
||||
data, err := yaml.Dump(&member, yaml.V2)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -386,8 +384,7 @@ func (c *cmdClusterInfo) command() *cobra.Command {
|
||||
cmd.Use = cli.U("info", cmdClusterInfoUsage...)
|
||||
cmd.Short = i18n.G("Show useful information about a cluster member")
|
||||
cmd.Long = cli.FormatSection(color.DescriptionPrefix, i18n.G(
|
||||
`Show useful information about a cluster member`,
|
||||
))
|
||||
`Show useful information about a cluster member`))
|
||||
|
||||
cmd.RunE = c.run
|
||||
|
||||
@ -403,7 +400,7 @@ func (c *cmdClusterInfo) command() *cobra.Command {
|
||||
}
|
||||
|
||||
func (c *cmdClusterInfo) run(cmd *cobra.Command, args []string) error {
|
||||
parsed, err := c.global.Parse(cmdClusterInfoUsage, cmd, args)
|
||||
parsed, err := cmdClusterInfoUsage.Parse(c.global.conf, cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -418,7 +415,7 @@ func (c *cmdClusterInfo) run(cmd *cobra.Command, args []string) error {
|
||||
}
|
||||
|
||||
// Render as YAML.
|
||||
data, err := yaml.Dump(&member, yaml.WithV2Defaults())
|
||||
data, err := yaml.Dump(&member, yaml.V2)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -462,7 +459,7 @@ func (c *cmdClusterGet) command() *cobra.Command {
|
||||
}
|
||||
|
||||
func (c *cmdClusterGet) run(cmd *cobra.Command, args []string) error {
|
||||
parsed, err := c.global.Parse(cmdClusterGetUsage, cmd, args)
|
||||
parsed, err := cmdClusterGetUsage.Parse(c.global.conf, cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -567,7 +564,7 @@ func (c *cmdClusterSet) set(cmd *cobra.Command, parsed []*u.Parsed) error {
|
||||
}
|
||||
|
||||
func (c *cmdClusterSet) run(cmd *cobra.Command, args []string) error {
|
||||
parsed, err := c.global.Parse(cmdClusterSetUsage, cmd, args)
|
||||
parsed, err := cmdClusterSetUsage.Parse(c.global.conf, cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -584,7 +581,7 @@ type cmdClusterUnset struct {
|
||||
flagIsProperty bool
|
||||
}
|
||||
|
||||
var cmdClusterUnsetUsage = u.Usage{u.Member.Remote(), u.Key.List(1)}
|
||||
var cmdClusterUnsetUsage = u.Usage{u.Member.Remote(), u.Key}
|
||||
|
||||
func (c *cmdClusterUnset) command() *cobra.Command {
|
||||
cmd := &cobra.Command{}
|
||||
@ -592,7 +589,7 @@ func (c *cmdClusterUnset) command() *cobra.Command {
|
||||
cmd.Short = i18n.G("Unset a cluster member's configuration keys")
|
||||
cmd.Long = cli.FormatSection(color.DescriptionPrefix, cmd.Short)
|
||||
|
||||
cli.AddBoolFlag(cmd.Flags(), &c.flagIsProperty, "property|p", i18n.G("Unset the keys as cluster properties"))
|
||||
cli.AddBoolFlag(cmd.Flags(), &c.flagIsProperty, "property|p", i18n.G("Unset the key as a cluster property"))
|
||||
cmd.RunE = c.run
|
||||
|
||||
cmd.ValidArgsFunction = func(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
@ -611,7 +608,7 @@ func (c *cmdClusterUnset) command() *cobra.Command {
|
||||
}
|
||||
|
||||
func (c *cmdClusterUnset) run(cmd *cobra.Command, args []string) error {
|
||||
parsed, err := c.global.Parse(cmdClusterUnsetUsage, cmd, args)
|
||||
parsed, err := cmdClusterUnsetUsage.Parse(c.global.conf, cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -649,7 +646,7 @@ func (c *cmdClusterRename) command() *cobra.Command {
|
||||
}
|
||||
|
||||
func (c *cmdClusterRename) run(cmd *cobra.Command, args []string) error {
|
||||
parsed, err := c.global.Parse(cmdClusterRenameUsage, cmd, args)
|
||||
parsed, err := cmdClusterRenameUsage.Parse(c.global.conf, cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -732,7 +729,7 @@ Are you really sure you want to force removing %s? (yes/no): `), formatRemote(c.
|
||||
}
|
||||
|
||||
func (c *cmdClusterRemove) run(cmd *cobra.Command, args []string) error {
|
||||
parsed, err := c.global.Parse(cmdClusterRemoveUsage, cmd, args)
|
||||
parsed, err := cmdClusterRemoveUsage.Parse(c.global.conf, cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -781,8 +778,7 @@ func (c *cmdClusterEnable) command() *cobra.Command {
|
||||
|
||||
It's required that the server is already available on the network. You can check
|
||||
that by running 'incus config get core.https_address', and possibly set a value
|
||||
for the address if not yet set.`,
|
||||
))
|
||||
for the address if not yet set.`))
|
||||
|
||||
cmd.RunE = c.run
|
||||
|
||||
@ -798,7 +794,7 @@ func (c *cmdClusterEnable) command() *cobra.Command {
|
||||
}
|
||||
|
||||
func (c *cmdClusterEnable) run(cmd *cobra.Command, args []string) error {
|
||||
parsed, err := c.global.Parse(cmdClusterEnableUsage, cmd, args)
|
||||
parsed, err := cmdClusterEnableUsage.Parse(c.global.conf, cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -857,12 +853,10 @@ func (c *cmdClusterEdit) command() *cobra.Command {
|
||||
cmd.Use = cli.U("edit", cmdClusterEditUsage...)
|
||||
cmd.Short = i18n.G("Edit cluster member configurations as YAML")
|
||||
cmd.Long = cli.FormatSection(color.DescriptionPrefix, i18n.G(
|
||||
`Edit cluster member configurations as YAML`,
|
||||
))
|
||||
`Edit cluster member configurations as YAML`))
|
||||
cmd.Example = cli.FormatSection("", i18n.G(
|
||||
`incus cluster edit <cluster member> < member.yaml
|
||||
Update a cluster member using the content of member.yaml`,
|
||||
))
|
||||
Update a cluster member using the content of member.yaml`))
|
||||
|
||||
cmd.RunE = c.run
|
||||
|
||||
@ -880,12 +874,11 @@ func (c *cmdClusterEdit) command() *cobra.Command {
|
||||
func (c *cmdClusterEdit) helpTemplate() string {
|
||||
return i18n.G(
|
||||
`### This is a YAML representation of the cluster member.
|
||||
### Any line starting with a '# will be ignored.`,
|
||||
)
|
||||
### Any line starting with a '# will be ignored.`)
|
||||
}
|
||||
|
||||
func (c *cmdClusterEdit) run(cmd *cobra.Command, args []string) error {
|
||||
parsed, err := c.global.Parse(cmdClusterEditUsage, cmd, args)
|
||||
parsed, err := cmdClusterEditUsage.Parse(c.global.conf, cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -917,7 +910,7 @@ func (c *cmdClusterEdit) run(cmd *cobra.Command, args []string) error {
|
||||
|
||||
memberWritable := member.Writable()
|
||||
|
||||
data, err := yaml.Dump(&memberWritable, yaml.WithV2Defaults())
|
||||
data, err := yaml.Dump(&memberWritable, yaml.V2)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -988,7 +981,7 @@ func (c *cmdClusterJoin) command() *cobra.Command {
|
||||
}
|
||||
|
||||
func (c *cmdClusterJoin) run(cmd *cobra.Command, args []string) error {
|
||||
parsed, err := c.global.Parse(cmdClusterJoinUsage, cmd, args)
|
||||
parsed, err := cmdClusterJoinUsage.Parse(c.global.conf, cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -1053,7 +1046,7 @@ func (c *cmdClusterAdd) command() *cobra.Command {
|
||||
}
|
||||
|
||||
func (c *cmdClusterAdd) run(cmd *cobra.Command, args []string) error {
|
||||
parsed, err := c.global.Parse(cmdClusterAddUsage, cmd, args)
|
||||
parsed, err := cmdClusterAddUsage.Parse(c.global.conf, cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -1113,7 +1106,7 @@ Default column layout: ntE
|
||||
|
||||
== Columns ==
|
||||
The -c option takes a comma separated list of arguments that control
|
||||
which join tokens attributes to output when displaying in table or csv
|
||||
which network zone attributes to output when displaying in table or csv
|
||||
format.
|
||||
|
||||
Column arguments are either pre-defined shorthand chars (see below),
|
||||
@ -1124,8 +1117,7 @@ Commas between consecutive shorthand chars are optional.
|
||||
Pre-defined column shorthand chars:
|
||||
n - Name
|
||||
t - Token
|
||||
E - Expires At`,
|
||||
))
|
||||
E - Expires At`))
|
||||
cli.AddStringFlag(cmd.Flags(), &c.flagFormat, "format|f", c.global.defaultListFormat(), "", i18n.G(`Format (csv|json|table|yaml|compact|markdown), use suffix ",noheader" to disable headers and ",header" to enable if demanded, e.g. csv,header`))
|
||||
cli.AddStringFlag(cmd.Flags(), &c.flagColumns, "columns|c", defaultclusterTokensColumns, "", i18n.G("Columns"))
|
||||
|
||||
@ -1185,7 +1177,7 @@ func (c *cmdClusterListTokens) expiresAtColumnData(token *api.ClusterMemberJoinT
|
||||
}
|
||||
|
||||
func (c *cmdClusterListTokens) run(cmd *cobra.Command, args []string) error {
|
||||
parsed, err := c.global.Parse(cmdClusterListTokensUsage, cmd, args)
|
||||
parsed, err := cmdClusterListTokensUsage.Parse(c.global.conf, cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -1278,7 +1270,7 @@ func (c *cmdClusterRevokeToken) command() *cobra.Command {
|
||||
}
|
||||
|
||||
func (c *cmdClusterRevokeToken) run(cmd *cobra.Command, args []string) error {
|
||||
parsed, err := c.global.Parse(cmdClusterRevokeTokenUsage, cmd, args)
|
||||
parsed, err := cmdClusterRevokeTokenUsage.Parse(c.global.conf, cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -1373,7 +1365,7 @@ func (c *cmdClusterUpdateCertificate) command() *cobra.Command {
|
||||
}
|
||||
|
||||
func (c *cmdClusterUpdateCertificate) run(cmd *cobra.Command, args []string) error {
|
||||
parsed, err := c.global.Parse(cmdClusterUpdateCertificateUsage, cmd, args)
|
||||
parsed, err := cmdClusterUpdateCertificateUsage.Parse(c.global.conf, cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -1460,9 +1452,7 @@ func (c *cmdClusterEvacuate) command() *cobra.Command {
|
||||
cmd.Aliases = []string{"evac"}
|
||||
cmd.Use = cli.U("evacuate", cmdClusterEvacuateRestoreUsage...)
|
||||
cmd.Short = i18n.G("Evacuate cluster member")
|
||||
cmd.Long = cli.FormatSection(color.DescriptionPrefix, i18n.G(`Evacuate cluster member
|
||||
|
||||
The action flag allows overriding the default server-side action ("cluster.evacuate" instance configuration option)`))
|
||||
cmd.Long = cli.FormatSection(color.DescriptionPrefix, i18n.G(`Evacuate cluster member`))
|
||||
|
||||
cli.AddStringFlag(cmd.Flags(), &c.action.flagAction, "action", "", "", i18n.G(`Force a particular evacuation action`))
|
||||
|
||||
@ -1491,10 +1481,7 @@ func (c *cmdClusterRestore) command() *cobra.Command {
|
||||
cmd := c.action.command()
|
||||
cmd.Use = cli.U("restore", cmdClusterEvacuateRestoreUsage...)
|
||||
cmd.Short = i18n.G("Restore cluster member")
|
||||
cmd.Long = cli.FormatSection(color.DescriptionPrefix, i18n.G(`Restore cluster member
|
||||
|
||||
The action flag allows overriding the default behavior of moving and starting back all instances.
|
||||
The only supported value at the moment is "skip" which brings the cluster member online without relocating any instances.`))
|
||||
cmd.Long = cli.FormatSection(color.DescriptionPrefix, i18n.G(`Restore cluster member`))
|
||||
|
||||
cli.AddStringFlag(cmd.Flags(), &c.action.flagAction, "action", "", "", i18n.G(`Force a particular restoration action`))
|
||||
|
||||
@ -1518,7 +1505,7 @@ func (c *cmdClusterEvacuateAction) command() *cobra.Command {
|
||||
}
|
||||
|
||||
func (c *cmdClusterEvacuateAction) run(cmd *cobra.Command, args []string) error {
|
||||
parsed, err := c.global.Parse(cmdClusterEvacuateRestoreUsage, cmd, args)
|
||||
parsed, err := cmdClusterEvacuateRestoreUsage.Parse(c.global.conf, cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -1782,12 +1769,8 @@ func askClustering(asker ask.Asker, config *api.InitPreseed, cluster incus.Insta
|
||||
return err
|
||||
}
|
||||
|
||||
clusterURL, err := url.Parse(connectInfo.URL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
config.Cluster.ClusterAddress = clusterURL.Host
|
||||
// Set the token.
|
||||
config.Cluster.ClusterAddress = connectInfo.URL
|
||||
config.Cluster.ClusterCertificate = connectInfo.Certificate
|
||||
config.Cluster.ClusterToken = joinToken.String()
|
||||
}
|
||||
|
||||
@ -94,7 +94,7 @@ type cmdClusterGroupAssign struct {
|
||||
cluster *cmdCluster
|
||||
}
|
||||
|
||||
var cmdClusterGroupAssignUsage = u.Usage{u.Member.Remote(), u.Group.List(1)}
|
||||
var cmdClusterGroupAssignUsage = u.Usage{u.Member.Remote(), u.Group.List(1, ",")}
|
||||
|
||||
func (c *cmdClusterGroupAssign) command() *cobra.Command {
|
||||
cmd := &cobra.Command{}
|
||||
@ -102,15 +102,13 @@ func (c *cmdClusterGroupAssign) command() *cobra.Command {
|
||||
cmd.Aliases = []string{"apply"}
|
||||
cmd.Short = i18n.G("Assign sets of groups to cluster members")
|
||||
cmd.Long = cli.FormatSection(color.DescriptionPrefix, i18n.G(
|
||||
`Assign sets of groups to cluster members`,
|
||||
))
|
||||
`Assign sets of groups to cluster members`))
|
||||
cmd.Example = cli.FormatSection("", i18n.G(
|
||||
`incus cluster group assign foo default bar
|
||||
`incus cluster group assign foo default,bar
|
||||
Set the groups for "foo" to "default" and "bar".
|
||||
|
||||
incus cluster group assign foo default
|
||||
Reset "foo" to only using the "default" cluster group.`,
|
||||
))
|
||||
Reset "foo" to only using the "default" cluster group.`))
|
||||
|
||||
cmd.RunE = c.run
|
||||
|
||||
@ -130,7 +128,7 @@ incus cluster group assign foo default
|
||||
}
|
||||
|
||||
func (c *cmdClusterGroupAssign) run(cmd *cobra.Command, args []string) error {
|
||||
parsed, err := c.global.Parse(cmdClusterGroupAssignUsage, cmd, args)
|
||||
parsed, err := cmdClusterGroupAssignUsage.Parse(c.global.conf, cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -195,7 +193,7 @@ incus cluster group create g1 < config.yaml
|
||||
}
|
||||
|
||||
func (c *cmdClusterGroupCreate) run(cmd *cobra.Command, args []string) error {
|
||||
parsed, err := c.global.Parse(cmdClusterGroupCreateUsage, cmd, args)
|
||||
parsed, err := cmdClusterGroupCreateUsage.Parse(c.global.conf, cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -264,7 +262,7 @@ func (c *cmdClusterGroupDelete) command() *cobra.Command {
|
||||
}
|
||||
|
||||
func (c *cmdClusterGroupDelete) run(cmd *cobra.Command, args []string) error {
|
||||
parsed, err := c.global.Parse(cmdClusterGroupDeleteUsage, cmd, args)
|
||||
parsed, err := cmdClusterGroupDeleteUsage.Parse(c.global.conf, cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -320,7 +318,7 @@ func (c *cmdClusterGroupEdit) command() *cobra.Command {
|
||||
}
|
||||
|
||||
func (c *cmdClusterGroupEdit) run(cmd *cobra.Command, args []string) error {
|
||||
parsed, err := c.global.Parse(cmdClusterGroupEditUsage, cmd, args)
|
||||
parsed, err := cmdClusterGroupEditUsage.Parse(c.global.conf, cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -351,7 +349,7 @@ func (c *cmdClusterGroupEdit) run(cmd *cobra.Command, args []string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
data, err := yaml.Dump(group, yaml.WithV2Defaults())
|
||||
data, err := yaml.Dump(group, yaml.V2)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -399,8 +397,7 @@ func (c *cmdClusterGroupEdit) run(cmd *cobra.Command, args []string) error {
|
||||
func (c *cmdClusterGroupEdit) helpTemplate() string {
|
||||
return i18n.G(
|
||||
`### This is a YAML representation of the cluster group.
|
||||
### Any line starting with a '# will be ignored.`,
|
||||
)
|
||||
### Any line starting with a '# will be ignored.`)
|
||||
}
|
||||
|
||||
// List.
|
||||
@ -426,8 +423,8 @@ Default column layout: ndm
|
||||
|
||||
== Columns ==
|
||||
The -c option takes a comma separated list of arguments that control
|
||||
which cluster groups attributes to output when displaying in table
|
||||
or csv format.
|
||||
which instance attributes to output when displaying in table or csv
|
||||
format.
|
||||
|
||||
Column arguments are either pre-defined shorthand chars (see below),
|
||||
or (extended) config keys.
|
||||
@ -437,8 +434,7 @@ Commas between consecutive shorthand chars are optional.
|
||||
Pre-defined column shorthand chars:
|
||||
n - Name
|
||||
d - Description
|
||||
m - Member`,
|
||||
))
|
||||
m - Member`))
|
||||
|
||||
cli.AddStringFlag(cmd.Flags(), &c.flagColumns, "columns|c", defaultClusterGroupColumns, "", i18n.G("Columns"))
|
||||
cli.AddStringFlag(cmd.Flags(), &c.flagFormat, "format|f", c.global.defaultListFormat(), "", i18n.G(`Format (csv|json|table|yaml|compact|markdown), use suffix ",noheader" to disable headers and ",header" to enable it if missing, e.g. csv,header`))
|
||||
@ -503,7 +499,7 @@ func (c *cmdClusterGroupList) membersColumnData(group api.ClusterGroup) string {
|
||||
}
|
||||
|
||||
func (c *cmdClusterGroupList) run(cmd *cobra.Command, args []string) error {
|
||||
parsed, err := c.global.Parse(cmdClusterGroupListUsage, cmd, args)
|
||||
parsed, err := cmdClusterGroupListUsage.Parse(c.global.conf, cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -565,8 +561,7 @@ func (c *cmdClusterGroupRemove) command() *cobra.Command {
|
||||
cmd.Use = cli.U("remove", cmdClusterGroupRemoveUsage...)
|
||||
cmd.Short = i18n.G("Remove member from group")
|
||||
cmd.Long = cli.FormatSection(color.DescriptionPrefix, i18n.G(
|
||||
`Remove a cluster member from a cluster group`,
|
||||
))
|
||||
`Remove a cluster member from a cluster group`))
|
||||
|
||||
cmd.RunE = c.run
|
||||
|
||||
@ -586,7 +581,7 @@ func (c *cmdClusterGroupRemove) command() *cobra.Command {
|
||||
}
|
||||
|
||||
func (c *cmdClusterGroupRemove) run(cmd *cobra.Command, args []string) error {
|
||||
parsed, err := c.global.Parse(cmdClusterGroupRemoveUsage, cmd, args)
|
||||
parsed, err := cmdClusterGroupRemoveUsage.Parse(c.global.conf, cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -657,7 +652,7 @@ func (c *cmdClusterGroupRename) command() *cobra.Command {
|
||||
}
|
||||
|
||||
func (c *cmdClusterGroupRename) run(cmd *cobra.Command, args []string) error {
|
||||
parsed, err := c.global.Parse(cmdClusterGroupRenameUsage, cmd, args)
|
||||
parsed, err := cmdClusterGroupRenameUsage.Parse(c.global.conf, cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -707,7 +702,7 @@ func (c *cmdClusterGroupShow) command() *cobra.Command {
|
||||
}
|
||||
|
||||
func (c *cmdClusterGroupShow) run(cmd *cobra.Command, args []string) error {
|
||||
parsed, err := c.global.Parse(cmdClusterGroupShowUsage, cmd, args)
|
||||
parsed, err := cmdClusterGroupShowUsage.Parse(c.global.conf, cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -721,7 +716,7 @@ func (c *cmdClusterGroupShow) run(cmd *cobra.Command, args []string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
data, err := yaml.Dump(&group, yaml.WithV2Defaults())
|
||||
data, err := yaml.Dump(&group, yaml.V2)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -743,8 +738,7 @@ func (c *cmdClusterGroupAdd) command() *cobra.Command {
|
||||
cmd.Use = cli.U("add", cmdClusterGroupAddUsage...)
|
||||
cmd.Short = i18n.G("Add member to group")
|
||||
cmd.Long = cli.FormatSection(color.DescriptionPrefix, i18n.G(
|
||||
`Add a cluster member to a cluster group`,
|
||||
))
|
||||
`Add a cluster member to a cluster group`))
|
||||
|
||||
cmd.RunE = c.run
|
||||
|
||||
@ -764,7 +758,7 @@ func (c *cmdClusterGroupAdd) command() *cobra.Command {
|
||||
}
|
||||
|
||||
func (c *cmdClusterGroupAdd) run(cmd *cobra.Command, args []string) error {
|
||||
parsed, err := c.global.Parse(cmdClusterGroupAddUsage, cmd, args)
|
||||
parsed, err := cmdClusterGroupAddUsage.Parse(c.global.conf, cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -832,7 +826,7 @@ func (c *cmdClusterGroupGet) command() *cobra.Command {
|
||||
}
|
||||
|
||||
func (c *cmdClusterGroupGet) run(cmd *cobra.Command, args []string) error {
|
||||
parsed, err := c.global.Parse(cmdClusterGroupGetUsage, cmd, args)
|
||||
parsed, err := cmdClusterGroupGetUsage.Parse(c.global.conf, cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -937,7 +931,7 @@ func (c *cmdClusterGroupSet) set(cmd *cobra.Command, parsed []*u.Parsed) error {
|
||||
}
|
||||
|
||||
func (c *cmdClusterGroupSet) run(cmd *cobra.Command, args []string) error {
|
||||
parsed, err := c.global.Parse(cmdClusterGroupSetUsage, cmd, args)
|
||||
parsed, err := cmdClusterGroupSetUsage.Parse(c.global.conf, cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -954,7 +948,7 @@ type cmdClusterGroupUnset struct {
|
||||
flagIsProperty bool
|
||||
}
|
||||
|
||||
var cmdClusterGroupUnsetUsage = u.Usage{u.Group.Remote(), u.Key.List(1)}
|
||||
var cmdClusterGroupUnsetUsage = u.Usage{u.Group.Remote(), u.Key}
|
||||
|
||||
func (c *cmdClusterGroupUnset) command() *cobra.Command {
|
||||
cmd := &cobra.Command{}
|
||||
@ -962,7 +956,7 @@ func (c *cmdClusterGroupUnset) command() *cobra.Command {
|
||||
cmd.Short = i18n.G("Unset a cluster group's configuration keys")
|
||||
cmd.Long = cli.FormatSection(color.DescriptionPrefix, cmd.Short)
|
||||
|
||||
cli.AddBoolFlag(cmd.Flags(), &c.flagIsProperty, "property|p", i18n.G("Unset the keys as cluster group properties"))
|
||||
cli.AddBoolFlag(cmd.Flags(), &c.flagIsProperty, "property|p", i18n.G("Unset the key as a cluster group property"))
|
||||
cmd.RunE = c.run
|
||||
|
||||
cmd.ValidArgsFunction = func(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
@ -981,7 +975,7 @@ func (c *cmdClusterGroupUnset) command() *cobra.Command {
|
||||
}
|
||||
|
||||
func (c *cmdClusterGroupUnset) run(cmd *cobra.Command, args []string) error {
|
||||
parsed, err := c.global.Parse(cmdClusterGroupUnsetUsage, cmd, args)
|
||||
parsed, err := cmdClusterGroupUnsetUsage.Parse(c.global.conf, cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -66,7 +66,7 @@ func (c *cmdClusterRoleAdd) command() *cobra.Command {
|
||||
}
|
||||
|
||||
func (c *cmdClusterRoleAdd) run(cmd *cobra.Command, args []string) error {
|
||||
parsed, err := c.global.Parse(cmdClusterRoleAddUsage, cmd, args)
|
||||
parsed, err := cmdClusterRoleAddUsage.Parse(c.global.conf, cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -107,8 +107,7 @@ func (c *cmdClusterRoleRemove) command() *cobra.Command {
|
||||
cmd.Aliases = []string{"delete", "rm"}
|
||||
cmd.Short = i18n.G("Remove roles from a cluster member")
|
||||
cmd.Long = cli.FormatSection(color.DescriptionPrefix, i18n.G(
|
||||
`Remove roles from a cluster member`,
|
||||
))
|
||||
`Remove roles from a cluster member`))
|
||||
|
||||
cmd.RunE = c.run
|
||||
|
||||
@ -128,7 +127,7 @@ func (c *cmdClusterRoleRemove) command() *cobra.Command {
|
||||
}
|
||||
|
||||
func (c *cmdClusterRoleRemove) run(cmd *cobra.Command, args []string) error {
|
||||
parsed, err := c.global.Parse(cmdClusterRoleRemoteUsage, cmd, args)
|
||||
parsed, err := cmdClusterRoleRemoteUsage.Parse(c.global.conf, cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -391,7 +391,7 @@ func (g *cmdGlobal) cmpInstancesAndSnapshots(toComplete string) ([]string, cobra
|
||||
resource := resources[0]
|
||||
|
||||
if strings.Contains(resource.name, instance.SnapshotDelimiter) {
|
||||
instName, _, _ := strings.Cut(resource.name, instance.SnapshotDelimiter)
|
||||
instName := strings.SplitN(resource.name, instance.SnapshotDelimiter, 2)[0]
|
||||
snapshots, _ := resource.server.GetInstanceSnapshotNames(instName)
|
||||
for _, snapshot := range snapshots {
|
||||
results = append(results, fmt.Sprintf("%s/%s", instName, snapshot))
|
||||
@ -1123,7 +1123,7 @@ func (g *cmdGlobal) cmpStoragePoolWithVolume(toComplete string) ([]string, cobra
|
||||
return results, cobra.ShellCompDirectiveNoSpace
|
||||
}
|
||||
|
||||
pool, _, _ := strings.Cut(toComplete, "/")
|
||||
pool := strings.Split(toComplete, "/")[0]
|
||||
volumes, compdir := g.cmpStoragePoolVolumes(pool)
|
||||
if compdir == cobra.ShellCompDirectiveError {
|
||||
return nil, compdir
|
||||
@ -1268,31 +1268,6 @@ func (g *cmdGlobal) cmpStoragePoolVolumeProfiles(poolName string, volumeName str
|
||||
return results, cobra.ShellCompDirectiveNoFileComp
|
||||
}
|
||||
|
||||
func (g *cmdGlobal) cmpStoragePoolVolumeBitmaps(poolName string, volumeName string) ([]string, cobra.ShellCompDirective) {
|
||||
// Parse remote
|
||||
resources, err := g.parseServers(poolName)
|
||||
if err != nil || len(resources) == 0 {
|
||||
return nil, cobra.ShellCompDirectiveError
|
||||
}
|
||||
|
||||
resource := resources[0]
|
||||
client := resource.server
|
||||
|
||||
pool := poolName
|
||||
if strings.Contains(poolName, ":") {
|
||||
pool = strings.Split(poolName, ":")[1]
|
||||
}
|
||||
|
||||
volName, volType := parseVolume("custom", volumeName)
|
||||
|
||||
bitmaps, err := client.GetStorageVolumeBitmapNames(pool, volType, volName)
|
||||
if err != nil {
|
||||
return nil, cobra.ShellCompDirectiveError
|
||||
}
|
||||
|
||||
return bitmaps, cobra.ShellCompDirectiveNoFileComp
|
||||
}
|
||||
|
||||
func (g *cmdGlobal) cmpStoragePoolVolumeSnapshots(poolName string, volumeName string) ([]string, cobra.ShellCompDirective) {
|
||||
// Parse remote
|
||||
resources, err := g.parseServers(poolName)
|
||||
|
||||
@ -30,8 +30,7 @@ func (c *cmdConfig) command() *cobra.Command {
|
||||
cmd.Use = cli.U("config")
|
||||
cmd.Short = i18n.G("Manage instance and server configuration options")
|
||||
cmd.Long = cli.FormatSection(color.DescriptionPrefix, i18n.G(
|
||||
`Manage instance and server configuration options`,
|
||||
))
|
||||
`Manage instance and server configuration options`))
|
||||
|
||||
// Device
|
||||
configDeviceCmd := cmdConfigDevice{global: c.global, config: c}
|
||||
@ -95,12 +94,10 @@ func (c *cmdConfigEdit) command() *cobra.Command {
|
||||
cmd.Use = cli.U("edit", cmdConfigEditUsage...)
|
||||
cmd.Short = i18n.G("Edit instance or server configurations as YAML")
|
||||
cmd.Long = cli.FormatSection(color.DescriptionPrefix, i18n.G(
|
||||
`Edit instance or server configurations as YAML`,
|
||||
))
|
||||
`Edit instance or server configurations as YAML`))
|
||||
cmd.Example = cli.FormatSection("", i18n.G(
|
||||
`incus config edit <instance> < instance.yaml
|
||||
Update the instance configuration from instance.yaml.`,
|
||||
))
|
||||
Update the instance configuration from config.yaml.`))
|
||||
|
||||
cli.AddStringFlag(cmd.Flags(), &c.config.flagTarget, "target", "", "", i18n.G("Cluster member name"))
|
||||
cmd.RunE = c.run
|
||||
@ -135,12 +132,11 @@ func (c *cmdConfigEdit) helpTemplate() string {
|
||||
### type: disk
|
||||
### ephemeral: false
|
||||
###
|
||||
### Note that the name is shown but cannot be changed`,
|
||||
)
|
||||
### Note that the name is shown but cannot be changed`)
|
||||
}
|
||||
|
||||
func (c *cmdConfigEdit) run(cmd *cobra.Command, args []string) error {
|
||||
parsed, err := c.global.Parse(cmdConfigEditUsage, cmd, args)
|
||||
parsed, err := cmdConfigEditUsage.Parse(c.global.conf, cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -211,7 +207,7 @@ func (c *cmdConfigEdit) run(cmd *cobra.Command, args []string) error {
|
||||
inst.ExpandedConfig = nil
|
||||
inst.ExpandedDevices = nil
|
||||
|
||||
data, err = yaml.Dump(&inst, yaml.WithV2Defaults())
|
||||
data, err = yaml.Dump(&inst, yaml.V2)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -227,7 +223,7 @@ func (c *cmdConfigEdit) run(cmd *cobra.Command, args []string) error {
|
||||
inst.ExpandedConfig = nil
|
||||
inst.ExpandedDevices = nil
|
||||
|
||||
data, err = yaml.Dump(&inst, yaml.WithV2Defaults())
|
||||
data, err = yaml.Dump(&inst, yaml.V2)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -320,7 +316,7 @@ func (c *cmdConfigEdit) run(cmd *cobra.Command, args []string) error {
|
||||
}
|
||||
|
||||
brief := server.Writable()
|
||||
data, err := yaml.Dump(&brief, yaml.WithV2Defaults())
|
||||
data, err := yaml.Dump(&brief, yaml.V2)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -379,8 +375,7 @@ func (c *cmdConfigGet) command() *cobra.Command {
|
||||
cmd.Use = cli.U("get", cmdConfigGetUsage...)
|
||||
cmd.Short = i18n.G("Get values for instance or server configuration keys")
|
||||
cmd.Long = cli.FormatSection(color.DescriptionPrefix, i18n.G(
|
||||
`Get values for instance or server configuration keys`,
|
||||
))
|
||||
`Get values for instance or server configuration keys`))
|
||||
|
||||
cli.AddBoolFlag(cmd.Flags(), &c.flagExpanded, "expanded|e", i18n.G("Access the expanded configuration"))
|
||||
cli.AddBoolFlag(cmd.Flags(), &c.flagIsProperty, "property|p", i18n.G("Get the key as an instance property"))
|
||||
@ -405,7 +400,7 @@ func (c *cmdConfigGet) command() *cobra.Command {
|
||||
func (c *cmdConfigGet) run(cmd *cobra.Command, args []string) error {
|
||||
// Do NOT blindly copy the following parsing line; it performs right-to-left parsing, which in
|
||||
// most cases is NOT what you want.
|
||||
parsed, err := c.global.Parse(cmdConfigGetUsage, cmd, args, true)
|
||||
parsed, err := cmdConfigGetUsage.Parse(c.global.conf, cmd, args, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -513,8 +508,7 @@ func (c *cmdConfigSet) command() *cobra.Command {
|
||||
`Set instance or server configuration keys
|
||||
|
||||
For backward compatibility, a single configuration key may still be set with:
|
||||
incus config set [<remote>:][<instance>] <key> <value>`,
|
||||
))
|
||||
incus config set [<remote>:][<instance>] <key> <value>`))
|
||||
cmd.Example = cli.FormatSection("", i18n.G(
|
||||
`incus config set [<remote>:]<instance> limits.cpu=2
|
||||
Will set a CPU limit of "2" for the instance.
|
||||
@ -523,8 +517,7 @@ incus config set my-instance cloud-init.user-data - < cloud-init.yaml
|
||||
Sets the cloud-init user-data for instance "my-instance" by reading "cloud-init.yaml" through stdin.
|
||||
|
||||
incus config set core.https_address=[::]:8443
|
||||
Will have the server listen on IPv4 and IPv6 port 8443.`,
|
||||
))
|
||||
Will have the server listen on IPv4 and IPv6 port 8443.`))
|
||||
|
||||
cli.AddStringFlag(cmd.Flags(), &c.config.flagTarget, "target", "", "", i18n.G("Cluster member name"))
|
||||
cli.AddBoolFlag(cmd.Flags(), &c.flagIsProperty, "property|p", i18n.G("Set the key as an instance property"))
|
||||
@ -594,7 +587,7 @@ func (c *cmdConfigSet) set(cmd *cobra.Command, parsed []*u.Parsed) error {
|
||||
return op.Wait()
|
||||
}
|
||||
|
||||
return errors.New(i18n.G("There is no config key to set on an instance snapshot."))
|
||||
return errors.New(i18n.G("The is no config key to set on an instance snapshot."))
|
||||
}
|
||||
|
||||
inst, etag, err := d.GetInstance(parsedPath.String)
|
||||
@ -667,7 +660,7 @@ func (c *cmdConfigSet) set(cmd *cobra.Command, parsed []*u.Parsed) error {
|
||||
func (c *cmdConfigSet) run(cmd *cobra.Command, args []string) error {
|
||||
// Do NOT blindly copy the following parsing line; it performs right-to-left parsing, which in
|
||||
// most cases is NOT what you want.
|
||||
parsed, err := c.global.Parse(cmdConfigSetUsage, cmd, args, true)
|
||||
parsed, err := cmdConfigSetUsage.Parse(c.global.conf, cmd, args, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -690,8 +683,7 @@ func (c *cmdConfigShow) command() *cobra.Command {
|
||||
cmd.Use = cli.U("show", cmdConfigShowUsage...)
|
||||
cmd.Short = i18n.G("Show instance or server configurations")
|
||||
cmd.Long = cli.FormatSection(color.DescriptionPrefix, i18n.G(
|
||||
`Show instance or server configurations`,
|
||||
))
|
||||
`Show instance or server configurations`))
|
||||
|
||||
cli.AddBoolFlag(cmd.Flags(), &c.flagExpanded, "expanded|e", i18n.G("Show the expanded configuration"))
|
||||
cli.AddStringFlag(cmd.Flags(), &c.config.flagTarget, "target", "", "", i18n.G("Cluster member name"))
|
||||
@ -709,7 +701,7 @@ func (c *cmdConfigShow) command() *cobra.Command {
|
||||
}
|
||||
|
||||
func (c *cmdConfigShow) run(cmd *cobra.Command, args []string) error {
|
||||
parsed, err := c.global.Parse(cmdConfigShowUsage, cmd, args)
|
||||
parsed, err := cmdConfigShowUsage.Parse(c.global.conf, cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -742,7 +734,7 @@ func (c *cmdConfigShow) run(cmd *cobra.Command, args []string) error {
|
||||
}
|
||||
|
||||
brief := server.Writable()
|
||||
data, err = yaml.Dump(&brief, yaml.WithV2Defaults())
|
||||
data, err = yaml.Dump(&brief, yaml.V2)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -785,7 +777,7 @@ func (c *cmdConfigShow) run(cmd *cobra.Command, args []string) error {
|
||||
}
|
||||
}
|
||||
|
||||
data, err = yaml.Dump(&brief, yaml.WithV2Defaults())
|
||||
data, err = yaml.Dump(&brief, yaml.V2)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -805,21 +797,17 @@ type cmdConfigUnset struct {
|
||||
flagIsProperty bool
|
||||
}
|
||||
|
||||
var cmdConfigUnsetUsage = u.Usage{u.MakePath(u.Instance, u.Snapshot.Optional()).Optional().Remote(), u.Key.List(1)}
|
||||
var cmdConfigUnsetUsage = u.Usage{u.MakePath(u.Instance, u.Snapshot.Optional()).Optional().Remote(), u.Key}
|
||||
|
||||
func (c *cmdConfigUnset) command() *cobra.Command {
|
||||
cmd := &cobra.Command{}
|
||||
cmd.Use = cli.U("unset", cmdConfigUnsetUsage...)
|
||||
cmd.Short = i18n.G("Unset instance or server configuration keys")
|
||||
cmd.Long = cli.FormatSection(color.DescriptionPrefix, i18n.G(
|
||||
`Unset instance or server configuration keys
|
||||
|
||||
Unsetting several keys in one go is only supported for instance configuration.
|
||||
`,
|
||||
))
|
||||
`Unset instance or server configuration keys`))
|
||||
|
||||
cli.AddStringFlag(cmd.Flags(), &c.config.flagTarget, "target", "", "", i18n.G("Cluster member name"))
|
||||
cli.AddBoolFlag(cmd.Flags(), &c.flagIsProperty, "property|p", i18n.G("Unset the keys as instance properties"))
|
||||
cli.AddBoolFlag(cmd.Flags(), &c.flagIsProperty, "property|p", i18n.G("Unset the key as an instance property"))
|
||||
cmd.RunE = c.run
|
||||
|
||||
cmd.ValidArgsFunction = func(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
@ -838,8 +826,9 @@ Unsetting several keys in one go is only supported for instance configuration.
|
||||
}
|
||||
|
||||
func (c *cmdConfigUnset) run(cmd *cobra.Command, args []string) error {
|
||||
// Do NOT blindly copy the following parsing line; it is a dirty hack to disambiguate the parser.
|
||||
parsed, err := c.global.Parse(cmdConfigUnsetUsage, cmd, args, len(args) == 1)
|
||||
// Do NOT blindly copy the following parsing line; it performs right-to-left parsing, which in
|
||||
// most cases is NOT what you want.
|
||||
parsed, err := cmdConfigUnsetUsage.Parse(c.global.conf, cmd, args, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -97,16 +97,14 @@ func (c *cmdConfigDeviceAdd) command() *cobra.Command {
|
||||
Will mount the host's /share/c1 onto /opt in the instance.
|
||||
|
||||
incus config device add [<remote>:]instance1 <device-name> disk pool=some-pool source=some-volume path=/opt
|
||||
Will mount the some-volume volume on some-pool onto /opt in the instance.`,
|
||||
))
|
||||
Will mount the some-volume volume on some-pool onto /opt in the instance.`))
|
||||
} else if c.profile != nil {
|
||||
cmd.Example = cli.FormatSection("", i18n.G(
|
||||
`incus profile device add [<remote>:]profile1 <device-name> disk source=/share/c1 path=/opt
|
||||
Will mount the host's /share/c1 onto /opt in the instance.
|
||||
|
||||
incus profile device add [<remote>:]profile1 <device-name> disk pool=some-pool source=some-volume path=/opt
|
||||
Will mount the some-volume volume on some-pool onto /opt in the instance.`,
|
||||
))
|
||||
Will mount the some-volume volume on some-pool onto /opt in the instance.`))
|
||||
}
|
||||
|
||||
cmd.RunE = c.run
|
||||
@ -127,7 +125,7 @@ incus profile device add [<remote>:]profile1 <device-name> disk pool=some-pool s
|
||||
}
|
||||
|
||||
func (c *cmdConfigDeviceAdd) run(cmd *cobra.Command, args []string) error {
|
||||
parsed, err := c.global.Parse(c.configDevice.formatUsage(cmdConfigDeviceAddUsage), cmd, args)
|
||||
parsed, err := c.configDevice.formatUsage(cmdConfigDeviceAddUsage).Parse(c.global.conf, cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -211,8 +209,7 @@ func (c *cmdConfigDeviceGet) command() *cobra.Command {
|
||||
cmd.Use = cli.U("get", c.configDevice.formatUsage(cmdConfigDeviceGetUsage)...)
|
||||
cmd.Short = i18n.G("Get values for device configuration keys")
|
||||
cmd.Long = cli.FormatSection(color.DescriptionPrefix, i18n.G(
|
||||
`Get values for device configuration keys`,
|
||||
))
|
||||
`Get values for device configuration keys`))
|
||||
|
||||
cmd.RunE = c.run
|
||||
|
||||
@ -240,7 +237,7 @@ func (c *cmdConfigDeviceGet) command() *cobra.Command {
|
||||
}
|
||||
|
||||
func (c *cmdConfigDeviceGet) run(cmd *cobra.Command, args []string) error {
|
||||
parsed, err := c.global.Parse(c.configDevice.formatUsage(cmdConfigDeviceGetUsage), cmd, args)
|
||||
parsed, err := c.configDevice.formatUsage(cmdConfigDeviceGetUsage).Parse(c.global.conf, cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -319,7 +316,7 @@ func (c *cmdConfigDeviceList) command() *cobra.Command {
|
||||
}
|
||||
|
||||
func (c *cmdConfigDeviceList) run(cmd *cobra.Command, args []string) error {
|
||||
parsed, err := c.global.Parse(c.configDevice.formatUsage(cmdConfigDeviceListUsage), cmd, args)
|
||||
parsed, err := c.configDevice.formatUsage(cmdConfigDeviceListUsage).Parse(c.global.conf, cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -367,8 +364,7 @@ func (c *cmdConfigDeviceOverride) command() *cobra.Command {
|
||||
cmd.Use = cli.U("override", cmdConfigDeviceOverrideUsage...)
|
||||
cmd.Short = i18n.G("Copy profile inherited devices and override configuration keys")
|
||||
cmd.Long = cli.FormatSection(color.DescriptionPrefix, i18n.G(
|
||||
`Copy profile inherited devices and override configuration keys`,
|
||||
))
|
||||
`Copy profile inherited devices and override configuration keys`))
|
||||
|
||||
cmd.RunE = c.run
|
||||
|
||||
@ -384,7 +380,7 @@ func (c *cmdConfigDeviceOverride) command() *cobra.Command {
|
||||
}
|
||||
|
||||
func (c *cmdConfigDeviceOverride) run(cmd *cobra.Command, args []string) error {
|
||||
parsed, err := c.global.Parse(cmdConfigDeviceOverrideUsage, cmd, args)
|
||||
parsed, err := cmdConfigDeviceOverrideUsage.Parse(c.global.conf, cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -475,7 +471,7 @@ func (c *cmdConfigDeviceRemove) command() *cobra.Command {
|
||||
}
|
||||
|
||||
func (c *cmdConfigDeviceRemove) run(cmd *cobra.Command, args []string) error {
|
||||
parsed, err := c.global.Parse(c.configDevice.formatUsage(cmdConfigDeviceRemoveUsage), cmd, args)
|
||||
parsed, err := c.configDevice.formatUsage(cmdConfigDeviceRemoveUsage).Parse(c.global.conf, cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -572,15 +568,13 @@ func (c *cmdConfigDeviceSet) command() *cobra.Command {
|
||||
`Set device configuration keys
|
||||
|
||||
For backward compatibility, a single configuration key may still be set with:
|
||||
incus config device set [<remote>:]<instance> <device> <key> <value>`,
|
||||
))
|
||||
incus config device set [<remote>:]<instance> <device> <key> <value>`))
|
||||
} else if c.profile != nil {
|
||||
cmd.Long = cli.FormatSection(color.DescriptionPrefix, i18n.G(
|
||||
`Set device configuration keys
|
||||
|
||||
For backward compatibility, a single configuration key may still be set with:
|
||||
incus profile device set [<remote>:]<profile> <device> <key> <value>`,
|
||||
))
|
||||
incus profile device set [<remote>:]<profile> <device> <key> <value>`))
|
||||
}
|
||||
|
||||
cmd.RunE = c.run
|
||||
@ -670,7 +664,7 @@ func (c *cmdConfigDeviceSet) set(cmd *cobra.Command, parsed []*u.Parsed) error {
|
||||
}
|
||||
|
||||
func (c *cmdConfigDeviceSet) run(cmd *cobra.Command, args []string) error {
|
||||
parsed, err := c.global.Parse(c.configDevice.formatUsage(cmdConfigDeviceSetUsage), cmd, args)
|
||||
parsed, err := c.configDevice.formatUsage(cmdConfigDeviceSetUsage).Parse(c.global.conf, cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -712,7 +706,7 @@ func (c *cmdConfigDeviceShow) command() *cobra.Command {
|
||||
}
|
||||
|
||||
func (c *cmdConfigDeviceShow) run(cmd *cobra.Command, args []string) error {
|
||||
parsed, err := c.global.Parse(c.configDevice.formatUsage(cmdConfigDeviceShowUsage), cmd, args)
|
||||
parsed, err := c.configDevice.formatUsage(cmdConfigDeviceShowUsage).Parse(c.global.conf, cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -738,7 +732,7 @@ func (c *cmdConfigDeviceShow) run(cmd *cobra.Command, args []string) error {
|
||||
devices = inst.Devices
|
||||
}
|
||||
|
||||
data, err := yaml.Dump(&devices, yaml.WithV2Defaults())
|
||||
data, err := yaml.Dump(&devices, yaml.V2)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -757,7 +751,7 @@ type cmdConfigDeviceUnset struct {
|
||||
profile *cmdProfile
|
||||
}
|
||||
|
||||
var cmdConfigDeviceUnsetUsage = u.Usage{u.Device, u.Key.List(1)}
|
||||
var cmdConfigDeviceUnsetUsage = u.Usage{u.Device, u.Key}
|
||||
|
||||
func (c *cmdConfigDeviceUnset) command() *cobra.Command {
|
||||
cmd := &cobra.Command{}
|
||||
@ -791,7 +785,7 @@ func (c *cmdConfigDeviceUnset) command() *cobra.Command {
|
||||
}
|
||||
|
||||
func (c *cmdConfigDeviceUnset) run(cmd *cobra.Command, args []string) error {
|
||||
parsed, err := c.global.Parse(c.configDevice.formatUsage(cmdConfigDeviceUnsetUsage), cmd, args)
|
||||
parsed, err := c.configDevice.formatUsage(cmdConfigDeviceUnsetUsage).Parse(c.global.conf, cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -91,12 +91,11 @@ func (c *cmdConfigMetadataEdit) helpTemplate() string {
|
||||
### - ""
|
||||
### create_only: false
|
||||
### template: template.tpl
|
||||
### properties: {}`,
|
||||
)
|
||||
### properties: {}`)
|
||||
}
|
||||
|
||||
func (c *cmdConfigMetadataEdit) run(cmd *cobra.Command, args []string) error {
|
||||
parsed, err := c.global.Parse(cmdConfigMetadataEditUsage, cmd, args)
|
||||
parsed, err := cmdConfigMetadataEditUsage.Parse(c.global.conf, cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -125,7 +124,7 @@ func (c *cmdConfigMetadataEdit) run(cmd *cobra.Command, args []string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
origContent, err := yaml.Dump(metadata, yaml.WithV2Defaults())
|
||||
origContent, err := yaml.Dump(metadata, yaml.V2)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -196,7 +195,7 @@ func (c *cmdConfigMetadataShow) command() *cobra.Command {
|
||||
}
|
||||
|
||||
func (c *cmdConfigMetadataShow) run(cmd *cobra.Command, args []string) error {
|
||||
parsed, err := c.global.Parse(cmdConfigMetadataShowUsage, cmd, args)
|
||||
parsed, err := cmdConfigMetadataShowUsage.Parse(c.global.conf, cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -210,7 +209,7 @@ func (c *cmdConfigMetadataShow) run(cmd *cobra.Command, args []string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
content, err := yaml.Dump(metadata, yaml.WithV2Defaults())
|
||||
content, err := yaml.Dump(metadata, yaml.V2)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -68,8 +68,7 @@ func (c *cmdConfigTemplateCreate) command() *cobra.Command {
|
||||
cmd.Aliases = []string{"add"}
|
||||
cmd.Short = i18n.G("Create new instance file templates")
|
||||
cmd.Long = cli.FormatSection(color.DescriptionPrefix, i18n.G(
|
||||
`Create new instance file templates`,
|
||||
))
|
||||
`Create new instance file templates`))
|
||||
cmd.Example = cli.FormatSection("", i18n.G(`incus config template create u1 t1
|
||||
Create template t1 for instance u1
|
||||
|
||||
@ -90,7 +89,7 @@ incus config template create u1 t1 < config.tpl
|
||||
}
|
||||
|
||||
func (c *cmdConfigTemplateCreate) run(cmd *cobra.Command, args []string) error {
|
||||
parsed, err := c.global.Parse(cmdConfigTemplateCreateUsage, cmd, args)
|
||||
parsed, err := cmdConfigTemplateCreateUsage.Parse(c.global.conf, cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -149,7 +148,7 @@ func (c *cmdConfigTemplateDelete) command() *cobra.Command {
|
||||
}
|
||||
|
||||
func (c *cmdConfigTemplateDelete) run(cmd *cobra.Command, args []string) error {
|
||||
parsed, err := c.global.Parse(cmdConfigTemplateDeleteUsage, cmd, args)
|
||||
parsed, err := cmdConfigTemplateDeleteUsage.Parse(c.global.conf, cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -195,7 +194,7 @@ func (c *cmdConfigTemplateEdit) command() *cobra.Command {
|
||||
}
|
||||
|
||||
func (c *cmdConfigTemplateEdit) run(cmd *cobra.Command, args []string) error {
|
||||
parsed, err := c.global.Parse(cmdConfigTemplateEditUsage, cmd, args)
|
||||
parsed, err := cmdConfigTemplateEditUsage.Parse(c.global.conf, cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -289,7 +288,7 @@ func (c *cmdConfigTemplateList) command() *cobra.Command {
|
||||
}
|
||||
|
||||
func (c *cmdConfigTemplateList) run(cmd *cobra.Command, args []string) error {
|
||||
parsed, err := c.global.Parse(cmdConfigTemplateListUsage, cmd, args)
|
||||
parsed, err := cmdConfigTemplateListUsage.Parse(c.global.conf, cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -332,8 +331,7 @@ func (c *cmdConfigTemplateShow) command() *cobra.Command {
|
||||
cmd.Use = cli.U("show", cmdConfigTemplateShowUsage...)
|
||||
cmd.Short = i18n.G("Show content of instance file templates")
|
||||
cmd.Long = cli.FormatSection(color.DescriptionPrefix, i18n.G(
|
||||
`Show content of instance file templates`,
|
||||
))
|
||||
`Show content of instance file templates`))
|
||||
|
||||
cmd.RunE = c.run
|
||||
|
||||
@ -353,7 +351,7 @@ func (c *cmdConfigTemplateShow) command() *cobra.Command {
|
||||
}
|
||||
|
||||
func (c *cmdConfigTemplateShow) run(cmd *cobra.Command, args []string) error {
|
||||
parsed, err := c.global.Parse(cmdConfigTemplateShowUsage, cmd, args)
|
||||
parsed, err := cmdConfigTemplateShowUsage.Parse(c.global.conf, cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -95,8 +95,7 @@ func (c *cmdConfigTrustAdd) command() *cobra.Command {
|
||||
`Add new trusted client
|
||||
|
||||
This will issue a trust token to be used by the client to add itself to the trust store.
|
||||
`,
|
||||
))
|
||||
`))
|
||||
|
||||
cli.AddBoolFlag(cmd.Flags(), &c.flagRestricted, "restricted", i18n.G("Restrict the certificate to one or more projects"))
|
||||
cli.AddStringFlag(cmd.Flags(), &c.flagProjects, "projects", "", "", i18n.G("List of projects to restrict the certificate to"))
|
||||
@ -107,7 +106,7 @@ This will issue a trust token to be used by the client to add itself to the trus
|
||||
}
|
||||
|
||||
func (c *cmdConfigTrustAdd) run(cmd *cobra.Command, args []string) error {
|
||||
parsed, err := c.global.Parse(cmdConfigTrustAddUsage, cmd, args)
|
||||
parsed, err := cmdConfigTrustAddUsage.Parse(c.global.conf, cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -172,8 +171,7 @@ func (c *cmdConfigTrustAddCertificate) command() *cobra.Command {
|
||||
The following certificate types are supported:
|
||||
- client (default)
|
||||
- metrics
|
||||
`,
|
||||
))
|
||||
`))
|
||||
|
||||
cli.AddBoolFlag(cmd.Flags(), &c.flagRestricted, "restricted", i18n.G("Restrict the certificate to one or more projects"))
|
||||
cli.AddStringFlag(cmd.Flags(), &c.flagProjects, "projects", "", "", i18n.G("List of projects to restrict the certificate to"))
|
||||
@ -187,7 +185,7 @@ The following certificate types are supported:
|
||||
}
|
||||
|
||||
func (c *cmdConfigTrustAddCertificate) run(cmd *cobra.Command, args []string) error {
|
||||
parsed, err := c.global.Parse(cmdConfigTrustAddCertificateUsage, cmd, args)
|
||||
parsed, err := cmdConfigTrustAddCertificateUsage.Parse(c.global.conf, cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -273,12 +271,11 @@ func (c *cmdConfigTrustEdit) helpTemplate() string {
|
||||
`### This is a YAML representation of the certificate.
|
||||
### Any line starting with a '# will be ignored.
|
||||
###
|
||||
### Note that the fingerprint is shown but cannot be changed`,
|
||||
)
|
||||
### Note that the fingerprint is shown but cannot be changed`)
|
||||
}
|
||||
|
||||
func (c *cmdConfigTrustEdit) run(cmd *cobra.Command, args []string) error {
|
||||
parsed, err := c.global.Parse(cmdConfigTrustEditUsage, cmd, args)
|
||||
parsed, err := cmdConfigTrustEditUsage.Parse(c.global.conf, cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -308,7 +305,7 @@ func (c *cmdConfigTrustEdit) run(cmd *cobra.Command, args []string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
data, err := yaml.Dump(&cert, yaml.WithV2Defaults())
|
||||
data, err := yaml.Dump(&cert, yaml.V2)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -388,6 +385,7 @@ or csv format.
|
||||
Default column layout is: ntdfe
|
||||
|
||||
Column shorthand chars:
|
||||
|
||||
n - Name
|
||||
t - Type
|
||||
c - Common Name
|
||||
@ -396,8 +394,7 @@ Column shorthand chars:
|
||||
i - Issue date
|
||||
e - Expiry date
|
||||
r - Whether certificate is restricted
|
||||
p - Newline-separated list of projects`,
|
||||
))
|
||||
p - Newline-separated list of projects`))
|
||||
|
||||
cli.AddStringFlag(cmd.Flags(), &c.flagColumns, "columns|c", "ntdfe", "", i18n.G("Columns"))
|
||||
cli.AddStringFlag(cmd.Flags(), &c.flagFormat, "format|f", c.global.defaultListFormat(), "", i18n.G(`Format (csv|json|table|yaml|compact|markdown), use suffix ",noheader" to disable headers and ",header" to enable it if missing, e.g. csv,header`))
|
||||
@ -490,7 +487,7 @@ func (c *cmdConfigTrustList) projectColumnData(rowData rowData) string {
|
||||
}
|
||||
|
||||
func (c *cmdConfigTrustList) run(cmd *cobra.Command, args []string) error {
|
||||
parsed, err := c.global.Parse(cmdConfigTrustListUsage, cmd, args)
|
||||
parsed, err := cmdConfigTrustListUsage.Parse(c.global.conf, cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -572,8 +569,8 @@ Default column layout: ntE
|
||||
|
||||
== Columns ==
|
||||
The -c option takes a comma separated list of arguments that control
|
||||
which certificate add tokens attributes to output when displaying in
|
||||
table or csv format.
|
||||
which network zone attributes to output when displaying in table or csv
|
||||
format.
|
||||
|
||||
Column arguments are either pre-defined shorthand chars (see below),
|
||||
or (extended) config keys.
|
||||
@ -583,8 +580,7 @@ Commas between consecutive shorthand chars are optional.
|
||||
Pre-defined column shorthand chars:
|
||||
n - Name
|
||||
t - Token
|
||||
E - Expires At`,
|
||||
))
|
||||
E - Expires At`))
|
||||
cli.AddStringFlag(cmd.Flags(), &c.flagFormat, "format|f", c.global.defaultListFormat(), "", i18n.G(`Format (csv|json|table|yaml|compact|markdown), use suffix ",noheader" to disable headers and ",header" to enable it if missing, e.g. csv,header`))
|
||||
cli.AddStringFlag(cmd.Flags(), &c.flagColumns, "columns|c", defaultConfigTrustListTokenColumns, "", i18n.G("Columns"))
|
||||
|
||||
@ -644,7 +640,7 @@ func (c *cmdConfigTrustListTokens) expiresAtColumnData(token *api.CertificateAdd
|
||||
}
|
||||
|
||||
func (c *cmdConfigTrustListTokens) run(cmd *cobra.Command, args []string) error {
|
||||
parsed, err := c.global.Parse(cmdConfigTrustListTokensUsage, cmd, args)
|
||||
parsed, err := cmdConfigTrustListTokensUsage.Parse(c.global.conf, cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -721,7 +717,7 @@ func (c *cmdConfigTrustRemove) command() *cobra.Command {
|
||||
}
|
||||
|
||||
func (c *cmdConfigTrustRemove) run(cmd *cobra.Command, args []string) error {
|
||||
parsed, err := c.global.Parse(cmdConfigTrustRemoveUsage, cmd, args)
|
||||
parsed, err := cmdConfigTrustRemoveUsage.Parse(c.global.conf, cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -754,7 +750,7 @@ func (c *cmdConfigTrustRevokeToken) command() *cobra.Command {
|
||||
}
|
||||
|
||||
func (c *cmdConfigTrustRevokeToken) run(cmd *cobra.Command, args []string) error {
|
||||
parsed, err := c.global.Parse(cmdConfigTrustRevokeTokenUsage, cmd, args)
|
||||
parsed, err := cmdConfigTrustRevokeTokenUsage.Parse(c.global.conf, cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -822,7 +818,7 @@ func (c *cmdConfigTrustShow) command() *cobra.Command {
|
||||
}
|
||||
|
||||
func (c *cmdConfigTrustShow) run(cmd *cobra.Command, args []string) error {
|
||||
parsed, err := c.global.Parse(cmdConfigTrustShowUsage, cmd, args)
|
||||
parsed, err := cmdConfigTrustShowUsage.Parse(c.global.conf, cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -836,7 +832,7 @@ func (c *cmdConfigTrustShow) run(cmd *cobra.Command, args []string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
data, err := yaml.Dump(&cert, yaml.WithV2Defaults())
|
||||
data, err := yaml.Dump(&cert, yaml.V2)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -4,9 +4,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"net"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
@ -36,8 +34,6 @@ type cmdConsole struct {
|
||||
flagForce bool
|
||||
flagShowLog bool
|
||||
flagType string
|
||||
|
||||
withLog bool
|
||||
}
|
||||
|
||||
var cmdConsoleUsage = u.Usage{u.Instance.Remote()}
|
||||
@ -50,8 +46,7 @@ func (c *cmdConsole) command() *cobra.Command {
|
||||
`Attach to instance consoles
|
||||
|
||||
This command allows you to interact with the boot console of an instance
|
||||
as well as retrieve past log entries from it.`,
|
||||
))
|
||||
as well as retrieve past log entries from it.`))
|
||||
|
||||
cmd.RunE = c.run
|
||||
cli.AddBoolFlag(cmd.Flags(), &c.flagForce, "force|f", i18n.G("Forces a connection to the console, even if there is already an active session"))
|
||||
@ -114,7 +109,7 @@ func (er stdinMirror) Read(p []byte) (int, error) {
|
||||
}
|
||||
|
||||
func (c *cmdConsole) run(cmd *cobra.Command, args []string) error {
|
||||
parsed, err := c.global.Parse(cmdConsoleUsage, cmd, args)
|
||||
parsed, err := cmdConsoleUsage.Parse(c.global.conf, cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -176,7 +171,7 @@ func (c *cmdConsole) text(d incus.InstanceServer, name string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
defer logger.WarnOnError(func() error { return termios.Restore(cfd, oldTTYstate) }, "Failed to restore terminal")
|
||||
defer func() { _ = termios.Restore(cfd, oldTTYstate) }()
|
||||
|
||||
handler := c.controlSocketHandler
|
||||
|
||||
@ -200,20 +195,11 @@ func (c *cmdConsole) text(d incus.InstanceServer, name string) error {
|
||||
sendDisconnect := make(chan struct{})
|
||||
defer close(sendDisconnect)
|
||||
|
||||
// Buffer websocket output so it doesn't interleave with the log
|
||||
// content printed below.
|
||||
var terminalOut io.WriteCloser = os.Stdout
|
||||
var buffered *bufferedWriter
|
||||
if c.withLog {
|
||||
buffered = &bufferedWriter{out: os.Stdout}
|
||||
terminalOut = buffered
|
||||
}
|
||||
|
||||
consoleArgs := incus.InstanceConsoleArgs{
|
||||
Terminal: &readWriteCloser{stdinMirror{
|
||||
os.Stdin,
|
||||
manualDisconnect, new(bool),
|
||||
}, terminalOut},
|
||||
}, os.Stdout},
|
||||
Control: handler,
|
||||
ConsoleDisconnect: consoleDisconnect,
|
||||
}
|
||||
@ -238,33 +224,6 @@ func (c *cmdConsole) text(d incus.InstanceServer, name string) error {
|
||||
|
||||
fmt.Print(i18n.G("To detach from the console, press: <ctrl>+a q") + "\n\r")
|
||||
|
||||
// With the websocket attached, fetch the existing console log,
|
||||
// print it, then release any buffered websocket output. A small
|
||||
// overlap is preferable to losing output produced before the attach.
|
||||
if c.withLog {
|
||||
logArgs := &incus.InstanceConsoleLogArgs{}
|
||||
log, err := d.GetInstanceConsoleLog(name, logArgs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
content, err := io.ReadAll(log)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// In raw mode the terminal doesn't translate \n to \r\n, so
|
||||
// normalize line endings before printing the log.
|
||||
text := strings.ReplaceAll(string(content), "\r\n", "\n")
|
||||
text = strings.ReplaceAll(text, "\n", "\r\n")
|
||||
fmt.Print(text)
|
||||
|
||||
err = buffered.Flush()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for the operation to complete
|
||||
err = op.Wait()
|
||||
if err != nil {
|
||||
@ -331,22 +290,9 @@ func (c *cmdConsole) vga(d incus.InstanceServer, name string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// The socket file is usually already removed by listener.Close(), so ignore not-exist errors.
|
||||
defer logger.WarnOnError(func() error {
|
||||
err := os.Remove(path.Name())
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
return nil
|
||||
}
|
||||
defer func() { _ = os.Remove(path.Name()) }()
|
||||
|
||||
return err
|
||||
}, "Failed to remove temporary file")
|
||||
|
||||
spiceURL := &url.URL{
|
||||
Scheme: "spice+unix",
|
||||
Path: path.Name(),
|
||||
}
|
||||
|
||||
socket = spiceURL.String()
|
||||
socket = fmt.Sprintf("spice+unix://%s", path.Name())
|
||||
} else {
|
||||
listener, err = net.Listen("tcp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
|
||||
@ -15,7 +15,6 @@ import (
|
||||
"github.com/lxc/incus/v7/internal/instance"
|
||||
"github.com/lxc/incus/v7/shared/api"
|
||||
cli "github.com/lxc/incus/v7/shared/cmd"
|
||||
"github.com/lxc/incus/v7/shared/util"
|
||||
)
|
||||
|
||||
type cmdCopy struct {
|
||||
@ -53,8 +52,7 @@ Transfer modes (--mode):
|
||||
- relay: The CLI connects to both source and server and proxies the data (both source and target must listen on network)
|
||||
|
||||
The pull transfer mode is the default as it is compatible with all server versions.
|
||||
`,
|
||||
))
|
||||
`))
|
||||
|
||||
cmd.RunE = c.run
|
||||
cli.AddStringArrayFlag(cmd.Flags(), &c.flagConfig, "config|c", i18n.G("Config key/value to apply to the new instance"))
|
||||
@ -87,43 +85,6 @@ The pull transfer mode is the default as it is compatible with all server versio
|
||||
return cmd
|
||||
}
|
||||
|
||||
// applyStoragePool sets the storage pool on the root disk and any dependent
|
||||
// disk device, unless their pool was explicitly overridden via --device.
|
||||
func applyStoragePool(devices map[string]map[string]string, deviceOverrides map[string]map[string]string, pool string) {
|
||||
if pool == "" {
|
||||
return
|
||||
}
|
||||
|
||||
rootDiskDeviceKey, _, _ := instance.GetRootDiskDevice(devices)
|
||||
|
||||
for devName, dev := range devices {
|
||||
if dev["type"] != "disk" {
|
||||
continue
|
||||
}
|
||||
|
||||
// Only override the root disk and dependent disk devices.
|
||||
if devName != rootDiskDeviceKey && util.IsFalseOrEmpty(dev["dependent"]) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Don't override devices with an explicit pool override.
|
||||
if deviceOverrides[devName]["pool"] != "" {
|
||||
continue
|
||||
}
|
||||
|
||||
dev["pool"] = pool
|
||||
}
|
||||
|
||||
// Create a root disk device if the instance doesn't have one.
|
||||
if rootDiskDeviceKey == "" {
|
||||
devices["root"] = map[string]string{
|
||||
"type": "disk",
|
||||
"path": "/",
|
||||
"pool": pool,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// copyOrMove runs the post-parsing command logic.
|
||||
func (c *cmdCopy) copyOrMove(cmd *cobra.Command, src *u.Parsed, dst *u.Parsed, keepVolatile bool, ephemeral int, stateful bool, instanceOnly bool, mode string, pool string, move bool) error {
|
||||
srcServer := src.RemoteServer
|
||||
@ -244,7 +205,17 @@ func (c *cmdCopy) copyOrMove(cmd *cobra.Command, src *u.Parsed, dst *u.Parsed, k
|
||||
entry.Ephemeral = false
|
||||
}
|
||||
|
||||
applyStoragePool(entry.Devices, deviceMap, pool)
|
||||
rootDiskDeviceKey, _, _ := instance.GetRootDiskDevice(entry.Devices)
|
||||
|
||||
if rootDiskDeviceKey != "" && pool != "" {
|
||||
entry.Devices[rootDiskDeviceKey]["pool"] = pool
|
||||
} else if pool != "" {
|
||||
entry.Devices["root"] = map[string]string{
|
||||
"type": "disk",
|
||||
"path": "/",
|
||||
"pool": pool,
|
||||
}
|
||||
}
|
||||
|
||||
if entry.Config != nil {
|
||||
// Strip the last_state.power key in all cases
|
||||
@ -326,7 +297,16 @@ func (c *cmdCopy) copyOrMove(cmd *cobra.Command, src *u.Parsed, dst *u.Parsed, k
|
||||
entry.Ephemeral = false
|
||||
}
|
||||
|
||||
applyStoragePool(entry.Devices, deviceMap, pool)
|
||||
rootDiskDeviceKey, _, _ := instance.GetRootDiskDevice(entry.Devices)
|
||||
if rootDiskDeviceKey != "" && pool != "" {
|
||||
entry.Devices[rootDiskDeviceKey]["pool"] = pool
|
||||
} else if pool != "" {
|
||||
entry.Devices["root"] = map[string]string{
|
||||
"type": "disk",
|
||||
"path": "/",
|
||||
"pool": pool,
|
||||
}
|
||||
}
|
||||
|
||||
// Strip the volatile keys if requested
|
||||
if !keepVolatile {
|
||||
@ -347,11 +327,6 @@ func (c *cmdCopy) copyOrMove(cmd *cobra.Command, src *u.Parsed, dst *u.Parsed, k
|
||||
dstServer = dstServer.UseTarget(c.flagTarget)
|
||||
}
|
||||
|
||||
// A stateless move of a running container is performed as a near-live migration.
|
||||
if srcServer.HasExtension("container_incremental_copy") && dstServer.HasExtension("container_incremental_copy") && move && !stateful && c.flagRefresh && !instanceOnly && entry.StatusCode == api.Running && entry.Type == "container" {
|
||||
return c.nearLiveMoveInstance(srcServer, dstServer, srcInstanceName, dstInstanceName, entry, &args)
|
||||
}
|
||||
|
||||
op, err = dstServer.CopyInstance(srcServer, *entry, &args)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -447,7 +422,7 @@ func (c *cmdCopy) copyOrMove(cmd *cobra.Command, src *u.Parsed, dst *u.Parsed, k
|
||||
}
|
||||
|
||||
func (c *cmdCopy) run(cmd *cobra.Command, args []string) error {
|
||||
parsed, err := c.global.Parse(cmdCopyUsage, cmd, args)
|
||||
parsed, err := cmdCopyUsage.Parse(c.global.conf, cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -85,7 +85,7 @@ incus launch images:debian/12 v2 --vm -d root,size=50GiB -d root,io.bus=nvme
|
||||
}
|
||||
|
||||
func (c *cmdCreate) run(cmd *cobra.Command, args []string) error {
|
||||
parsed, err := c.global.Parse(cmdCreateUsage, cmd, args)
|
||||
parsed, err := cmdCreateUsage.Parse(c.global.conf, cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
85
cmd/incus/debug.go
Normal file
85
cmd/incus/debug.go
Normal file
@ -0,0 +1,85 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/lxc/incus/v7/cmd/incus/color"
|
||||
u "github.com/lxc/incus/v7/cmd/incus/usage"
|
||||
"github.com/lxc/incus/v7/internal/i18n"
|
||||
cli "github.com/lxc/incus/v7/shared/cmd"
|
||||
"github.com/lxc/incus/v7/shared/util"
|
||||
)
|
||||
|
||||
type cmdDebug struct {
|
||||
global *cmdGlobal
|
||||
}
|
||||
|
||||
func (c *cmdDebug) command() *cobra.Command {
|
||||
cmd := &cobra.Command{}
|
||||
cmd.Hidden = true
|
||||
cmd.Use = cli.U("debug")
|
||||
cmd.Short = i18n.G("Debug commands")
|
||||
cmd.Long = cli.FormatSection(color.DescriptionPrefix, i18n.G(`Debug commands for instances`))
|
||||
|
||||
debugAttachCmd := cmdDebugMemory{global: c.global, debug: c}
|
||||
cmd.AddCommand(debugAttachCmd.command())
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
type cmdDebugMemory struct {
|
||||
global *cmdGlobal
|
||||
debug *cmdDebug
|
||||
|
||||
flagFormat string
|
||||
}
|
||||
|
||||
var cmdDebugMemoryUsage = u.Usage{u.Instance.Remote(), u.Target(u.File)}
|
||||
|
||||
func (c *cmdDebugMemory) command() *cobra.Command {
|
||||
cmd := &cobra.Command{}
|
||||
cmd.Use = cli.U("dump-memory", cmdDebugMemoryUsage...)
|
||||
cmd.Short = i18n.G("Export a virtual machine's memory state")
|
||||
cmd.Long = cli.FormatSection(color.DescriptionPrefix, i18n.G(
|
||||
`Export the current memory state of a running virtual machine into a dump file.
|
||||
This can be useful for debugging or analysis purposes.`))
|
||||
cmd.Example = cli.FormatSection("", i18n.G(
|
||||
`incus debug dump-memory vm1 memory-dump.elf --format=elf
|
||||
Creates an ELF format memory dump of the vm1 instance.`))
|
||||
|
||||
cmd.RunE = c.run
|
||||
cli.AddStringFlag(cmd.Flags(), &c.flagFormat, "format|f", "elf", "", i18n.G("Format of memory dump (e.g. elf, win-dmp, kdump-zlib, kdump-raw-zlib, ...)"))
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c *cmdDebugMemory) run(cmd *cobra.Command, args []string) error {
|
||||
parsed, err := cmdDebugMemoryUsage.Parse(c.global.conf, cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
d := parsed[0].RemoteServer
|
||||
instanceName := parsed[0].RemoteObject.String
|
||||
path := parsed[1].String
|
||||
|
||||
target, err := os.Create(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rc, err := d.GetInstanceDebugMemory(instanceName, c.flagFormat)
|
||||
if err != nil {
|
||||
return fmt.Errorf(i18n.G("Failed to dump instance memory: %w"), err)
|
||||
}
|
||||
|
||||
_, err = util.SafeCopy(target, rc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@ -1,217 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"go.yaml.in/yaml/v4"
|
||||
|
||||
"github.com/lxc/incus/v7/cmd/incus/color"
|
||||
u "github.com/lxc/incus/v7/cmd/incus/usage"
|
||||
"github.com/lxc/incus/v7/internal/i18n"
|
||||
cli "github.com/lxc/incus/v7/shared/cmd"
|
||||
)
|
||||
|
||||
type cmdDefault struct {
|
||||
global *cmdGlobal
|
||||
}
|
||||
|
||||
func (c *cmdDefault) command() *cobra.Command {
|
||||
cmd := &cobra.Command{}
|
||||
cmd.Use = cli.U("default")
|
||||
cmd.Short = i18n.G("Manage client defaults")
|
||||
cmd.Long = cli.FormatSection(color.DescriptionPrefix, i18n.G(`Manage client defaults`))
|
||||
cmd.Hidden = true
|
||||
|
||||
// Set
|
||||
defaultSetCmd := cmdDefaultSet{global: c.global, def: c}
|
||||
cmd.AddCommand(defaultSetCmd.command())
|
||||
|
||||
// Show
|
||||
defaultShowCmd := cmdDefaultShow{global: c.global, def: c}
|
||||
cmd.AddCommand(defaultShowCmd.command())
|
||||
|
||||
// Unset
|
||||
defaultUnsetCmd := cmdDefaultUnset{global: c.global, def: c, defaultSet: &defaultSetCmd}
|
||||
cmd.AddCommand(defaultUnsetCmd.command())
|
||||
|
||||
// Workaround for subcommand usage errors. See: https://github.com/spf13/cobra/issues/706
|
||||
cmd.Args = cobra.NoArgs
|
||||
cmd.Run = func(cmd *cobra.Command, _ []string) { _ = cmd.Usage() }
|
||||
return cmd
|
||||
}
|
||||
|
||||
// Set.
|
||||
type cmdDefaultSet struct {
|
||||
global *cmdGlobal
|
||||
def *cmdDefault
|
||||
}
|
||||
|
||||
var cmdDefaultSetUsage = u.Usage{u.KV.List(1)}
|
||||
|
||||
func (c *cmdDefaultSet) command() *cobra.Command {
|
||||
cmd := &cobra.Command{}
|
||||
cmd.Use = cli.U("set", cmdDefaultSetUsage...)
|
||||
cmd.Short = i18n.G("Set client defaults")
|
||||
cmd.Long = cli.FormatSection(color.DescriptionPrefix, i18n.G(`Set client defaults`))
|
||||
|
||||
cmd.RunE = c.run
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
// set runs the post-parsing command logic.
|
||||
func (c *cmdDefaultSet) set(cmd *cobra.Command, parsed []*u.Parsed) error {
|
||||
conf := c.global.conf
|
||||
|
||||
keys, err := kvToMap(parsed[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for k, v := range keys {
|
||||
switch k {
|
||||
case "list_format":
|
||||
err = c.setListFormat(v)
|
||||
case "console_type":
|
||||
err = c.setConsoleType(v)
|
||||
case "console_spice_command":
|
||||
err = c.setConsoleSpiceCommand(v)
|
||||
case "no_color":
|
||||
err = c.setNoColor(v)
|
||||
default:
|
||||
err = fmt.Errorf(i18n.G("Invalid default %q"), k)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return conf.SaveConfig(c.global.confPath)
|
||||
}
|
||||
|
||||
func (c *cmdDefaultSet) run(cmd *cobra.Command, args []string) error {
|
||||
parsed, err := c.global.Parse(cmdDefaultSetUsage, cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.set(cmd, parsed)
|
||||
}
|
||||
|
||||
func (c *cmdDefaultSet) setListFormat(listFormat string) error {
|
||||
// Validate list format
|
||||
switch listFormat {
|
||||
case cli.TableFormatCSV, cli.TableFormatJSON, cli.TableFormatTable, cli.TableFormatYAML, cli.TableFormatCompact, cli.TableFormatMarkdown, "":
|
||||
default:
|
||||
return fmt.Errorf(i18n.G("Invalid value %q for list_format"), listFormat)
|
||||
}
|
||||
|
||||
c.global.conf.Defaults.ListFormat = listFormat
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *cmdDefaultSet) setConsoleType(consoleType string) error {
|
||||
// Validate console type
|
||||
switch consoleType {
|
||||
case "console", "vga", "":
|
||||
default:
|
||||
return fmt.Errorf(i18n.G("Invalid value %q for console_type"), consoleType)
|
||||
}
|
||||
|
||||
c.global.conf.Defaults.ConsoleType = consoleType
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *cmdDefaultSet) setConsoleSpiceCommand(consoleSpiceCommand string) error {
|
||||
c.global.conf.Defaults.ConsoleSpiceCommand = consoleSpiceCommand
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *cmdDefaultSet) setNoColor(noColorStr string) error {
|
||||
// Treat an empty value as unsetting the key
|
||||
if noColorStr == "" {
|
||||
noColorStr = "false"
|
||||
}
|
||||
|
||||
// Validate no color input
|
||||
noColor, err := strconv.ParseBool(noColorStr)
|
||||
if err != nil {
|
||||
return errors.New(i18n.G("no_color must be boolean"))
|
||||
}
|
||||
|
||||
c.global.conf.Defaults.NoColor = noColor
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Show.
|
||||
type cmdDefaultShow struct {
|
||||
global *cmdGlobal
|
||||
def *cmdDefault
|
||||
}
|
||||
|
||||
var cmdDefaultShowUsage = u.Usage{}
|
||||
|
||||
func (c *cmdDefaultShow) command() *cobra.Command {
|
||||
cmd := &cobra.Command{}
|
||||
cmd.Use = cli.U("show", cmdDefaultShowUsage...)
|
||||
cmd.Short = i18n.G("Show client defaults")
|
||||
cmd.Long = cli.FormatSection(color.DescriptionPrefix, i18n.G(`Show client defaults`))
|
||||
|
||||
cmd.RunE = c.run
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c *cmdDefaultShow) run(cmd *cobra.Command, args []string) error {
|
||||
conf := c.global.conf
|
||||
|
||||
// Get Defaults
|
||||
defaults := conf.Defaults
|
||||
|
||||
// Display defaults
|
||||
data, err := yaml.Dump(&defaults, yaml.WithV2Defaults())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("%s", data)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Unset.
|
||||
type cmdDefaultUnset struct {
|
||||
global *cmdGlobal
|
||||
def *cmdDefault
|
||||
defaultSet *cmdDefaultSet
|
||||
}
|
||||
|
||||
var cmdDefaultUnsetUsage = u.Usage{u.Default.List(1)}
|
||||
|
||||
func (c *cmdDefaultUnset) command() *cobra.Command {
|
||||
cmd := &cobra.Command{}
|
||||
cmd.Use = cli.U("unset", cmdDefaultUnsetUsage...)
|
||||
cmd.Short = i18n.G("Unset client defaults")
|
||||
cmd.Long = cli.FormatSection(color.DescriptionPrefix, i18n.G(`Unset client defaults`))
|
||||
|
||||
cmd.RunE = c.run
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c *cmdDefaultUnset) run(cmd *cobra.Command, args []string) error {
|
||||
parsed, err := c.global.Parse(cmdDefaultUnsetUsage, cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return unsetKey(c.defaultSet, cmd, parsed)
|
||||
}
|
||||
@ -146,7 +146,7 @@ func (c *cmdDelete) deleteOne(p *u.Parsed) error {
|
||||
}
|
||||
|
||||
func (c *cmdDelete) run(cmd *cobra.Command, args []string) error {
|
||||
parsed, err := c.global.Parse(cmdDeleteUsage, cmd, args)
|
||||
parsed, err := cmdDeleteUsage.Parse(c.global.conf, cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -52,8 +52,7 @@ executable, passing the shell commands as arguments, for example:
|
||||
|
||||
incus exec <instance> -- sh -c "cd /tmp && pwd"
|
||||
|
||||
Mode defaults to non-interactive, interactive mode is selected if both stdin AND stdout are terminals (stderr is ignored).`,
|
||||
))
|
||||
Mode defaults to non-interactive, interactive mode is selected if both stdin AND stdout are terminals (stderr is ignored).`))
|
||||
cmd.Example = cli.FormatSection("", i18n.G(`incus exec c1 bash
|
||||
Run the "bash" command in instance "c1"
|
||||
|
||||
@ -99,7 +98,8 @@ func (c *cmdExec) sendTermSize(control *websocket.Conn) error {
|
||||
}
|
||||
|
||||
func (c *cmdExec) run(cmd *cobra.Command, args []string) error {
|
||||
parsed, err := c.global.Parse(cmdExecUsage, cmd, args)
|
||||
conf := c.global.conf
|
||||
parsed, err := cmdExecUsage.Parse(conf, cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -159,7 +159,7 @@ func (c *cmdExec) run(cmd *cobra.Command, args []string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
defer logger.WarnOnError(func() error { return termios.Restore(stdinFd, oldttystate) }, "Failed to restore terminal")
|
||||
defer func() { _ = termios.Restore(stdinFd, oldttystate) }()
|
||||
}
|
||||
|
||||
// Setup interactive console handler
|
||||
|
||||
@ -35,7 +35,7 @@ func (c *cmdExec) controlSocketHandler(control *websocket.Conn) {
|
||||
unix.SIGCONT)
|
||||
|
||||
closeMsg := websocket.FormatCloseMessage(websocket.CloseNormalClosure, "")
|
||||
defer logger.WarnOnError(func() error { return control.WriteMessage(websocket.CloseMessage, closeMsg) }, "Failed to write close message")
|
||||
defer func() { _ = control.WriteMessage(websocket.CloseMessage, closeMsg) }()
|
||||
|
||||
for {
|
||||
sig := <-ch
|
||||
|
||||
@ -17,7 +17,6 @@ import (
|
||||
"github.com/lxc/incus/v7/shared/api"
|
||||
"github.com/lxc/incus/v7/shared/archive"
|
||||
cli "github.com/lxc/incus/v7/shared/cmd"
|
||||
"github.com/lxc/incus/v7/shared/logger"
|
||||
"github.com/lxc/incus/v7/shared/util"
|
||||
)
|
||||
|
||||
@ -38,15 +37,13 @@ func (c *cmdExport) command() *cobra.Command {
|
||||
cmd.Use = cli.U("export", cmdExportUsage...)
|
||||
cmd.Short = i18n.G("Export instance backups")
|
||||
cmd.Long = cli.FormatSection(color.DescriptionPrefix, i18n.G(
|
||||
`Export instances as backup tarballs.`,
|
||||
))
|
||||
`Export instances as backup tarballs.`))
|
||||
cmd.Example = cli.FormatSection("", i18n.G(
|
||||
`incus export u1 backup0.tar.gz
|
||||
Download a backup tarball of the u1 instance.
|
||||
|
||||
incus export u1 -
|
||||
Download a backup tarball with it written to the standard output.`,
|
||||
))
|
||||
Download a backup tarball with it written to the standard output.`))
|
||||
|
||||
cmd.RunE = c.run
|
||||
cli.AddBoolFlag(cmd.Flags(), &c.flagInstanceOnly, "instance-only", i18n.G("Whether or not to only backup the instance (without snapshots)"))
|
||||
@ -59,7 +56,7 @@ incus export u1 -
|
||||
}
|
||||
|
||||
func (c *cmdExport) run(cmd *cobra.Command, args []string) error {
|
||||
parsed, err := c.global.Parse(cmdExportUsage, cmd, args)
|
||||
parsed, err := cmdExportUsage.Parse(c.global.conf, cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -162,7 +159,7 @@ func (c *cmdExport) run(cmd *cobra.Command, args []string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
defer logger.WarnOnErrorExcept(target.Close, []error{os.ErrClosed}, "Failed to close target file")
|
||||
defer func() { _ = target.Close() }()
|
||||
}
|
||||
|
||||
// Prepare the download request.
|
||||
|
||||
@ -7,7 +7,6 @@ import (
|
||||
"io/fs"
|
||||
"net"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strconv"
|
||||
@ -98,20 +97,18 @@ func (c *cmdFileCreate) command() *cobra.Command {
|
||||
cmd.Use = cli.U("create", cmdFileCreateUsage...)
|
||||
cmd.Short = i18n.G("Create files and directories in instances")
|
||||
cmd.Long = cli.FormatSection(color.DescriptionPrefix, i18n.G(
|
||||
`Create files and directories in instances`,
|
||||
))
|
||||
`Create files and directories in instances`))
|
||||
cmd.Example = cli.FormatSection("", i18n.G(
|
||||
`incus file create foo/bar
|
||||
To create a file /bar in the foo instance.
|
||||
|
||||
incus file create --type=symlink foo/bar baz
|
||||
To create a symlink /bar in instance foo whose target is baz.`,
|
||||
))
|
||||
To create a symlink /bar in instance foo whose target is baz.`))
|
||||
|
||||
cli.AddBoolFlag(cmd.Flags(), &c.file.flagMkdir, "create-dirs|p", i18n.G("Create any directories necessary"))
|
||||
cli.AddBoolFlag(cmd.Flags(), &c.flagForce, "force|f", i18n.G("Force creating files or directories"))
|
||||
cli.AddIntFlag(cmd.Flags(), &c.file.flagGID, "gid", i18n.G("Set the file's gid on create"), -1)
|
||||
cli.AddIntFlag(cmd.Flags(), &c.file.flagUID, "uid", i18n.G("Set the file's uid on create"), -1)
|
||||
cli.AddIntFlag(cmd.Flags(), &c.file.flagGID, "gid", -1, i18n.G("Set the file's gid on create"))
|
||||
cli.AddIntFlag(cmd.Flags(), &c.file.flagUID, "uid", -1, i18n.G("Set the file's uid on create"))
|
||||
cli.AddStringFlag(cmd.Flags(), &c.file.flagMode, "mode", "", "", i18n.G("Set the file's perms on create"))
|
||||
cli.AddStringFlag(cmd.Flags(), &c.flagType, "type|t", "file", "", i18n.G("The type to create (file, symlink, or directory)"))
|
||||
|
||||
@ -129,7 +126,7 @@ incus file create --type=symlink foo/bar baz
|
||||
}
|
||||
|
||||
func (c *cmdFileCreate) run(cmd *cobra.Command, args []string) error {
|
||||
parsed, err := c.global.Parse(cmdFileCreateUsage, cmd, args)
|
||||
parsed, err := cmdFileCreateUsage.Parse(c.global.conf, cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -149,7 +146,7 @@ func (c *cmdFileCreate) run(cmd *cobra.Command, args []string) error {
|
||||
return errors.New(i18n.G(`Symlink target path can only be used for type "symlink"`))
|
||||
}
|
||||
|
||||
symlinkTargetPath = path.Clean(symlinkTargetPath)
|
||||
symlinkTargetPath = filepath.Clean(symlinkTargetPath)
|
||||
}
|
||||
|
||||
if isDir {
|
||||
@ -162,7 +159,7 @@ func (c *cmdFileCreate) run(cmd *cobra.Command, args []string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
defer logger.WarnOnError(sftpConn.Close, "Failed to close SFTP connection")
|
||||
defer func() { _ = sftpConn.Close() }()
|
||||
|
||||
// Determine the target uid
|
||||
uid := max(c.file.flagUID, 0)
|
||||
@ -195,7 +192,7 @@ func (c *cmdFileCreate) run(cmd *cobra.Command, args []string) error {
|
||||
|
||||
// Create needed paths if requested
|
||||
if c.file.flagMkdir {
|
||||
err = sftpRecursiveMkdir(sftpConn, path.Dir(targetPath), nil, int64(uid), int64(gid))
|
||||
err = sftpRecursiveMkdir(sftpConn, filepath.Dir(targetPath), nil, int64(uid), int64(gid))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -288,7 +285,7 @@ func (c *cmdFileDelete) command() *cobra.Command {
|
||||
}
|
||||
|
||||
func (c *cmdFileDelete) run(cmd *cobra.Command, args []string) error {
|
||||
parsed, err := c.global.Parse(cmdFileDeleteUsage, cmd, args)
|
||||
parsed, err := cmdFileDeleteUsage.Parse(c.global.conf, cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -308,7 +305,7 @@ func (c *cmdFileDelete) run(cmd *cobra.Command, args []string) error {
|
||||
err := func() error {
|
||||
d := p.RemoteServer
|
||||
instanceName := p.RemoteObject.List[0].String
|
||||
filePath, _ := normalizePath(p.RemoteObject.List[1].String)
|
||||
path, _ := normalizePath(p.RemoteObject.List[1].String)
|
||||
instanceID := p.RemoteName + ":" + instanceName
|
||||
|
||||
sftpConn, ok := sftpClients[instanceID]
|
||||
@ -322,7 +319,7 @@ func (c *cmdFileDelete) run(cmd *cobra.Command, args []string) error {
|
||||
}
|
||||
|
||||
if c.flagForce {
|
||||
err = sftpConn.RemoveAll(filePath)
|
||||
err = sftpConn.RemoveAll(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -330,7 +327,7 @@ func (c *cmdFileDelete) run(cmd *cobra.Command, args []string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
return sftpConn.Remove(filePath)
|
||||
return sftpConn.Remove(path)
|
||||
}()
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
@ -374,7 +371,7 @@ func (c *cmdFileEdit) command() *cobra.Command {
|
||||
}
|
||||
|
||||
func (c *cmdFileEdit) run(cmd *cobra.Command, args []string) error {
|
||||
parsed, err := c.global.Parse(cmdFileEditUsage, cmd, args)
|
||||
parsed, err := cmdFileEditUsage.Parse(c.global.conf, cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -388,7 +385,7 @@ func (c *cmdFileEdit) run(cmd *cobra.Command, args []string) error {
|
||||
}
|
||||
|
||||
// Create temp file
|
||||
f, err := os.CreateTemp("", fmt.Sprintf("incus_file_edit_*%s", path.Ext(fileName)))
|
||||
f, err := os.CreateTemp("", fmt.Sprintf("incus_file_edit_*%s", filepath.Ext(fileName)))
|
||||
if err != nil {
|
||||
return fmt.Errorf(i18n.G("Unable to create a temporary file: %v"), err)
|
||||
}
|
||||
@ -402,7 +399,7 @@ func (c *cmdFileEdit) run(cmd *cobra.Command, args []string) error {
|
||||
c.filePush.edit = true
|
||||
|
||||
// Extract current value
|
||||
defer logger.WarnOnError(func() error { return os.Remove(fname) }, "Failed to remove temporary file")
|
||||
defer func() { _ = os.Remove(fname) }()
|
||||
err = c.filePull.pull(parsed, fname)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -444,8 +441,7 @@ func (c *cmdFilePull) command() *cobra.Command {
|
||||
To pull /etc/hosts from the instance and write it to the current directory.
|
||||
|
||||
incus file pull foo/etc/hosts -
|
||||
To pull /etc/hosts from the instance and write its output to standard output.`,
|
||||
))
|
||||
To pull /etc/hosts from the instance and write its output to standard output.`))
|
||||
|
||||
cli.AddBoolFlag(cmd.Flags(), &c.file.flagMkdir, "create-dirs|p", i18n.G("Create any directories necessary"))
|
||||
cli.AddBoolFlag(cmd.Flags(), &c.puller.flagRecursive, "recursive|r", i18n.G("Recursively transfer files"))
|
||||
@ -525,7 +521,7 @@ func (c *cmdFilePull) pull(parsedFiles []*u.Parsed, target string) error {
|
||||
err := func() error {
|
||||
d := p.RemoteServer
|
||||
instanceName := p.RemoteObject.List[0].String
|
||||
filePath := p.RemoteObject.List[1].String
|
||||
path := p.RemoteObject.List[1].String
|
||||
instanceID := p.RemoteName + ":" + instanceName
|
||||
|
||||
sftpConn, ok := sftpClients[instanceID]
|
||||
@ -538,20 +534,20 @@ func (c *cmdFilePull) pull(parsedFiles []*u.Parsed, target string) error {
|
||||
sftpClients[instanceID] = sftpConn
|
||||
}
|
||||
|
||||
srcInfo, normalizedPath, err := c.puller.statFile(sftpConn, filePath)
|
||||
srcInfo, normalizedPath, err := c.puller.statFile(sftpConn, path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Recursively copy directories.
|
||||
if srcInfo.IsDir() {
|
||||
return sftpRecursivePullFile(sftpConn, srcInfo, filePath, normalizedPath, target, c.global.flagQuiet, c.puller.flagDereference, len(parsedFiles) > 1 || util.PathExists(target))
|
||||
return sftpRecursivePullFile(sftpConn, srcInfo, path, normalizedPath, target, c.global.flagQuiet, c.puller.flagDereference, len(parsedFiles) > 1 || util.PathExists(target))
|
||||
}
|
||||
|
||||
// Determine the target path.
|
||||
var targetPath string
|
||||
if targetIsDir {
|
||||
targetPath = filepath.Join(target, path.Base(normalizedPath))
|
||||
targetPath = filepath.Join(target, filepath.Base(normalizedPath))
|
||||
} else {
|
||||
targetPath = target
|
||||
}
|
||||
@ -574,7 +570,7 @@ func (c *cmdFilePull) pull(parsedFiles []*u.Parsed, target string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
defer logger.WarnOnError(f.Close, "Failed to close file") // nolint:revive
|
||||
defer func() { _ = f.Close() }() // nolint:revive
|
||||
|
||||
err = os.Chmod(targetPath, os.FileMode(srcInfo.Mode()))
|
||||
if err != nil {
|
||||
@ -616,7 +612,7 @@ func (c *cmdFilePull) pull(parsedFiles []*u.Parsed, target string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
defer logger.WarnOnError(src.Close, "Failed to close source file")
|
||||
defer func() { _ = src.Close() }()
|
||||
|
||||
_, err = util.SafeCopy(writer, src)
|
||||
if err != nil {
|
||||
@ -643,7 +639,7 @@ func (c *cmdFilePull) pull(parsedFiles []*u.Parsed, target string) error {
|
||||
func (c *cmdFilePull) run(cmd *cobra.Command, args []string) error {
|
||||
// Do NOT blindly copy the following parsing line; it performs right-to-left parsing, which in
|
||||
// most cases is NOT what you want.
|
||||
parsed, err := c.global.Parse(cmdFilePullUsage, cmd, args, true)
|
||||
parsed, err := cmdFilePullUsage.Parse(c.global.conf, cmd, args, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -673,13 +669,12 @@ func (c *cmdFilePush) command() *cobra.Command {
|
||||
To push /etc/hosts into the instance "foo".
|
||||
|
||||
echo "Hello world" | incus file push - foo/root/test
|
||||
To read "Hello world" from standard input and write it into /root/test in instance "foo".`,
|
||||
))
|
||||
To read "Hello world" from standard input and write it into /root/test in instance "foo".`))
|
||||
|
||||
cli.AddBoolFlag(cmd.Flags(), &c.file.flagMkdir, "create-dirs|p", i18n.G("Create any directories necessary"))
|
||||
cli.AddIntFlag(cmd.Flags(), &c.file.flagUID, "uid", i18n.G("Set the files' UIDs on push"), -1)
|
||||
cli.AddIntFlag(cmd.Flags(), &c.file.flagGID, "gid", i18n.G("Set the files' GIDs on push"), -1)
|
||||
cli.AddStringFlag(cmd.Flags(), &c.file.flagMode, "mode", "", "", i18n.G("Set the file's perms on push (in recursive mode, only sets the target directory's permissions)"))
|
||||
cli.AddIntFlag(cmd.Flags(), &c.file.flagUID, "uid", -1, i18n.G("Set the files' UIDs on push (in recursive mode, only sets the target directory's UID if it doesn't exist and -p is used)"))
|
||||
cli.AddIntFlag(cmd.Flags(), &c.file.flagGID, "gid", -1, i18n.G("Set the files' GIDs on push (in recursive mode, only sets the target directory's GID if it doesn't exist and -p is used)"))
|
||||
cli.AddStringFlag(cmd.Flags(), &c.file.flagMode, "mode", "", "", i18n.G("Set the file's perms on push (in recursive mode, sets the target directory's permissions if it doesn't exist)"))
|
||||
cli.AddBoolFlag(cmd.Flags(), &c.pusher.flagRecursive, "recursive|r", i18n.G("Recursively transfer files"))
|
||||
cli.AddBoolFlag(cmd.Flags(), &c.pusher.flagNoDereference, "no-dereference|P", i18n.G("Never follow symbolic links in source path"))
|
||||
cli.AddBoolFlag(cmd.Flags(), &c.pusher.flagFollow, "follow|H", i18n.G("Follow command-line symbolic links in source path"))
|
||||
@ -716,7 +711,7 @@ func (c *cmdFilePush) push(srcFiles []string, parsedTarget *u.Parsed) error {
|
||||
return err
|
||||
}
|
||||
|
||||
defer logger.WarnOnError(sftpConn.Close, "Failed to close SFTP connection")
|
||||
defer func() { _ = sftpConn.Close() }()
|
||||
|
||||
targetInfo, err := sftpConn.Stat(target)
|
||||
if err == nil {
|
||||
@ -732,7 +727,7 @@ func (c *cmdFilePush) push(srcFiles []string, parsedTarget *u.Parsed) error {
|
||||
return errors.New(i18n.G("Missing target directory"))
|
||||
}
|
||||
|
||||
mode := -1
|
||||
var mode os.FileMode
|
||||
if c.file.flagMode != "" {
|
||||
if len(c.file.flagMode) == 3 {
|
||||
c.file.flagMode = "0" + c.file.flagMode
|
||||
@ -743,26 +738,22 @@ func (c *cmdFilePush) push(srcFiles []string, parsedTarget *u.Parsed) error {
|
||||
return err
|
||||
}
|
||||
|
||||
mode = int(os.FileMode(m).Perm())
|
||||
mode = os.FileMode(m)
|
||||
}
|
||||
|
||||
var errs []error
|
||||
canProcessStdin := len(srcFiles) == 1
|
||||
usePercentage := true
|
||||
|
||||
// Push the files
|
||||
for _, srcPath := range srcFiles {
|
||||
for _, path := range srcFiles {
|
||||
err := func() error {
|
||||
var f *os.File
|
||||
var linkTarget string
|
||||
var size int64
|
||||
args := incus.InstanceFileArgs{
|
||||
UID: int64(c.file.flagUID),
|
||||
GID: int64(c.file.flagGID),
|
||||
Mode: mode,
|
||||
}
|
||||
|
||||
if isStdin(srcPath) {
|
||||
m := mode
|
||||
uid := max(c.file.flagUID, 0)
|
||||
gid := max(c.file.flagGID, 0)
|
||||
if isStdin(path) {
|
||||
if !canProcessStdin {
|
||||
return errors.New(i18n.G("stdin can only be used once, with no other source arguments"))
|
||||
}
|
||||
@ -772,53 +763,54 @@ func (c *cmdFilePush) push(srcFiles []string, parsedTarget *u.Parsed) error {
|
||||
}
|
||||
|
||||
canProcessStdin = false
|
||||
usePercentage = false
|
||||
f = os.Stdin
|
||||
} else {
|
||||
srcInfo, wPath, err := c.pusher.statFile(srcPath)
|
||||
srcInfo, wPath, err := c.pusher.statFile(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Recursively copy directories.
|
||||
if srcInfo.IsDir() {
|
||||
return sftpRecursivePushFile(sftpConn, wPath, srcPath, target, args, c.global.flagQuiet, c.pusher.flagDereference, len(srcFiles) > 1 || targetExists)
|
||||
return sftpRecursivePushFile(sftpConn, wPath, path, target, c.global.flagQuiet, c.pusher.flagDereference, len(srcFiles) > 1 || targetExists)
|
||||
}
|
||||
|
||||
if srcInfo.Mode()&os.ModeSymlink != 0 {
|
||||
linkTarget, err = os.Readlink(srcPath)
|
||||
linkTarget, err = os.Readlink(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
f, err = os.Open(srcPath)
|
||||
f, err = os.Open(path)
|
||||
if err != nil {
|
||||
return fmt.Errorf(i18n.G("Failed to open source file %q: %v"), f, err)
|
||||
}
|
||||
|
||||
size = srcInfo.Size()
|
||||
defer logger.WarnOnError(f.Close, "Failed to close file")
|
||||
defer func() { _ = f.Close() }()
|
||||
}
|
||||
|
||||
dMode, dUID, dGID := internalIO.GetOwnerMode(srcInfo)
|
||||
if c.file.flagUID == -1 || c.file.flagGID == -1 {
|
||||
dMode, dUID, dGID := internalIO.GetOwnerMode(srcInfo)
|
||||
|
||||
if args.Mode == -1 {
|
||||
args.Mode = int(dMode)
|
||||
}
|
||||
if c.file.flagMode == "" {
|
||||
m = dMode
|
||||
}
|
||||
|
||||
if args.UID == -1 {
|
||||
args.UID = int64(dUID)
|
||||
}
|
||||
if c.file.flagUID == -1 {
|
||||
uid = dUID
|
||||
}
|
||||
|
||||
if args.GID == -1 {
|
||||
args.GID = int64(dGID)
|
||||
if c.file.flagGID == -1 {
|
||||
gid = dGID
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Determine the target path.
|
||||
var targetPath string
|
||||
if targetIsDir {
|
||||
targetPath = path.Join(target, filepath.Base(srcPath))
|
||||
targetPath = filepath.Join(target, filepath.Base(path))
|
||||
} else {
|
||||
targetPath = target
|
||||
}
|
||||
@ -826,23 +818,39 @@ func (c *cmdFilePush) push(srcFiles []string, parsedTarget *u.Parsed) error {
|
||||
// Create needed paths if requested
|
||||
if c.file.flagMkdir {
|
||||
mode := os.FileMode(DirMode)
|
||||
err = sftpRecursiveMkdir(sftpConn, path.Dir(targetPath), &mode, int64(args.UID), int64(args.GID))
|
||||
err = sftpRecursiveMkdir(sftpConn, filepath.Dir(targetPath), &mode, int64(uid), int64(gid))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the path already exists.
|
||||
_, err := sftpConn.Stat(targetPath)
|
||||
if err == nil && c.noModeChange {
|
||||
args.UID = -1
|
||||
args.GID = -1
|
||||
args.Mode = -1
|
||||
// Transfer the files.
|
||||
args := incus.InstanceFileArgs{
|
||||
UID: -1,
|
||||
GID: -1,
|
||||
Mode: -1,
|
||||
}
|
||||
|
||||
// Check if the path already exists.
|
||||
_, err := sftpConn.Stat(targetPath)
|
||||
fileExists := err == nil
|
||||
|
||||
if !c.noModeChange {
|
||||
if !fileExists || c.file.flagUID != -1 {
|
||||
args.UID = int64(uid)
|
||||
}
|
||||
|
||||
if !fileExists || c.file.flagGID != -1 {
|
||||
args.GID = int64(gid)
|
||||
}
|
||||
|
||||
if !fileExists || c.file.flagMode != "" {
|
||||
args.Mode = int(m.Perm())
|
||||
}
|
||||
}
|
||||
|
||||
// Transfer the files.
|
||||
progress := cli.ProgressRenderer{
|
||||
Format: fmt.Sprintf(i18n.G("Pushing %s to %s: %%s"), srcPath, targetPath),
|
||||
Format: fmt.Sprintf(i18n.G("Pushing %s to %s: %%s"), path, targetPath),
|
||||
Quiet: c.global.flagQuiet,
|
||||
}
|
||||
|
||||
@ -855,18 +863,16 @@ func (c *cmdFilePush) push(srcFiles []string, parsedTarget *u.Parsed) error {
|
||||
ReadCloser: f,
|
||||
Tracker: &ioprogress.ProgressTracker{
|
||||
Length: size,
|
||||
Handler: func(v int64, speed int64) {
|
||||
if usePercentage {
|
||||
progress.UpdateProgress(ioprogress.ProgressData{Text: fmt.Sprintf("%d%% (%s/s)", v, units.GetByteSizeString(speed, 2))})
|
||||
} else {
|
||||
progress.UpdateProgress(ioprogress.ProgressData{Text: fmt.Sprintf("%s (%s/s)", units.GetByteSizeString(v, 2), units.GetByteSizeString(speed, 2))})
|
||||
}
|
||||
Handler: func(percent int64, speed int64) {
|
||||
progress.UpdateProgress(ioprogress.ProgressData{
|
||||
Text: fmt.Sprintf("%d%% (%s/s)", percent, units.GetByteSizeString(speed, 2)),
|
||||
})
|
||||
},
|
||||
},
|
||||
}, f)
|
||||
}
|
||||
|
||||
logger.Infof("Pushing %s to %s (%s)", srcPath, targetPath, args.Type)
|
||||
logger.Infof("Pushing %s to %s (%s)", path, targetPath, args.Type)
|
||||
err = sftpCreateFile(sftpConn, targetPath, args, true)
|
||||
progress.Done("")
|
||||
return err
|
||||
@ -886,7 +892,7 @@ func (c *cmdFilePush) push(srcFiles []string, parsedTarget *u.Parsed) error {
|
||||
func (c *cmdFilePush) run(cmd *cobra.Command, args []string) error {
|
||||
// Do NOT blindly copy the following parsing line; it performs right-to-left parsing, which in
|
||||
// most cases is NOT what you want.
|
||||
parsed, err := c.global.Parse(cmdFilePushUsage, cmd, args, true)
|
||||
parsed, err := cmdFilePushUsage.Parse(c.global.conf, cmd, args, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -912,15 +918,13 @@ func (c *cmdFileMount) command() *cobra.Command {
|
||||
cmd.Short = i18n.G("Mount files from instances")
|
||||
cmd.Long = cli.FormatSection(color.DescriptionPrefix, i18n.G(
|
||||
`Mount files from instances.
|
||||
If no target path is provided, start an SSH SFTP listener instead.`,
|
||||
))
|
||||
If no target path is provided, start an SSH SFTP listener instead.`))
|
||||
cmd.Example = cli.FormatSection("", i18n.G(
|
||||
`incus file mount foo/root fooroot
|
||||
To mount /root from the instance foo onto the local fooroot directory.
|
||||
|
||||
incus file mount foo
|
||||
To start an SSH SFTP listener for the root filesystem of instance foo.`,
|
||||
))
|
||||
To start an SSH SFTP listener for the root filesystem of instance foo.`))
|
||||
|
||||
cli.AddStringFlag(cmd.Flags(), &c.flagListen, "listen", "", "", i18n.G("Setup SSH SFTP listener on address:port instead of mounting"))
|
||||
cli.AddBoolFlag(cmd.Flags(), &c.flagAuthNone, "no-auth", i18n.G("Disable authentication when using SSH SFTP listener"))
|
||||
@ -944,7 +948,7 @@ incus file mount foo
|
||||
}
|
||||
|
||||
func (c *cmdFileMount) run(cmd *cobra.Command, args []string) error {
|
||||
parsed, err := c.global.Parse(cmdFileMountUsage, cmd, args)
|
||||
parsed, err := cmdFileMountUsage.Parse(c.global.conf, cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -996,7 +1000,7 @@ func (c *cmdFileMount) run(cmd *cobra.Command, args []string) error {
|
||||
return fmt.Errorf(i18n.G("Failed connecting to instance SFTP: %w"), err)
|
||||
}
|
||||
|
||||
defer logger.WarnOnError(sftpConn.Close, "Failed to close SFTP connection")
|
||||
defer func() { _ = sftpConn.Close() }()
|
||||
|
||||
return sshfsMount(cmd.Context(), sftpConn, instanceName, instancePath, targetPath)
|
||||
}
|
||||
|
||||
@ -30,7 +30,6 @@ import (
|
||||
"github.com/lxc/incus/v7/shared/archive"
|
||||
"github.com/lxc/incus/v7/shared/ask"
|
||||
cli "github.com/lxc/incus/v7/shared/cmd"
|
||||
"github.com/lxc/incus/v7/shared/logger"
|
||||
"github.com/lxc/incus/v7/shared/osarch"
|
||||
"github.com/lxc/incus/v7/shared/subprocess"
|
||||
"github.com/lxc/incus/v7/shared/termios"
|
||||
@ -65,8 +64,7 @@ as a compressed tarball (or for split images, the concatenation of the
|
||||
metadata and rootfs tarballs).
|
||||
|
||||
Images can be referenced by their full hash, shortest unique partial
|
||||
hash or alias name (if one is set).`,
|
||||
))
|
||||
hash or alias name (if one is set).`))
|
||||
|
||||
// Alias
|
||||
imageAliasCmd := cmdImageAlias{global: c.global, image: c}
|
||||
@ -170,8 +168,7 @@ func (c *cmdImageCopy) command() *cobra.Command {
|
||||
`Copy images between servers
|
||||
|
||||
The auto-update flag instructs the server to keep this image up to date.
|
||||
It requires the source to be an alias and for it to be public.`,
|
||||
))
|
||||
It requires the source to be an alias and for it to be public.`))
|
||||
|
||||
cli.AddBoolFlag(cmd.Flags(), &c.flagPublic, "public", i18n.G("Make image public"))
|
||||
cli.AddBoolFlag(cmd.Flags(), &c.flagCopyAliases, "copy-aliases", i18n.G("Copy aliases from source"))
|
||||
@ -179,7 +176,7 @@ It requires the source to be an alias and for it to be public.`,
|
||||
cli.AddBoolFlag(cmd.Flags(), &c.flagAutoUpdate, "auto-update", i18n.G("Keep the image up to date after initial copy"))
|
||||
cli.AddStringArrayFlag(cmd.Flags(), &c.flagAliases, "alias", i18n.G("New aliases to add to the image"))
|
||||
cli.AddBoolFlag(cmd.Flags(), &c.flagVM, "vm", i18n.G("Copy virtual machine images"))
|
||||
cli.AddStringFlag(cmd.Flags(), &c.flagMode, "mode", "pull", "", i18n.G("Transfer mode. One of pull, push or relay"))
|
||||
cli.AddStringFlag(cmd.Flags(), &c.flagMode, "mode", "pull", "", i18n.G("Transfer mode. One of pull (default), push or relay"))
|
||||
cli.AddStringFlag(cmd.Flags(), &c.flagTargetProject, "target-project", "", "", i18n.G("Copy to a project different from the source"))
|
||||
cli.AddStringArrayFlag(cmd.Flags(), &c.flagProfile, "profile|p", i18n.G("Profile to apply to the new image"))
|
||||
cmd.RunE = c.run
|
||||
@ -201,7 +198,7 @@ It requires the source to be an alias and for it to be public.`,
|
||||
|
||||
func (c *cmdImageCopy) run(cmd *cobra.Command, args []string) error {
|
||||
conf := c.global.conf
|
||||
parsed, err := c.global.Parse(cmdImageCopyUsage, cmd, args)
|
||||
parsed, err := cmdImageCopyUsage.Parse(c.global.conf, cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -349,7 +346,7 @@ func (c *cmdImageDelete) command() *cobra.Command {
|
||||
}
|
||||
|
||||
func (c *cmdImageDelete) run(cmd *cobra.Command, args []string) error {
|
||||
parsed, err := c.global.Parse(cmdImageDeleteUsage, cmd, args)
|
||||
parsed, err := cmdImageDeleteUsage.Parse(c.global.conf, cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -398,8 +395,7 @@ func (c *cmdImageEdit) command() *cobra.Command {
|
||||
Launch a text editor to edit the properties
|
||||
|
||||
incus image edit <image> < image.yaml
|
||||
Load the image properties from a YAML file`,
|
||||
))
|
||||
Load the image properties from a YAML file`))
|
||||
|
||||
cmd.RunE = c.run
|
||||
|
||||
@ -421,12 +417,11 @@ func (c *cmdImageEdit) helpTemplate() string {
|
||||
###
|
||||
### Each property is represented by a single line:
|
||||
### An example would be:
|
||||
### description: My custom image`,
|
||||
)
|
||||
### description: My custom image`)
|
||||
}
|
||||
|
||||
func (c *cmdImageEdit) run(cmd *cobra.Command, args []string) error {
|
||||
parsed, err := c.global.Parse(cmdImageEditUsage, cmd, args)
|
||||
parsed, err := cmdImageEditUsage.Parse(c.global.conf, cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -463,7 +458,7 @@ func (c *cmdImageEdit) run(cmd *cobra.Command, args []string) error {
|
||||
}
|
||||
|
||||
brief := imgInfo.Writable()
|
||||
data, err := yaml.Dump(&brief, yaml.WithV2Defaults())
|
||||
data, err := yaml.Dump(&brief, yaml.V2)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -523,8 +518,7 @@ func (c *cmdImageExport) command() *cobra.Command {
|
||||
cmd.Long = cli.FormatSection(color.DescriptionPrefix, i18n.G(
|
||||
`Export and download images
|
||||
|
||||
The output target is optional and defaults to the working directory.`,
|
||||
))
|
||||
The output target is optional and defaults to the working directory.`))
|
||||
|
||||
cli.AddBoolFlag(cmd.Flags(), &c.flagVM, "vm", i18n.G("Query virtual machine images"))
|
||||
cmd.RunE = c.run
|
||||
@ -541,7 +535,7 @@ The output target is optional and defaults to the working directory.`,
|
||||
}
|
||||
|
||||
func (c *cmdImageExport) run(cmd *cobra.Command, args []string) error {
|
||||
parsed, err := c.global.Parse(cmdImageExportUsage, cmd, args)
|
||||
parsed, err := cmdImageExportUsage.Parse(c.global.conf, cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -581,14 +575,14 @@ func (c *cmdImageExport) run(cmd *cobra.Command, args []string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
defer logger.WarnOnError(dest.Close, "Failed to close file")
|
||||
defer func() { _ = dest.Close() }()
|
||||
|
||||
destRootfs, err := os.Create(targetRootfs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer logger.WarnOnError(destRootfs.Close, "Failed to close file")
|
||||
defer func() { _ = destRootfs.Close() }()
|
||||
|
||||
// Prepare the download request
|
||||
progress := cli.ProgressRenderer{
|
||||
@ -696,8 +690,7 @@ func (c *cmdImageImport) command() *cobra.Command {
|
||||
cmd.Long = cli.FormatSection(color.DescriptionPrefix, i18n.G(
|
||||
`Import image into the image store
|
||||
|
||||
Directory import is only available on Linux and must be performed as root.`,
|
||||
))
|
||||
Directory import is only available on Linux and must be performed as root.`))
|
||||
|
||||
cli.AddBoolFlag(cmd.Flags(), &c.flagPublic, "public", i18n.G("Make image public"))
|
||||
cli.AddBoolFlag(cmd.Flags(), &c.flagReuse, "reuse", i18n.G("If the image alias already exists, delete and create a new one"))
|
||||
@ -732,7 +725,7 @@ func (c *cmdImageImport) packImageDir(path string) (string, error) {
|
||||
return "", err
|
||||
}
|
||||
|
||||
defer logger.WarnOnError(outFile.Close, "Failed to close file")
|
||||
defer func() { _ = outFile.Close() }()
|
||||
|
||||
outFileName := outFile.Name()
|
||||
_, err = subprocess.RunCommand("tar", "-C", path, "--numeric-owner", "--restrict", "--force-local", "--xattrs", "-cJf", outFileName, "rootfs", "templates", "metadata.yaml")
|
||||
@ -746,7 +739,7 @@ func (c *cmdImageImport) packImageDir(path string) (string, error) {
|
||||
func (c *cmdImageImport) run(cmd *cobra.Command, args []string) error {
|
||||
// Do NOT blindly copy the following parsing line; it performs right-to-left parsing, which in
|
||||
// most cases is NOT what you want.
|
||||
parsed, err := c.global.Parse(cmdImageImportUsage, cmd, args, true)
|
||||
parsed, err := cmdImageImportUsage.Parse(c.global.conf, cmd, args, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -795,7 +788,7 @@ func (c *cmdImageImport) run(cmd *cobra.Command, args []string) error {
|
||||
return err
|
||||
}
|
||||
// remove temp file
|
||||
defer logger.WarnOnError(func() error { return os.Remove(imageFile) }, "Failed to remove temporary file")
|
||||
defer func() { _ = os.Remove(imageFile) }()
|
||||
}
|
||||
|
||||
meta, err = os.Open(imageFile)
|
||||
@ -803,7 +796,7 @@ func (c *cmdImageImport) run(cmd *cobra.Command, args []string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
defer logger.WarnOnError(meta.Close, "Failed to close file")
|
||||
defer func() { _ = meta.Close() }()
|
||||
|
||||
// Open rootfs
|
||||
if hasRootfsFile {
|
||||
@ -812,7 +805,7 @@ func (c *cmdImageImport) run(cmd *cobra.Command, args []string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
defer logger.WarnOnError(rootfs.Close, "Failed to close file")
|
||||
defer func() { _ = rootfs.Close() }()
|
||||
|
||||
_, ext, _, err := archive.DetectCompressionFile(rootfs)
|
||||
if err != nil {
|
||||
@ -907,8 +900,7 @@ func (c *cmdImageInfo) command() *cobra.Command {
|
||||
cmd.Use = cli.U("info", cmdImageInfoUsage...)
|
||||
cmd.Short = i18n.G("Show useful information about images")
|
||||
cmd.Long = cli.FormatSection(color.DescriptionPrefix, i18n.G(
|
||||
`Show useful information about images`,
|
||||
))
|
||||
`Show useful information about images`))
|
||||
|
||||
cli.AddBoolFlag(cmd.Flags(), &c.flagVM, "vm", i18n.G("Query virtual machine images"))
|
||||
cmd.RunE = c.run
|
||||
@ -925,7 +917,7 @@ func (c *cmdImageInfo) command() *cobra.Command {
|
||||
}
|
||||
|
||||
func (c *cmdImageInfo) run(cmd *cobra.Command, args []string) error {
|
||||
parsed, err := c.global.Parse(cmdImageInfoUsage, cmd, args)
|
||||
parsed, err := cmdImageInfoUsage.Parse(c.global.conf, cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -1061,6 +1053,7 @@ or csv format.
|
||||
Default column layout is: lfpdasu
|
||||
|
||||
Column shorthand chars:
|
||||
|
||||
l - Shortest image alias (and optionally number of other aliases)
|
||||
L - Newline-separated list of all image aliases
|
||||
f - Fingerprint (short)
|
||||
@ -1071,8 +1064,7 @@ Column shorthand chars:
|
||||
a - Architecture
|
||||
s - Size
|
||||
u - Upload date
|
||||
t - Type`,
|
||||
))
|
||||
t - Type`))
|
||||
|
||||
cli.AddStringFlag(cmd.Flags(), &c.flagColumns, "columns|c", defaultImagesColumns, "", i18n.G("Columns"))
|
||||
cli.AddStringFlag(cmd.Flags(), &c.flagFormat, "format|f", c.global.defaultListFormat(), "", i18n.G(`Format (csv|json|table|yaml|compact|markdown), use suffix ",noheader" to disable headers and ",header" to enable it if missing, e.g. csv,header`))
|
||||
@ -1292,7 +1284,7 @@ func (c *cmdImageList) imageShouldShow(filters []string, state *api.Image) bool
|
||||
}
|
||||
|
||||
func (c *cmdImageList) run(cmd *cobra.Command, args []string) error {
|
||||
parsed, err := c.global.Parse(cmdImageListUsage, cmd, args)
|
||||
parsed, err := cmdImageListUsage.Parse(c.global.conf, cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -1401,7 +1393,7 @@ func (c *cmdImageRefresh) command() *cobra.Command {
|
||||
}
|
||||
|
||||
func (c *cmdImageRefresh) run(cmd *cobra.Command, args []string) error {
|
||||
parsed, err := c.global.Parse(cmdImageRefreshUsage, cmd, args)
|
||||
parsed, err := cmdImageRefreshUsage.Parse(c.global.conf, cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -1483,7 +1475,7 @@ func (c *cmdImageShow) command() *cobra.Command {
|
||||
}
|
||||
|
||||
func (c *cmdImageShow) run(cmd *cobra.Command, args []string) error {
|
||||
parsed, err := c.global.Parse(cmdImageShowUsage, cmd, args)
|
||||
parsed, err := cmdImageShowUsage.Parse(c.global.conf, cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -1509,7 +1501,7 @@ func (c *cmdImageShow) run(cmd *cobra.Command, args []string) error {
|
||||
}
|
||||
|
||||
properties := info.Writable()
|
||||
data, err := yaml.Dump(&properties, yaml.WithV2Defaults())
|
||||
data, err := yaml.Dump(&properties, yaml.V2)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -1551,7 +1543,7 @@ func (c *cmdImageGetProp) command() *cobra.Command {
|
||||
}
|
||||
|
||||
func (c *cmdImageGetProp) run(cmd *cobra.Command, args []string) error {
|
||||
parsed, err := c.global.Parse(cmdImageGetPropUsage, cmd, args)
|
||||
parsed, err := cmdImageGetPropUsage.Parse(c.global.conf, cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -1637,7 +1629,7 @@ func (c *cmdImageSetProp) set(cmd *cobra.Command, parsed []*u.Parsed) error {
|
||||
}
|
||||
|
||||
func (c *cmdImageSetProp) run(cmd *cobra.Command, args []string) error {
|
||||
parsed, err := c.global.Parse(cmdImageSetPropUsage, cmd, args)
|
||||
parsed, err := cmdImageSetPropUsage.Parse(c.global.conf, cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -1673,7 +1665,7 @@ func (c *cmdImageUnsetProp) command() *cobra.Command {
|
||||
}
|
||||
|
||||
func (c *cmdImageUnsetProp) run(cmd *cobra.Command, args []string) error {
|
||||
parsed, err := c.global.Parse(cmdImageUnsetPropUsage, cmd, args)
|
||||
parsed, err := cmdImageUnsetPropUsage.Parse(c.global.conf, cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -1753,7 +1745,7 @@ This command will prompt for all of the metadata tarball fields:
|
||||
}
|
||||
|
||||
func (c *cmdImageGenerateMetadata) run(cmd *cobra.Command, args []string) error {
|
||||
parsed, err := c.global.Parse(cmdImageGenerateMetadataUsage, cmd, args)
|
||||
parsed, err := cmdImageGenerateMetadataUsage.Parse(c.global.conf, cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -1769,7 +1761,7 @@ func (c *cmdImageGenerateMetadata) run(cmd *cobra.Command, args []string) error
|
||||
return err
|
||||
}
|
||||
|
||||
defer logger.WarnOnError(metaFile.Close, "Failed to close file")
|
||||
defer metaFile.Close()
|
||||
|
||||
// Generate the metadata.
|
||||
timestamp := time.Now().UTC()
|
||||
@ -1834,7 +1826,7 @@ func (c *cmdImageGenerateMetadata) run(cmd *cobra.Command, args []string) error
|
||||
metadata.Properties["description"] = metaDescription
|
||||
|
||||
// Generate YAML.
|
||||
body, err := yaml.Dump(&metadata, yaml.WithV2Defaults())
|
||||
body, err := yaml.Dump(&metadata, yaml.V2)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -71,8 +71,7 @@ func (c *cmdImageAliasCreate) command() *cobra.Command {
|
||||
cmd.Aliases = []string{"add"}
|
||||
cmd.Short = i18n.G("Create aliases for existing images")
|
||||
cmd.Long = cli.FormatSection(color.DescriptionPrefix, i18n.G(
|
||||
`Create aliases for existing images`,
|
||||
))
|
||||
`Create aliases for existing images`))
|
||||
|
||||
cli.AddStringFlag(cmd.Flags(), &c.flagDescription, "description", "", "", i18n.G("Image alias description"))
|
||||
|
||||
@ -99,7 +98,7 @@ func (c *cmdImageAliasCreate) command() *cobra.Command {
|
||||
}
|
||||
|
||||
func (c *cmdImageAliasCreate) run(cmd *cobra.Command, args []string) error {
|
||||
parsed, err := c.global.Parse(cmdImageAliasCreateUsage, cmd, args)
|
||||
parsed, err := cmdImageAliasCreateUsage.Parse(c.global.conf, cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -143,7 +142,7 @@ func (c *cmdImageAliasDelete) command() *cobra.Command {
|
||||
}
|
||||
|
||||
func (c *cmdImageAliasDelete) run(cmd *cobra.Command, args []string) error {
|
||||
parsed, err := c.global.Parse(cmdImageAliasDeleteUsage, cmd, args)
|
||||
parsed, err := cmdImageAliasDeleteUsage.Parse(c.global.conf, cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -205,8 +204,7 @@ Pre-defined column shorthand chars:
|
||||
a - Alias
|
||||
f - Fingerprint
|
||||
t - Type
|
||||
d - Description`,
|
||||
))
|
||||
d - Description`))
|
||||
cli.AddStringFlag(cmd.Flags(), &c.flagFormat, "format|f", c.global.defaultListFormat(), "", i18n.G(`Format (csv|json|table|yaml|compact|markdown), use suffix ",noheader" to disable headers and ",header" to enable it if missing, e.g. csv,header`))
|
||||
cli.AddStringFlag(cmd.Flags(), &c.flagColumns, "columns|c", defaultImageAliasColumns, "", i18n.G("Columns"))
|
||||
|
||||
@ -290,7 +288,7 @@ func (c *cmdImageAliasList) descriptionColumntData(imageAlias api.ImageAliasesEn
|
||||
}
|
||||
|
||||
func (c *cmdImageAliasList) run(cmd *cobra.Command, args []string) error {
|
||||
parsed, err := c.global.Parse(cmdImageAliasListUsage, cmd, args)
|
||||
parsed, err := cmdImageAliasListUsage.Parse(c.global.conf, cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -377,7 +375,7 @@ func (c *cmdImageAliasRename) command() *cobra.Command {
|
||||
}
|
||||
|
||||
func (c *cmdImageAliasRename) run(cmd *cobra.Command, args []string) error {
|
||||
parsed, err := c.global.Parse(cmdImageAliasRenameUsage, cmd, args)
|
||||
parsed, err := cmdImageAliasRenameUsage.Parse(c.global.conf, cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -12,7 +12,6 @@ import (
|
||||
"github.com/lxc/incus/v7/internal/i18n"
|
||||
cli "github.com/lxc/incus/v7/shared/cmd"
|
||||
"github.com/lxc/incus/v7/shared/ioprogress"
|
||||
"github.com/lxc/incus/v7/shared/logger"
|
||||
"github.com/lxc/incus/v7/shared/units"
|
||||
)
|
||||
|
||||
@ -31,12 +30,10 @@ func (c *cmdImport) command() *cobra.Command {
|
||||
cmd.Use = cli.U("import", cmdImportUsage...)
|
||||
cmd.Short = i18n.G("Import instance backups")
|
||||
cmd.Long = cli.FormatSection(color.DescriptionPrefix, i18n.G(
|
||||
`Import backups of instances including their snapshots.`,
|
||||
))
|
||||
`Import backups of instances including their snapshots.`))
|
||||
cmd.Example = cli.FormatSection("", i18n.G(
|
||||
`incus import backup0.tar.gz
|
||||
Create a new instance using backup0.tar.gz as the source.`,
|
||||
))
|
||||
Create a new instance using backup0.tar.gz as the source.`))
|
||||
|
||||
cmd.RunE = c.run
|
||||
cli.AddStringFlag(cmd.Flags(), &c.flagStorage, "storage|s", "", "", i18n.G("Storage pool name"))
|
||||
@ -47,7 +44,7 @@ func (c *cmdImport) command() *cobra.Command {
|
||||
}
|
||||
|
||||
func (c *cmdImport) run(cmd *cobra.Command, args []string) error {
|
||||
parsed, err := c.global.Parse(cmdImportUsage, cmd, args)
|
||||
parsed, err := cmdImportUsage.Parse(c.global.conf, cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -57,18 +54,15 @@ func (c *cmdImport) run(cmd *cobra.Command, args []string) error {
|
||||
instanceName := parsed[2].String
|
||||
|
||||
var file *os.File
|
||||
usePercentage := true
|
||||
if isStdin(backupFile) {
|
||||
file = os.Stdin
|
||||
usePercentage = false
|
||||
} else {
|
||||
file, err = os.Open(backupFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// The HTTP transport closes the request body, so only warn on unexpected errors.
|
||||
defer logger.WarnOnErrorExcept(file.Close, []error{os.ErrClosed}, "Failed to close file")
|
||||
defer func() { _ = file.Close() }()
|
||||
}
|
||||
|
||||
fstat, err := file.Stat()
|
||||
@ -86,12 +80,8 @@ func (c *cmdImport) run(cmd *cobra.Command, args []string) error {
|
||||
ReadCloser: file,
|
||||
Tracker: &ioprogress.ProgressTracker{
|
||||
Length: fstat.Size(),
|
||||
Handler: func(v int64, speed int64) {
|
||||
if usePercentage {
|
||||
progress.UpdateProgress(ioprogress.ProgressData{Text: fmt.Sprintf("%d%% (%s/s)", v, units.GetByteSizeString(speed, 2))})
|
||||
} else {
|
||||
progress.UpdateProgress(ioprogress.ProgressData{Text: fmt.Sprintf("%s (%s/s)", units.GetByteSizeString(v, 2), units.GetByteSizeString(speed, 2))})
|
||||
}
|
||||
Handler: func(percent int64, speed int64) {
|
||||
progress.UpdateProgress(ioprogress.ProgressData{Text: fmt.Sprintf("%d%% (%s/s)", percent, units.GetByteSizeString(speed, 2))})
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@ -25,11 +25,10 @@ import (
|
||||
type cmdInfo struct {
|
||||
global *cmdGlobal
|
||||
|
||||
flagShowAccess bool
|
||||
flagShowLog string
|
||||
flagResources bool
|
||||
flagShowSensitive bool
|
||||
flagTarget string
|
||||
flagShowAccess bool
|
||||
flagShowLog string
|
||||
flagResources bool
|
||||
flagTarget string
|
||||
}
|
||||
|
||||
var cmdInfoUsage = u.Usage{u.Instance.Optional().Remote()}
|
||||
@ -39,21 +38,18 @@ func (c *cmdInfo) command() *cobra.Command {
|
||||
cmd.Use = cli.U("info", cmdInfoUsage...)
|
||||
cmd.Short = i18n.G("Show instance or server information")
|
||||
cmd.Long = cli.FormatSection(color.DescriptionPrefix, i18n.G(
|
||||
`Show instance or server information`,
|
||||
))
|
||||
`Show instance or server information`))
|
||||
cmd.Example = cli.FormatSection("", i18n.G(
|
||||
`incus info [<remote>:]<instance> [--show-log]
|
||||
For instance information.
|
||||
|
||||
incus info [<remote>:] [--resources]
|
||||
For server information.`,
|
||||
))
|
||||
For server information.`))
|
||||
|
||||
cmd.RunE = c.run
|
||||
cli.AddBoolFlag(cmd.Flags(), &c.flagShowAccess, "show-access", i18n.G("Show the instance's access list"))
|
||||
cli.AddStringFlag(cmd.Flags(), &c.flagShowLog, "show-log", "", "default", i18n.G("Show the instance's recent log entries"))
|
||||
cli.AddBoolFlag(cmd.Flags(), &c.flagResources, "resources", i18n.G("Show the resources available to the server"))
|
||||
cli.AddBoolFlag(cmd.Flags(), &c.flagShowSensitive, "show-sensitive", i18n.G("Show the server's sensitive information (full certificates, private keys and the API extension list)"))
|
||||
cli.AddStringFlag(cmd.Flags(), &c.flagTarget, "target", "", "", i18n.G("Cluster member name"))
|
||||
|
||||
cmd.ValidArgsFunction = func(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
@ -68,7 +64,7 @@ incus info [<remote>:] [--resources]
|
||||
}
|
||||
|
||||
func (c *cmdInfo) run(cmd *cobra.Command, args []string) error {
|
||||
parsed, err := c.global.Parse(cmdInfoUsage, cmd, args)
|
||||
parsed, err := cmdInfoUsage.Parse(c.global.conf, cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -87,7 +83,7 @@ func (c *cmdInfo) run(cmd *cobra.Command, args []string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
data, err := yaml.Dump(access, yaml.WithV2Defaults())
|
||||
data, err := yaml.Dump(access, yaml.V2)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -328,29 +324,13 @@ func (c *cmdInfo) renderCPU(cpu api.ResourcesCPUSocket, prefix string) {
|
||||
}
|
||||
}
|
||||
|
||||
// Only mention clusters when there is more than one.
|
||||
clusters := map[uint64]bool{}
|
||||
for _, core := range cpu.Cores {
|
||||
clusters[core.Cluster] = true
|
||||
}
|
||||
|
||||
// Group the cores per cluster when relevant.
|
||||
coreIndent := ""
|
||||
if len(clusters) > 1 {
|
||||
coreIndent = " "
|
||||
}
|
||||
|
||||
fmt.Print(prefix + i18n.G("Cores:") + "\n")
|
||||
for i, core := range cpu.Cores {
|
||||
if len(clusters) > 1 && (i == 0 || core.Cluster != cpu.Cores[i-1].Cluster) {
|
||||
fmt.Printf(prefix+" - "+i18n.G("Cluster %d")+"\n", core.Cluster)
|
||||
}
|
||||
|
||||
fmt.Printf(prefix+coreIndent+" - "+i18n.G("Core %d")+"\n", core.Core)
|
||||
fmt.Printf(prefix+coreIndent+" "+i18n.G("Frequency: %vMhz")+"\n", core.Frequency)
|
||||
fmt.Print(prefix + coreIndent + " " + i18n.G("Threads:") + "\n")
|
||||
for _, core := range cpu.Cores {
|
||||
fmt.Printf(prefix+" - "+i18n.G("Core %d")+"\n", core.Core)
|
||||
fmt.Printf(prefix+" "+i18n.G("Frequency: %vMhz")+"\n", core.Frequency)
|
||||
fmt.Print(prefix + " " + i18n.G("Threads:") + "\n")
|
||||
for _, thread := range core.Threads {
|
||||
fmt.Printf(prefix+coreIndent+" - "+i18n.G("%d (id: %d, online: %v, NUMA node: %v)")+"\n", thread.Thread, thread.ID, thread.Online, thread.NUMANode)
|
||||
fmt.Printf(prefix+" - "+i18n.G("%d (id: %d, online: %v, NUMA node: %v)")+"\n", thread.Thread, thread.ID, thread.Online, thread.NUMANode)
|
||||
}
|
||||
}
|
||||
|
||||
@ -639,13 +619,7 @@ func (c *cmdInfo) remoteInfo(d incus.InstanceServer) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// Show the filtered output unless --show-sensitive is passed by the user.
|
||||
var out any = &serverStatus
|
||||
if !c.flagShowSensitive {
|
||||
out = serverStatus.Filtered()
|
||||
}
|
||||
|
||||
data, err := yaml.Dump(out, yaml.WithV2Defaults())
|
||||
data, err := yaml.Dump(&serverStatus, yaml.V2)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -725,18 +699,18 @@ func (c *cmdInfo) instanceInfo(d incus.InstanceServer, name string, showLog stri
|
||||
fmt.Printf(" "+i18n.G("Processes: %d")+"\n", inst.State.Processes)
|
||||
|
||||
// Disk usage
|
||||
var diskInfo strings.Builder
|
||||
diskInfo := ""
|
||||
if inst.State.Disk != nil {
|
||||
for entry, disk := range inst.State.Disk {
|
||||
if disk.Usage > 0 {
|
||||
fmt.Fprintf(&diskInfo, " %s: %s\n", entry, units.GetByteSizeStringIEC(disk.Usage, 2))
|
||||
if disk.Usage != 0 {
|
||||
diskInfo += fmt.Sprintf(" %s: %s\n", entry, units.GetByteSizeStringIEC(disk.Usage, 2))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if diskInfo.String() != "" {
|
||||
if diskInfo != "" {
|
||||
fmt.Printf(" %s\n", i18n.G("Disk usage:"))
|
||||
fmt.Print(diskInfo.String())
|
||||
fmt.Print(diskInfo)
|
||||
}
|
||||
|
||||
// CPU usage
|
||||
@ -774,7 +748,7 @@ func (c *cmdInfo) instanceInfo(d incus.InstanceServer, name string, showLog stri
|
||||
}
|
||||
|
||||
// Network usage and IP info
|
||||
var networkInfo strings.Builder
|
||||
networkInfo := ""
|
||||
if inst.State.Network != nil {
|
||||
network := inst.State.Network
|
||||
|
||||
@ -786,41 +760,41 @@ func (c *cmdInfo) instanceInfo(d incus.InstanceServer, name string, showLog stri
|
||||
sort.Strings(netNames)
|
||||
|
||||
for _, netName := range netNames {
|
||||
fmt.Fprintf(&networkInfo, " %s:\n", netName)
|
||||
fmt.Fprintf(&networkInfo, " %s: %s\n", i18n.G("Type"), network[netName].Type)
|
||||
fmt.Fprintf(&networkInfo, " %s: %s\n", i18n.G("State"), strings.ToUpper(network[netName].State))
|
||||
networkInfo += fmt.Sprintf(" %s:\n", netName)
|
||||
networkInfo += fmt.Sprintf(" %s: %s\n", i18n.G("Type"), network[netName].Type)
|
||||
networkInfo += fmt.Sprintf(" %s: %s\n", i18n.G("State"), strings.ToUpper(network[netName].State))
|
||||
if network[netName].HostName != "" {
|
||||
fmt.Fprintf(&networkInfo, " %s: %s\n", i18n.G("Host interface"), network[netName].HostName)
|
||||
networkInfo += fmt.Sprintf(" %s: %s\n", i18n.G("Host interface"), network[netName].HostName)
|
||||
}
|
||||
|
||||
if network[netName].Hwaddr != "" {
|
||||
fmt.Fprintf(&networkInfo, " %s: %s\n", i18n.G("MAC address"), network[netName].Hwaddr)
|
||||
networkInfo += fmt.Sprintf(" %s: %s\n", i18n.G("MAC address"), network[netName].Hwaddr)
|
||||
}
|
||||
|
||||
if network[netName].Mtu != 0 {
|
||||
fmt.Fprintf(&networkInfo, " %s: %d\n", i18n.G("MTU"), network[netName].Mtu)
|
||||
networkInfo += fmt.Sprintf(" %s: %d\n", i18n.G("MTU"), network[netName].Mtu)
|
||||
}
|
||||
|
||||
fmt.Fprintf(&networkInfo, " %s: %s\n", i18n.G("Bytes received"), units.GetByteSizeString(network[netName].Counters.BytesReceived, 2))
|
||||
fmt.Fprintf(&networkInfo, " %s: %s\n", i18n.G("Bytes sent"), units.GetByteSizeString(network[netName].Counters.BytesSent, 2))
|
||||
fmt.Fprintf(&networkInfo, " %s: %d\n", i18n.G("Packets received"), network[netName].Counters.PacketsReceived)
|
||||
fmt.Fprintf(&networkInfo, " %s: %d\n", i18n.G("Packets sent"), network[netName].Counters.PacketsSent)
|
||||
networkInfo += fmt.Sprintf(" %s: %s\n", i18n.G("Bytes received"), units.GetByteSizeString(network[netName].Counters.BytesReceived, 2))
|
||||
networkInfo += fmt.Sprintf(" %s: %s\n", i18n.G("Bytes sent"), units.GetByteSizeString(network[netName].Counters.BytesSent, 2))
|
||||
networkInfo += fmt.Sprintf(" %s: %d\n", i18n.G("Packets received"), network[netName].Counters.PacketsReceived)
|
||||
networkInfo += fmt.Sprintf(" %s: %d\n", i18n.G("Packets sent"), network[netName].Counters.PacketsSent)
|
||||
|
||||
fmt.Fprintf(&networkInfo, " %s:\n", i18n.G("IP addresses"))
|
||||
networkInfo += fmt.Sprintf(" %s:\n", i18n.G("IP addresses"))
|
||||
|
||||
for _, addr := range network[netName].Addresses {
|
||||
if addr.Family == "inet" {
|
||||
fmt.Fprintf(&networkInfo, " %s: %s/%s (%s)\n", addr.Family, addr.Address, addr.Netmask, addr.Scope)
|
||||
networkInfo += fmt.Sprintf(" %s: %s/%s (%s)\n", addr.Family, addr.Address, addr.Netmask, addr.Scope)
|
||||
} else {
|
||||
fmt.Fprintf(&networkInfo, " %s: %s/%s (%s)\n", addr.Family, addr.Address, addr.Netmask, addr.Scope)
|
||||
networkInfo += fmt.Sprintf(" %s: %s/%s (%s)\n", addr.Family, addr.Address, addr.Netmask, addr.Scope)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if networkInfo.String() != "" {
|
||||
if networkInfo != "" {
|
||||
fmt.Printf(" %s\n", i18n.G("Network usage:"))
|
||||
fmt.Print(networkInfo.String())
|
||||
fmt.Print(networkInfo)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -23,25 +23,22 @@ func (c *cmdLaunch) command() *cobra.Command {
|
||||
cmd.Use = cli.U("launch", cmdCreateUsage...)
|
||||
cmd.Short = i18n.G("Create and start instances from images")
|
||||
cmd.Long = cli.FormatSection(color.DescriptionPrefix, i18n.G(
|
||||
`Create and start instances from images`,
|
||||
))
|
||||
`Create and start instances from images`))
|
||||
cmd.Example = cli.FormatSection("", i18n.G(
|
||||
`incus launch images:debian/12 c1
|
||||
Create and start a container named "c1"
|
||||
`incus launch images:debian/12 u1
|
||||
Create and start a container named u1
|
||||
|
||||
incus launch images:debian/12 c2 < config.yaml
|
||||
Create and start a container named "c2" with configuration from config.yaml
|
||||
incus launch images:debian/12 u1 < config.yaml
|
||||
Create and start a container with configuration from config.yaml
|
||||
|
||||
incus launch images:debian/12 c3 -t aws:t2.micro
|
||||
Create and start a container named "c3" using the same size as an AWS t2.micro (1 vCPU, 1GiB of RAM)
|
||||
Find the list of supported instance types here: https://images.linuxcontainers.org/meta/instance-types/
|
||||
incus launch images:debian/12 u2 -t aws:t2.micro
|
||||
Create and start a container using the same size as an AWS t2.micro (1 vCPU, 1GiB of RAM)
|
||||
|
||||
incus launch images:debian/12 v1 --vm -c limits.cpu=4 -c limits.memory=4GiB
|
||||
Create and start a virtual machine named "v1" with 4 vCPUs and 4GiB of RAM
|
||||
Create and start a virtual machine with 4 vCPUs and 4GiB of RAM
|
||||
|
||||
incus launch images:debian/12 v2 --vm -d root,size=50GiB -d root,io.bus=nvme
|
||||
Create and start a virtual machine named "v2", overriding the disk size and bus`,
|
||||
))
|
||||
Create and start a virtual machine, overriding the disk size and bus`))
|
||||
cmd.Hidden = false
|
||||
|
||||
cmd.RunE = c.run
|
||||
@ -61,7 +58,7 @@ incus launch images:debian/12 v2 --vm -d root,size=50GiB -d root,io.bus=nvme
|
||||
|
||||
func (c *cmdLaunch) run(cmd *cobra.Command, args []string) error {
|
||||
conf := c.global.conf
|
||||
parsed, err := c.global.Parse(cmdCreateUsage, cmd, args)
|
||||
parsed, err := cmdCreateUsage.Parse(conf, cmd, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -82,7 +79,6 @@ func (c *cmdLaunch) run(cmd *cobra.Command, args []string) error {
|
||||
console := cmdConsole{}
|
||||
console.global = c.global
|
||||
console.flagType = c.flagConsole
|
||||
console.withLog = c.flagConsole == "console"
|
||||
|
||||
consoleErr := console.console(d, instanceName)
|
||||
if consoleErr != nil {
|
||||
@ -149,7 +145,6 @@ func (c *cmdLaunch) run(cmd *cobra.Command, args []string) error {
|
||||
console := cmdConsole{}
|
||||
console.global = c.global
|
||||
console.flagType = c.flagConsole
|
||||
console.withLog = c.flagConsole == "console"
|
||||
|
||||
consoleErr := console.console(d, instanceName)
|
||||
if consoleErr != nil {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user