Compare commits

..

No commits in common. "main" and "v7.0.0" have entirely different histories.
main ... v7.0.0

735 changed files with 47297 additions and 83135 deletions

View File

@ -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

View File

@ -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}"

View File

@ -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>

View File

@ -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"

View File

@ -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>

View File

@ -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"

View File

@ -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/

View File

@ -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:

View File

@ -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

View File

@ -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

View File

@ -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
View File

@ -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

View File

@ -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$

View File

@ -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

View File

@ -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.

View File

@ -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

View File

@ -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}'

View File

@ -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.

View File

@ -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)
}

View File

@ -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{

View File

@ -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
}

View File

@ -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)

View File

@ -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)

View File

@ -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 {

View File

@ -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

View 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

View File

@ -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)
}

View File

@ -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 != "" {

View File

@ -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)
}

View File

@ -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

View File

@ -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:

View File

@ -9,5 +9,4 @@ var Imports = []string{
"fmt",
"strings",
"github.com/mattn/go-sqlite3",
"github.com/google/uuid",
}

View File

@ -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.

View File

@ -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)")

View File

@ -578,7 +578,6 @@ func validateFieldConfig(config url.Values) error {
"join",
"leftjoin",
"joinon",
"joinas",
"omit":
_, err := exactlyOneValue(tag, values)

View File

@ -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":

View File

@ -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 {

View File

@ -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"
}

View File

@ -33,7 +33,6 @@ var api10 = []APIEndpoint{
operationCmd,
operationWebsocket,
operationWait,
portForwardCmd,
sftpCmd,
stateCmd,
}

View File

@ -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 {

View File

@ -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 {

View File

@ -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
}

View File

@ -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 {

View File

@ -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")

View File

@ -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.

View File

@ -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()

View File

@ -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)
}

View File

@ -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
}

View File

@ -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 {

View File

@ -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
}

View File

@ -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)

View File

@ -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

View File

@ -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})

View File

@ -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 ""
}

View File

@ -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)

View 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 ""
}

View File

@ -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)

View File

@ -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.

View File

@ -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

View File

@ -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
}

View File

@ -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 {

View File

@ -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
}

View File

@ -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)

View File

@ -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
}

View File

@ -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())

View File

@ -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

View File

@ -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
}

View File

@ -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)
}

View File

@ -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

View File

@ -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}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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()
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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)

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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 {

View File

@ -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
}

View File

@ -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
View 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
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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

View File

@ -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

View File

@ -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.

View File

@ -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)
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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))})
},
},
},

View File

@ -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)
}
}

View File

@ -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