Compare commits

..

No commits in common. "main" and "v6.23.0" have entirely different histories.

1120 changed files with 68778 additions and 123388 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

@ -2,12 +2,6 @@ name: Bug report
description: File a bug report.
type: bug
body:
- type: markdown
attributes:
value: |
> [!NOTE]
> Thank you for taking the time to fill out this bug report. As this issue will be read and debugged by humans, we kindly ask you to refrain from using AI tools to try to interpret your problem or suggest patches, as per our [contribution guidelines](https://github.com/lxc/incus/blob/main/CONTRIBUTING.md).
- type: checkboxes
attributes:
label: Is there an existing issue for this?

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,118 +11,24 @@ 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: Run GoReleaser
uses: goreleaser/goreleaser-action@v7
with:
@ -131,16 +37,9 @@ jobs:
args: release --clean
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
INCUS_VERSION: ${{ needs.version.outputs.display }}
INCUS_VERSION: "6.22"
- 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

@ -1,32 +0,0 @@
name: Cleanup slop
on:
issues:
types:
- opened
permissions:
issues: write
jobs:
close-untyped:
name: Close issue if type is missing
if: ${{ !github.event.issue.pull_request && !github.event.issue.type && github.event.issue.author_association != 'MEMBER' && github.event.issue.author_association != 'OWNER' }}
runs-on: ubuntu-latest
steps:
- name: Close issue
uses: actions/github-script@v9
with:
script: |
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body: "Issues must be created through the [GitHub web interface](https://github.com/lxc/incus/issues/new/choose). Closing automatically."
});
await github.rest.issues.update({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
state: "closed",
});

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,33 +265,16 @@ jobs:
if: matrix.go == 'tip'
- name: Install dependencies
env:
GITHUB_TOKEN: ${{ github.token }}
run: |
set -x
# Configure ppa:ubuntu-lxc/daily directly
# (apt-add-repository relies on the Launchpad API which is unreliable).
sudo install -d -m 0755 /etc/apt/keyrings
codename="$(lsb_release -cs)"
curl -fsSL "https://keyserver.ubuntu.com/pks/lookup?op=get&options=mr&search=0xE9C00C1B1B59A86C2CFEE6990CE27B8C4122B4B7" | sudo tee /etc/apt/keyrings/ubuntu-lxc-daily.asc > /dev/null
sudo tee /etc/apt/sources.list.d/ubuntu-lxc-daily.sources > /dev/null <<EOF
Types: deb
URIs: https://ppa.launchpadcontent.net/ubuntu-lxc/daily/ubuntu
Suites: ${codename}
Components: main
Signed-By: /etc/apt/keyrings/ubuntu-lxc-daily.asc
EOF
sudo add-apt-repository ppa:ubuntu-lxc/daily -y --no-update
sudo add-apt-repository ppa:cowsql/stable -y --no-update
sudo apt-get update
sudo systemctl mask lxc.service lxc-net.service
sudo apt-get install --no-install-recommends -y \
apparmor \
autoconf \
automake \
bsdextrautils \
bzip2 \
curl \
@ -306,15 +283,14 @@ jobs:
libacl1-dev \
libcap-dev \
libdbus-1-dev \
libcowsql-dev \
libelf-dev \
liblxc-dev \
liblz4-dev \
libseccomp-dev \
libselinux-dev \
libsqlite3-dev \
libtool \
libudev-dev \
libuv1-dev \
linux-modules-extra-$(uname -r) \
llvm \
make \
@ -351,42 +327,31 @@ 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 minio.
curl -sSfL https://dl.min.io/server/minio/release/linux-$(dpkg --print-architecture)/archive/minio_20240116160738.0.0_$(dpkg --print-architecture).deb --output /tmp/minio.deb
sudo apt-get install /tmp/minio.deb --yes
# Download MinIO client
curl -sSfL https://dl.min.io/client/mc/release/linux-$(dpkg --print-architecture)/archive/mc.RELEASE.2024-01-16T16-06-34Z --output /tmp/mc
sudo mv /tmp/mc /usr/local/bin/
sudo chmod +x /usr/local/bin/mc
# 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
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: Run Incus build
env:
CGO_LDFLAGS_ALLOW: "(-Wl,-wrap,pthread_create)|(-Wl,-z,now)"
run: |
make
@ -453,18 +418,7 @@ jobs:
run: |
set -x
# Configure ppa:linbit/linbit-drbd9-stack directly
# (apt-add-repository relies on the Launchpad API which is unreliable).
sudo install -d -m 0755 /etc/apt/keyrings
curl -fsSL "https://keyserver.ubuntu.com/pks/lookup?op=get&options=mr&search=0xCC1B5A793C04BB3905AD837734893610CEAA9512" | sudo tee /etc/apt/keyrings/linbit-drbd9-stack.asc > /dev/null
sudo tee /etc/apt/sources.list.d/linbit-drbd9-stack.sources > /dev/null <<EOF
Types: deb
URIs: https://ppa.launchpadcontent.net/linbit/linbit-drbd9-stack/ubuntu
Suites: $(lsb_release -cs)
Components: main
Signed-By: /etc/apt/keyrings/linbit-drbd9-stack.asc
EOF
sudo apt-get update
sudo add-apt-repository ppa:linbit/linbit-drbd9-stack -y
# Install everything required to compile DRBD and run LINSTOR tools.
sudo apt-get install --no-install-recommends -y \
@ -514,7 +468,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 }} ./main.sh ${{ matrix.suite }}
client:
name: Client
@ -532,10 +486,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 +533,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,17 +569,17 @@ 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
- name: Install dependencies
run: |
sudo apt-get install -y aspell aspell-en ruby
sudo gem install --no-document mdl
sudo apt-get install aspell aspell-en
sudo snap install mdl
- name: Run markdown linter
run: |

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

309
Makefile
View File

@ -23,13 +23,11 @@ else
COWSQL_PATH=$(GOPATH)/deps/cowsql
endif
# section(Build): Build Incus
# raft
.PHONY: default
default: build
.PHONY: build
# doc: Build all Incus binaries (same as make and make default)
build:
ifeq "$(TAG_SQLITE3)" ""
@echo "Missing cowsql, run \"make deps\" to setup."
@ -42,66 +40,21 @@ endif
@echo "Incus built successfully"
.PHONY: client
# doc: Build the Incus client
client:
$(GO) install -v -tags "$(TAG_SQLITE3)" $(DEBUG) ./cmd/incus
@echo "Incus client built successfully"
.PHONY: incus-agent
# doc: Build the Incus agent
incus-agent:
CGO_ENABLED=0 $(GO) install -v -tags agent,netgo ./cmd/incus-agent
@echo "Incus agent built successfully"
.PHONY: incus-migrate
# doc: Build the Incus migration tool
incus-migrate:
CGO_ENABLED=0 $(GO) install -v -tags netgo ./cmd/incus-migrate
@echo "Incus migration tool built successfully"
.PHONY: debug
# doc: Build Incus in debug mode
debug:
ifeq "$(TAG_SQLITE3)" ""
@echo "Missing custom libsqlite3, run \"make deps\" to setup."
exit 1
endif
CC="$(CC)" CGO_LDFLAGS_ALLOW="$(CGO_LDFLAGS_ALLOW)" $(GO) install -v -tags "$(TAG_SQLITE3) logdebug" $(DEBUG) ./...
CGO_ENABLED=0 $(GO) install -v -tags "netgo,logdebug" ./cmd/incus-migrate
CGO_ENABLED=0 $(GO) install -v -tags "agent,netgo,logdebug" ./cmd/incus-agent
@echo "Incus built successfully"
.PHONY: nocache
# doc: Build Incus ignoring the local Go cache
nocache:
ifeq "$(TAG_SQLITE3)" ""
@echo "Missing custom libsqlite3, run \"make deps\" to setup."
exit 1
endif
CC="$(CC)" CGO_LDFLAGS_ALLOW="$(CGO_LDFLAGS_ALLOW)" $(GO) install -a -v -tags "$(TAG_SQLITE3)" $(DEBUG) ./...
CGO_ENABLED=0 $(GO) install -a -v -tags netgo ./cmd/incus-migrate
CGO_ENABLED=0 $(GO) install -a -v -tags agent,netgo ./cmd/incus-agent
@echo "Incus built successfully"
.PHONY: race
# doc: Build Incus in race condition detection mode
race:
ifeq "$(TAG_SQLITE3)" ""
@echo "Missing custom libsqlite3, run \"make deps\" to setup."
exit 1
endif
CC="$(CC)" CGO_LDFLAGS_ALLOW="$(CGO_LDFLAGS_ALLOW)" $(GO) install -race -v -tags "$(TAG_SQLITE3)" $(DEBUG) ./...
CGO_ENABLED=0 $(GO) install -v -tags netgo ./cmd/incus-migrate
CGO_ENABLED=0 $(GO) install -v -tags agent,netgo ./cmd/incus-agent
@echo "Incus built successfully"
# section(Dependencies): Manage Incus dependencies
.PHONY: deps
# doc: Build Incus dependencies
deps:
@if [ ! -e "$(RAFT_PATH)" ]; then \
git clone --depth=1 "https://github.com/cowsql/raft" "$(RAFT_PATH)"; \
@ -135,22 +88,18 @@ deps:
@echo "export CGO_LDFLAGS_ALLOW=\"(-Wl,-wrap,pthread_create)|(-Wl,-z,now)\""
.PHONY: update-gomod
# doc: Update Go dependencies
update-gomod:
ifneq "$(INCUS_OFFLINE)" ""
@echo "The update-gomod target cannot be run in offline mode."
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"
# section(Schemas): Update Incus data schemas
.PHONY: update-ovsdb
# doc: Update OVSDB schema
update-ovsdb:
go install github.com/ovn-kubernetes/libovsdb/cmd/modelgen@main
@ -173,12 +122,10 @@ update-ovsdb:
rm internal/server/network/ovn/schema/*.json
.PHONY: update-protobuf
# doc: Update Protobuf schema
update-protobuf:
protoc --go_out=. ./internal/migration/migrate.proto
.PHONY: update-schema
# doc: Update database schema
update-schema:
cd cmd/generate-database && $(GO) build -o $(GOPATH)/bin/generate-database -tags "$(TAG_SQLITE3)" $(DEBUG) && cd -
$(GO) generate ./...
@ -187,7 +134,6 @@ update-schema:
@echo "Code generation completed"
.PHONY: update-api
# doc: Update API schema
update-api:
ifeq "$(INCUS_OFFLINE)" ""
(cd / ; $(GO) install -v -x github.com/go-swagger/go-swagger/cmd/swagger@master)
@ -195,29 +141,12 @@ endif
swagger generate spec -o doc/rest-api.yaml -w ./cmd/incusd -m
.PHONY: update-metadata
# doc: Update configuration metadata
update-metadata: build
@echo "Generating golang documentation metadata"
cd cmd/generate-config && CGO_ENABLED=0 $(GO) build -o $(GOPATH)/bin/generate-config
$(GOPATH)/bin/generate-config . --json ./internal/server/metadata/configuration.json --txt ./doc/config_options.txt
# OpenFGA Syntax Transformer: https://github.com/openfga/syntax-transformer
.PHONY: update-openfga
# doc: Update OpenFGA schema
update-openfga:
ifeq ($(shell command -v fga),)
(cd / ; $(GO) install -v -x github.com/openfga/cli/cmd/fga@latest)
endif
@printf 'package auth\n\n// Code generated by Makefile; DO NOT EDIT.\n\nvar authModel = `%s`\n' '$(shell fga model transform --file=./internal/server/auth/driver_openfga_model.openfga | jq -c)' > ./internal/server/auth/driver_openfga_model.go
# section(Documentation): Build Incus documentation
.PHONY: doc
# doc: Setup the build environment and build the documentation
doc: doc-setup doc-incremental
.PHONY: doc-setup
# doc: Setup a documentation build environment
doc-setup: client
@echo "Setting up documentation build environment"
python3 -m venv doc/.sphinx/venv
@ -229,52 +158,75 @@ doc-setup: client
rm -Rf doc/html
rm -Rf doc/.sphinx/.doctrees
.PHONY: doc
doc: doc-setup doc-incremental
.PHONY: doc-incremental
# 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
doc-serve:
cd doc/html; python3 -m http.server 8001
.PHONY: doc-spellcheck
# doc: Check spelling errors on the documentation
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
. $(SPHINXENV) ; LOCAL_SPHINX_BUILD=True sphinx-build -c doc/ -b linkcheck doc/ doc/html/ -d doc/.sphinx/.doctrees
.PHONY: doc-lint
# doc: Lint the documentation
doc-lint:
doc/.sphinx/.markdownlint/doc-lint.sh
.PHONY: woke-install
# doc: Install the inclusive checker
woke-install:
@type woke >/dev/null 2>&1 || \
{ echo "Installing \"woke\" snap... \n"; sudo snap install woke; }
.PHONY: doc-woke
# doc: Check for non-inclusive phrasing
doc-woke: woke-install
woke *.md **/*.md -c https://github.com/canonical/Inclusive-naming/raw/main/config.yml
# section(Tests): Run the tests
.PHONY: debug
debug:
ifeq "$(TAG_SQLITE3)" ""
@echo "Missing custom libsqlite3, run \"make deps\" to setup."
exit 1
endif
CC="$(CC)" CGO_LDFLAGS_ALLOW="$(CGO_LDFLAGS_ALLOW)" $(GO) install -v -tags "$(TAG_SQLITE3) logdebug" $(DEBUG) ./...
CGO_ENABLED=0 $(GO) install -v -tags "netgo,logdebug" ./cmd/incus-migrate
CGO_ENABLED=0 $(GO) install -v -tags "agent,netgo,logdebug" ./cmd/incus-agent
@echo "Incus built successfully"
.PHONY: nocache
nocache:
ifeq "$(TAG_SQLITE3)" ""
@echo "Missing custom libsqlite3, run \"make deps\" to setup."
exit 1
endif
CC="$(CC)" CGO_LDFLAGS_ALLOW="$(CGO_LDFLAGS_ALLOW)" $(GO) install -a -v -tags "$(TAG_SQLITE3)" $(DEBUG) ./...
CGO_ENABLED=0 $(GO) install -a -v -tags netgo ./cmd/incus-migrate
CGO_ENABLED=0 $(GO) install -a -v -tags agent,netgo ./cmd/incus-agent
@echo "Incus built successfully"
race:
ifeq "$(TAG_SQLITE3)" ""
@echo "Missing custom libsqlite3, run \"make deps\" to setup."
exit 1
endif
CC="$(CC)" CGO_LDFLAGS_ALLOW="$(CGO_LDFLAGS_ALLOW)" $(GO) install -race -v -tags "$(TAG_SQLITE3)" $(DEBUG) ./...
CGO_ENABLED=0 $(GO) install -v -tags netgo ./cmd/incus-migrate
CGO_ENABLED=0 $(GO) install -v -tags agent,netgo ./cmd/incus-agent
@echo "Incus built successfully"
.PHONY: check
# doc: Run the test suite
check: default
ifeq "$(INCUS_OFFLINE)" ""
(cd / ; $(GO) install -v -x github.com/rogpeppe/godeps@latest)
@ -284,94 +236,7 @@ endif
CGO_LDFLAGS_ALLOW="$(CGO_LDFLAGS_ALLOW)" $(GO) test -v -tags "$(TAG_SQLITE3)" $(DEBUG) ./...
cd test && ./main.sh
.PHONY: static-analysis
# doc: Run static analysis
static-analysis:
ifeq ($(shell command -v go-licenses),)
(cd / ; $(GO) install -v -x github.com/google/go-licenses@latest)
endif
ifeq ($(shell command -v govulncheck),)
go install golang.org/x/vuln/cmd/govulncheck@latest
endif
ifeq ($(shell command -v golangci-lint),)
curl -sSfL https://golangci-lint.run/install.sh | sh -s -- -b $$($(GO) env GOPATH)/bin
endif
ifeq ($(shell command -v shellcheck),)
echo "Please install shellcheck"
exit 1
endif
ifeq ($(shell command -v flake8),)
echo "Please install flake8"
exit 1
endif
ifeq ($(shell command -v codespell),)
echo "Please install codespell"
exit 1
endif
ifeq ($(shell command -v run-parts),)
echo "Please install run-parts"
exit 1
endif
flake8 test/deps/import-busybox
shellcheck --shell sh test/*.sh test/includes/*.sh test/suites/*.sh test/backends/*.sh test/lint/*.sh
shellcheck test/extras/*.sh
run-parts $(shell run-parts -V >/dev/null 2>&1 && echo -n "--verbose --exit-on-error --regex '.sh'") test/lint
.PHONY: staticcheck
# doc: Run static checks
staticcheck:
ifeq ($(shell command -v staticcheck),)
(cd / ; $(GO) install -v -x honnef.co/go/tools/cmd/staticcheck@latest)
endif
# To get advance notice of deprecated function usage, consider running:
# sed -i 's/^go 1\.[0-9]\+$/go 1.18/' go.mod
# before 'make staticcheck'.
# Run staticcheck against all the dirs containing Go files.
staticcheck $$(git ls-files *.go | sed 's|^|./|; s|/[^/]\+\.go$$||' | sort -u)
.PHONY: unit-test
# doc: Run unit tests
unit-test:
sudo --preserve-env=CGO_CFLAGS,CGO_LDFLAGS,CGO_LDFLAGS_ALLOW,LD_LIBRARY_PATH LD_LIBRARY_PATH=${LD_LIBRARY_PATH} env "PATH=${PATH}" $(GO) test ./...
# section(Internationalization): Generate internationalization files
.PHONY: i18n
# doc: Generate internationalization files
i18n: update-pot update-po
po/%.mo: po/%.po
msgfmt --statistics -o $@ $<
po/%.po: po/$(DOMAIN).pot
msgmerge -U po/$*.po po/$(DOMAIN).pot
.PHONY: update-po
# doc: Update PO files
update-po:
set -eu; \
for lang in $(LINGUAS); do\
msgmerge --backup=none -U $$lang.po po/$(DOMAIN).pot; \
done
.PHONY: update-pot
# doc: Update POT file
update-pot:
ifeq "$(INCUS_OFFLINE)" ""
(cd / ; $(GO) install -v -x github.com/snapcore/snapd/i18n/xgettext-go@2.57.1)
endif
xgettext-go -o po/$(DOMAIN).pot --add-comments-tag=TRANSLATORS: --sort-output --package-name=$(DOMAIN) --msgid-bugs-address=lxc-devel@lists.linuxcontainers.org --keyword=i18n.G --keyword-plural=i18n.NG cmd/incus/*.go cmd/incus/color/*.go cmd/incus/usage/*.go shared/cliconfig/*.go
sed -i s/CHARSET/UTF-8/ po/$(DOMAIN).pot
.PHONY: build-mo
# doc! Build MO files
build-mo: $(MOFILES)
# section(Miscellaneous): Targets that don’t fit in any category
.PHONY: dist
# doc: Prepare a release tarball
dist: doc
# Cleanup
rm -Rf $(ARCHIVE).xz
@ -400,8 +265,92 @@ dist: doc
# Cleanup
rm -Rf $(TMP)
.PHONY: help
# 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}}'
.PHONY: i18n
i18n: update-pot update-po
po/%.mo: po/%.po
msgfmt --statistics -o $@ $<
po/%.po: po/$(DOMAIN).pot
msgmerge -U po/$*.po po/$(DOMAIN).pot
.PHONY: update-po
update-po:
set -eu; \
for lang in $(LINGUAS); do\
msgmerge --backup=none -U $$lang.po po/$(DOMAIN).pot; \
done
.PHONY: update-pot
update-pot:
ifeq "$(INCUS_OFFLINE)" ""
(cd / ; $(GO) install -v -x github.com/snapcore/snapd/i18n/xgettext-go@2.57.1)
endif
xgettext-go -o po/$(DOMAIN).pot --add-comments-tag=TRANSLATORS: --sort-output --package-name=$(DOMAIN) --msgid-bugs-address=lxc-devel@lists.linuxcontainers.org --keyword=i18n.G --keyword-plural=i18n.NG cmd/incus/*.go cmd/incus/color/*.go cmd/incus/usage/*.go shared/cliconfig/*.go
sed -i s/CHARSET/UTF-8/ po/$(DOMAIN).pot
.PHONY: build-mo
build-mo: $(MOFILES)
.PHONY: static-analysis
static-analysis:
ifeq ($(shell command -v go-licenses),)
(cd / ; $(GO) install -v -x github.com/google/go-licenses@latest)
endif
ifeq ($(shell command -v govulncheck),)
go install golang.org/x/vuln/cmd/govulncheck@latest
endif
ifeq ($(shell command -v golangci-lint),)
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $$($(GO) env GOPATH)/bin
endif
ifeq ($(shell command -v shellcheck),)
echo "Please install shellcheck"
exit 1
endif
ifeq ($(shell command -v flake8),)
echo "Please install flake8"
exit 1
endif
ifeq ($(shell command -v codespell),)
echo "Please install codespell"
exit 1
endif
ifeq ($(shell command -v run-parts),)
echo "Please install run-parts"
exit 1
endif
flake8 test/deps/import-busybox
shellcheck --shell sh test/*.sh test/includes/*.sh test/suites/*.sh test/backends/*.sh test/lint/*.sh
shellcheck test/extras/*.sh
run-parts $(shell run-parts -V >/dev/null 2>&1 && echo -n "--verbose --exit-on-error --regex '.sh'") test/lint
.PHONY: staticcheck
staticcheck:
ifeq ($(shell command -v staticcheck),)
(cd / ; $(GO) install -v -x honnef.co/go/tools/cmd/staticcheck@latest)
endif
# To get advance notice of deprecated function usage, consider running:
# sed -i 's/^go 1\.[0-9]\+$/go 1.18/' go.mod
# before 'make staticcheck'.
# Run staticcheck against all the dirs containing Go files.
staticcheck $$(git ls-files *.go | sed 's|^|./|; s|/[^/]\+\.go$$||' | sort -u)
.PHONY: tags
tags: */*.go
ifeq ($(shell command -v gotags),)
(cd / ; $(GO) install -v -x github.com/jstemmer/gotags@latest)
endif
find . -type f -name '*.go' | gotags -L - -f tags
# OpenFGA Syntax Transformer: https://github.com/openfga/syntax-transformer
.PHONY: update-openfga
update-openfga:
ifeq ($(shell command -v fga),)
(cd / ; $(GO) install -v -x github.com/openfga/cli/cmd/fga@latest)
endif
@printf 'package auth\n\n// Code generated by Makefile; DO NOT EDIT.\n\nvar authModel = `%s`\n' '$(shell fga model transform --file=./internal/server/auth/driver_openfga_model.openfga | jq -c)' > ./internal/server/auth/driver_openfga_model.go
.PHONY: unit-test
unit-test:
sudo --preserve-env=CGO_CFLAGS,CGO_LDFLAGS,CGO_LDFLAGS_ALLOW,LD_LIBRARY_PATH LD_LIBRARY_PATH=${LD_LIBRARY_PATH} env "PATH=${PATH}" $(GO) test ./...

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.
@ -38,7 +40,7 @@ See [Getting started](https://linuxcontainers.org/incus/docs/main/tutorial/first
Type | Service | Status
--- | --- | ---
Tests | GitHub | [![Build Status](https://github.com/lxc/incus/actions/workflows/tests.yml/badge.svg?branch=main)](https://github.com/lxc/incus/actions?query=event%3Apush+branch%3Amain)
Go documentation | Godoc | [![GoDoc](https://godoc.org/github.com/lxc/incus/v7/client?status.svg)](https://godoc.org/github.com/lxc/incus/v7/client)
Go documentation | Godoc | [![GoDoc](https://godoc.org/github.com/lxc/incus/v6/client?status.svg)](https://godoc.org/github.com/lxc/incus/v6/client)
Static analysis | GoReport | [![Go Report Card](https://goreportcard.com/badge/github.com/lxc/incus)](https://goreportcard.com/report/github.com/lxc/incus)
Translations | Weblate | [![Translation status](https://hosted.weblate.org/widget/incus/svg-badge.svg)](https://hosted.weblate.org/projects/incus/)

View File

@ -15,10 +15,10 @@ import (
"github.com/gorilla/websocket"
"github.com/zitadel/oidc/v3/pkg/oidc"
"github.com/lxc/incus/v7/shared/api"
"github.com/lxc/incus/v7/shared/logger"
"github.com/lxc/incus/v7/shared/simplestreams"
"github.com/lxc/incus/v7/shared/util"
"github.com/lxc/incus/v6/shared/api"
"github.com/lxc/incus/v6/shared/logger"
"github.com/lxc/incus/v6/shared/simplestreams"
"github.com/lxc/incus/v6/shared/util"
)
// ConnectionArgs represents a set of common connection properties.
@ -62,9 +62,6 @@ type ConnectionArgs struct {
// OpenID Connect tokens
OIDCTokens *oidc.Tokens[*oidc.IDTokenClaims]
// Do not block for OIDC authentication
OIDCNonInteractive bool
// Skip the event listener endpoint
SkipGetEvents bool
@ -354,7 +351,6 @@ func ConnectOCI(uri string, args *ConnectionArgs) (ImageServer, error) {
httpCertificate: args.TLSServerCert,
cache: map[string]ociInfo{},
errors: map[string]error{},
tempPath: args.TempPath,
}
@ -414,7 +410,7 @@ func httpsIncus(ctx context.Context, requestURL string, args *ConnectionArgs) (I
server.http = httpClient
if args.AuthType == api.AuthenticationMethodOIDC {
server.setupOIDCClient(args.OIDCTokens, args.OIDCNonInteractive)
server.setupOIDCClient(args.OIDCTokens)
}
// Test the connection and seed the server information

View File

@ -5,7 +5,7 @@ import (
"errors"
"sync"
"github.com/lxc/incus/v7/shared/api"
"github.com/lxc/incus/v6/shared/api"
)
// The EventListener struct is used to interact with an Incus event stream.

View File

@ -16,9 +16,9 @@ import (
"github.com/gorilla/websocket"
"github.com/lxc/incus/v7/shared/api"
"github.com/lxc/incus/v7/shared/logger"
"github.com/lxc/incus/v7/shared/tcp"
"github.com/lxc/incus/v6/shared/api"
"github.com/lxc/incus/v6/shared/logger"
"github.com/lxc/incus/v6/shared/tcp"
)
// ProtocolIncus represents an Incus API server.
@ -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

@ -5,7 +5,7 @@ import (
"fmt"
"net/url"
"github.com/lxc/incus/v7/shared/api"
"github.com/lxc/incus/v6/shared/api"
)
// Certificate handling functions

View File

@ -5,7 +5,7 @@ import (
"fmt"
"net/url"
"github.com/lxc/incus/v7/shared/api"
"github.com/lxc/incus/v6/shared/api"
)
// GetCluster returns information about a cluster.

View File

@ -11,7 +11,7 @@ import (
"github.com/gorilla/websocket"
"github.com/lxc/incus/v7/shared/api"
"github.com/lxc/incus/v6/shared/api"
)
// Event handling functions

View File

@ -14,13 +14,12 @@ import (
"strings"
"time"
"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"
"github.com/lxc/incus/v6/shared/api"
"github.com/lxc/incus/v6/shared/cancel"
"github.com/lxc/incus/v6/shared/ioprogress"
localtls "github.com/lxc/incus/v6/shared/tls"
"github.com/lxc/incus/v6/shared/units"
"github.com/lxc/incus/v6/shared/util"
)
// Image handling functions
@ -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 {
@ -301,7 +300,7 @@ func incusDownloadImage(fingerprint string, uri string, userAgent string, do fun
return nil, errors.New("Invalid multipart image")
}
size, err := util.SafeCopy(io.MultiWriter(req.MetaFile, hash256), part)
size, err := io.Copy(io.MultiWriter(req.MetaFile, hash256), part)
if err != nil {
return nil, err
}
@ -319,7 +318,7 @@ func incusDownloadImage(fingerprint string, uri string, userAgent string, do fun
return nil, errors.New("Invalid multipart image")
}
size, err = util.SafeCopy(io.MultiWriter(req.RootfsFile, hash256), part)
size, err = io.Copy(io.MultiWriter(req.RootfsFile, hash256), part)
if err != nil {
return nil, err
}
@ -347,7 +346,7 @@ func incusDownloadImage(fingerprint string, uri string, userAgent string, do fun
return nil, errors.New("No filename in Content-Disposition header")
}
size, err := util.SafeCopy(io.MultiWriter(req.MetaFile, hash256), body)
size, err := io.Copy(io.MultiWriter(req.MetaFile, hash256), body)
if err != nil {
return nil, err
}
@ -492,7 +491,7 @@ func (r *ProtocolIncus) CreateImage(image api.ImagesPost, args *ImageCreateArgs)
return
}
_, ioErr = util.SafeCopy(fw, args.MetaFile)
_, ioErr = io.Copy(fw, args.MetaFile)
if ioErr != nil {
return
}
@ -508,7 +507,7 @@ func (r *ProtocolIncus) CreateImage(image api.ImagesPost, args *ImageCreateArgs)
return
}
_, ioErr = util.SafeCopy(fw, args.RootfsFile)
_, ioErr = io.Copy(fw, args.RootfsFile)
if ioErr != nil {
return
}
@ -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,21 +13,18 @@ import (
"net/url"
"path/filepath"
"slices"
"strconv"
"strings"
"github.com/gorilla/websocket"
"github.com/pkg/sftp"
"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"
"github.com/lxc/incus/v7/shared/util"
"github.com/lxc/incus/v7/shared/ws"
"github.com/lxc/incus/v6/shared/api"
"github.com/lxc/incus/v6/shared/cancel"
"github.com/lxc/incus/v6/shared/ioprogress"
"github.com/lxc/incus/v6/shared/tcp"
localtls "github.com/lxc/incus/v6/shared/tls"
"github.com/lxc/incus/v6/shared/units"
"github.com/lxc/incus/v6/shared/ws"
)
// Instance handling functions.
@ -585,7 +582,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)
@ -880,12 +877,6 @@ func (r *ProtocolIncus) CopyInstance(source InstanceServer, instance api.Instanc
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
}
// Push mode migration
if args != nil && args.Mode == "push" {
// Get target server connection information
@ -1263,7 +1254,7 @@ func (r *ProtocolIncus) ExecInstance(instanceName string, exec api.InstanceExecP
if outputFiles["1"] != "" {
reader, _ := r.getInstanceExecOutputLogFile(instanceName, filepath.Base(outputFiles["1"]))
if args.Stdout != nil {
_, errCopy := util.SafeCopy(args.Stdout, reader)
_, errCopy := io.Copy(args.Stdout, reader)
// Regardless of errCopy value, we want to delete the file after a copy operation
errDelete := r.deleteInstanceExecOutputLogFile(instanceName, filepath.Base(outputFiles["1"]))
if errDelete != nil {
@ -1284,7 +1275,7 @@ func (r *ProtocolIncus) ExecInstance(instanceName string, exec api.InstanceExecP
if outputFiles["2"] != "" {
reader, _ := r.getInstanceExecOutputLogFile(instanceName, filepath.Base(outputFiles["2"]))
if args.Stderr != nil {
_, errCopy := util.SafeCopy(args.Stderr, reader)
_, errCopy := io.Copy(args.Stderr, reader)
errDelete := r.deleteInstanceExecOutputLogFile(instanceName, filepath.Base(outputFiles["1"]))
if errDelete != nil {
return nil, errDelete
@ -1459,8 +1450,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 +1462,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 +1651,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 +1659,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 +1671,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 {
@ -1744,43 +1715,7 @@ func (r *ProtocolIncus) rawConn(method string, apiURL *url.URL, protocol string,
return nil, errors.New("Missing or unexpected Upgrade header in response")
}
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)
return conn, err
}
// GetInstanceFileSFTPConn returns a connection to the instance's SFTP endpoint.
@ -1790,7 +1725,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 +2971,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 {
@ -3060,7 +2995,7 @@ func (r *ProtocolIncus) GetInstanceBackupFile(instanceName string, name string,
}
}
size, err := util.SafeCopy(req.BackupFile, body)
size, err := io.Copy(req.BackupFile, body)
if err != nil {
return nil, err
}
@ -3113,7 +3048,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 {
@ -3136,7 +3071,7 @@ func (r *ProtocolIncus) CreateInstanceBackupStream(instanceName string, backup a
}
}
_, err = util.SafeCopy(req.BackupFile, body)
_, err = io.Copy(req.BackupFile, body)
return err
}
@ -3263,252 +3198,3 @@ func (r *ProtocolIncus) GetInstanceDebugMemory(name string, format string) (io.R
return resp.Body, nil
}
// CreateInstanceBitmap requests that Incus creates a new bitmap for the instance.
func (r *ProtocolIncus) CreateInstanceBitmap(name string, bitmap api.StorageVolumeBitmapsPost) error {
if !r.HasExtension("storage_volume_nbd") {
return errors.New("The server is missing the required \"storage_volume_nbd\" API extension")
}
path, _, err := r.instanceTypeToPath(api.InstanceTypeAny)
if err != nil {
return err
}
// Send the request
_, _, err = r.query("POST", fmt.Sprintf("%s/%s/bitmaps", path, url.PathEscape(name)), bitmap, "")
if err != nil {
return err
}
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

@ -3,7 +3,7 @@ package incus
import (
"errors"
"github.com/lxc/incus/v7/shared/api"
"github.com/lxc/incus/v6/shared/api"
)
// GetMetadataConfiguration returns a configuration metadata struct.

View File

@ -7,7 +7,7 @@ import (
"net/http"
"net/url"
"github.com/lxc/incus/v7/shared/api"
"github.com/lxc/incus/v6/shared/api"
)
// GetNetworkACLNames returns a list of network ACL names.

View File

@ -5,7 +5,7 @@ import (
"fmt"
"net/url"
"github.com/lxc/incus/v7/shared/api"
"github.com/lxc/incus/v6/shared/api"
)
// GetNetworkAddressSetNames returns a list of network address set names.

View File

@ -1,7 +1,7 @@
package incus
import (
"github.com/lxc/incus/v7/shared/api"
"github.com/lxc/incus/v6/shared/api"
)
// GetNetworkAllocations returns a list of Network allocations for a specific project.

View File

@ -5,7 +5,7 @@ import (
"fmt"
"net/url"
"github.com/lxc/incus/v7/shared/api"
"github.com/lxc/incus/v6/shared/api"
)
// GetNetworkForwardAddresses returns a list of network forward listen addresses.

View File

@ -5,7 +5,7 @@ import (
"fmt"
"net/url"
"github.com/lxc/incus/v7/shared/api"
"github.com/lxc/incus/v6/shared/api"
)
// GetNetworkIntegrationNames returns a list of network integration names.

View File

@ -1,7 +1,7 @@
package incus
import (
"github.com/lxc/incus/v7/shared/api"
"github.com/lxc/incus/v6/shared/api"
)
// GetNetworkLoadBalancerAddresses returns a list of network load balancer listen addresses.

View File

@ -5,7 +5,7 @@ import (
"fmt"
"net/url"
"github.com/lxc/incus/v7/shared/api"
"github.com/lxc/incus/v6/shared/api"
)
// GetNetworkPeerNames returns a list of network peer names.

View File

@ -5,7 +5,7 @@ import (
"fmt"
"net/url"
"github.com/lxc/incus/v7/shared/api"
"github.com/lxc/incus/v6/shared/api"
)
// GetNetworkZoneNames returns a list of network zone names.

View File

@ -5,7 +5,7 @@ import (
"fmt"
"net/url"
"github.com/lxc/incus/v7/shared/api"
"github.com/lxc/incus/v6/shared/api"
)
// GetNetworkNames returns a list of network names.

View File

@ -20,7 +20,7 @@ import (
"github.com/zitadel/oidc/v3/pkg/oidc"
"golang.org/x/oauth2"
"github.com/lxc/incus/v7/shared/util"
"github.com/lxc/incus/v6/shared/util"
)
// ErrOIDCExpired is returned when the token is expired and we can't retry the request ourselves.
@ -28,13 +28,12 @@ var ErrOIDCExpired = errors.New("OIDC token expired, please re-try the request")
// setupOIDCClient initializes the OIDC (OpenID Connect) client with given tokens if it hasn't been set up already.
// It also assigns the protocol's http client to the oidcClient's httpClient.
func (r *ProtocolIncus) setupOIDCClient(token *oidc.Tokens[*oidc.IDTokenClaims], skipAuthenticate bool) {
func (r *ProtocolIncus) setupOIDCClient(token *oidc.Tokens[*oidc.IDTokenClaims]) {
if r.oidcClient != nil {
return
}
r.oidcClient = newOIDCClient(token)
r.oidcClient.skipAuthenticate = skipAuthenticate
r.oidcClient.httpClient = r.http
}
@ -83,10 +82,9 @@ func (o *oidcTransport) RoundTrip(r *http.Request) (*http.Response, error) {
var errRefreshAccessToken = errors.New("Failed refreshing access token")
type oidcClient struct {
httpClient *http.Client
oidcTransport *oidcTransport
tokens *oidc.Tokens[*oidc.IDTokenClaims]
skipAuthenticate bool
httpClient *http.Client
oidcTransport *oidcTransport
tokens *oidc.Tokens[*oidc.IDTokenClaims]
}
// oidcClient is a structure encapsulating an HTTP client, OIDC transport, and a token for OpenID Connect (OIDC) operations.
@ -143,10 +141,6 @@ func (o *oidcClient) do(req *http.Request) (*http.Response, error) {
// Refresh the token.
err = o.refresh(issuer, clientID, scopes)
if err != nil {
if o.skipAuthenticate {
return nil, fmt.Errorf("Authentication not found or expired: %w", err)
}
err = o.authenticate(issuer, clientID, audience, scopes)
if err != nil {
return nil, err
@ -205,10 +199,6 @@ func (o *oidcClient) dial(dialer websocket.Dialer, uri string, req *http.Request
err = o.refresh(issuer, clientID, scopes)
if err != nil {
if o.skipAuthenticate {
return nil, resp, fmt.Errorf("Authentication not found or expired: %w", err)
}
err = o.authenticate(issuer, clientID, audience, scopes)
if err != nil {
return nil, resp, err

View File

@ -6,7 +6,7 @@ import (
"github.com/gorilla/websocket"
"github.com/lxc/incus/v7/shared/api"
"github.com/lxc/incus/v6/shared/api"
)
// GetOperationUUIDs returns a list of operation uuids.

View File

@ -5,7 +5,7 @@ import (
"fmt"
"net/url"
"github.com/lxc/incus/v7/shared/api"
"github.com/lxc/incus/v6/shared/api"
)
// Profile handling functions

View File

@ -5,7 +5,7 @@ import (
"fmt"
"net/url"
"github.com/lxc/incus/v7/shared/api"
"github.com/lxc/incus/v6/shared/api"
)
// Project handling functions

View File

@ -9,10 +9,9 @@ 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"
"github.com/lxc/incus/v6/shared/api"
localtls "github.com/lxc/incus/v6/shared/tls"
"github.com/lxc/incus/v6/shared/util"
)
// Server handling functions
@ -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

@ -5,15 +5,14 @@ import (
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"net/url"
"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"
"github.com/lxc/incus/v6/shared/api"
"github.com/lxc/incus/v6/shared/cancel"
"github.com/lxc/incus/v6/shared/ioprogress"
"github.com/lxc/incus/v6/shared/units"
)
// GetStoragePoolBucketNames returns a list of storage bucket names.
@ -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 {
@ -497,7 +496,7 @@ func (r *ProtocolIncus) GetStoragePoolBucketBackupFile(pool string, bucketName s
}
}
size, err := util.SafeCopy(req.BackupFile, body)
size, err := io.Copy(req.BackupFile, body)
if err != nil {
return nil, err
}
@ -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 {
@ -568,7 +567,7 @@ func (r *ProtocolIncus) CreateStoragePoolBucketBackupStream(poolName string, buc
}
}
_, err = util.SafeCopy(req.BackupFile, body)
_, err = io.Copy(req.BackupFile, body)
return err
}
@ -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

@ -5,7 +5,7 @@ import (
"fmt"
"net/url"
"github.com/lxc/incus/v7/shared/api"
"github.com/lxc/incus/v6/shared/api"
)
// Storage pool handling functions

View File

@ -13,13 +13,11 @@ import (
"github.com/pkg/sftp"
"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"
"github.com/lxc/incus/v6/shared/api"
"github.com/lxc/incus/v6/shared/cancel"
"github.com/lxc/incus/v6/shared/ioprogress"
localtls "github.com/lxc/incus/v6/shared/tls"
"github.com/lxc/incus/v6/shared/units"
)
// Storage volumes handling function
@ -433,8 +431,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 +917,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 +1061,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 {
@ -1106,7 +1085,7 @@ func (r *ProtocolIncus) GetStorageVolumeBackupFile(pool string, volName string,
}
}
size, err := util.SafeCopy(req.BackupFile, body)
size, err := io.Copy(req.BackupFile, body)
if err != nil {
return nil, err
}
@ -1154,7 +1133,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 {
@ -1177,7 +1156,7 @@ func (r *ProtocolIncus) CreateStorageVolumeBackupStream(pool string, volName str
}
}
_, err = util.SafeCopy(req.BackupFile, body)
_, err = io.Copy(req.BackupFile, body)
return err
}
@ -1228,7 +1207,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 +1266,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)
@ -1311,28 +1290,6 @@ func (r *ProtocolIncus) CreateStoragePoolVolumeFromBackup(pool string, args Stor
return &op, nil
}
// GetStoragePoolVolumeBlockNBDConn returns a connection to the volume's NBD endpoint.
func (r *ProtocolIncus) GetStoragePoolVolumeBlockNBDConn(pool string, volType string, volName string, args StorageVolumeNBDPost) (net.Conn, error) {
if !r.HasExtension("storage_volume_nbd") {
return nil, errors.New(`The server is missing the required "storage_volume_nbd" API extension`)
}
u := api.NewURL()
u.URL = r.httpBaseURL // Preload the URL with the client base URL.
u.Path("1.0", "storage-pools", pool, "volumes", volType, volName, "nbd")
values := u.Query()
if args.Writable {
values.Set("writable", "1")
}
u.RawQuery = values.Encode()
r.setURLQueryAttributes(&u.URL)
return r.rawConn(http.MethodGet, &u.URL, "nbd", nil)
}
// GetStoragePoolVolumeFileSFTPConn returns a connection to the volume's SFTP endpoint.
func (r *ProtocolIncus) GetStoragePoolVolumeFileSFTPConn(pool string, volType string, volName string) (net.Conn, error) {
if !r.HasExtension("custom_volume_sftp") {
@ -1344,7 +1301,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.
@ -1524,103 +1481,3 @@ func (r *ProtocolIncus) DeleteStorageVolumeFile(pool string, volumeType string,
return nil
}
// GetStorageVolumeBitmapNames returns a list of volume bitmap names.
func (r *ProtocolIncus) GetStorageVolumeBitmapNames(pool string, volumeType string, volumeName string) ([]string, error) {
if !r.HasExtension("storage_volume_nbd") {
return nil, errors.New("The server is missing the required \"storage_volume_nbd\" API extension")
}
// Fetch the raw URL values.
urls := []string{}
baseURL := fmt.Sprintf(
"/storage-pools/%s/volumes/%s/%s/bitmaps",
url.PathEscape(pool), url.PathEscape(volumeType), url.PathEscape(volumeName),
)
_, err := r.queryStruct("GET", baseURL, nil, "", &urls)
if err != nil {
return nil, err
}
// Parse it.
return urlsToResourceNames(baseURL, urls...)
}
// GetStorageVolumeBitmaps returns a list of volume bitmaps.
func (r *ProtocolIncus) GetStorageVolumeBitmaps(pool string, volumeType string, volumeName 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")
}
bitmaps := []api.StorageVolumeBitmap{}
path := fmt.Sprintf("/storage-pools/%s/volumes/%s/%s/bitmaps?recursion=1",
url.PathEscape(pool),
url.PathEscape(volumeType),
url.PathEscape(volumeName))
_, err := r.queryStruct("GET", path, nil, "", &bitmaps)
if err != nil {
return nil, err
}
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") {
return errors.New("The server is missing the required \"storage_volume_nbd\" API extension")
}
path := fmt.Sprintf("/storage-pools/%s/volumes/%s/%s/bitmaps",
url.PathEscape(pool),
url.PathEscape(volumeType),
url.PathEscape(volumeName))
// Send the request
_, _, err := r.query("POST", path, bitmap, "")
if err != nil {
return err
}
return nil
}
// DeleteStorageVolumeBitmap deletes a volume bitmap.
func (r *ProtocolIncus) DeleteStorageVolumeBitmap(pool string, volumeType string, volumeName string, bitmapName string) error {
if !r.HasExtension("storage_volume_nbd") {
return errors.New("The server is missing the required \"storage_volume_nbd\" API extension")
}
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.query("DELETE", path, nil, "")
if err != nil {
return err
}
return nil
}

View File

@ -5,7 +5,7 @@ import (
"fmt"
"net/url"
"github.com/lxc/incus/v7/shared/api"
"github.com/lxc/incus/v6/shared/api"
)
// Warning handling functions

View File

@ -9,9 +9,9 @@ import (
"github.com/gorilla/websocket"
"github.com/pkg/sftp"
"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/v6/shared/api"
"github.com/lxc/incus/v6/shared/cancel"
"github.com/lxc/incus/v6/shared/ioprogress"
)
// The Operation type represents a currently running operation.
@ -124,8 +124,6 @@ type InstanceServer interface {
ConsoleInstance(instanceName string, console api.InstanceConsolePost, args *InstanceConsoleArgs) (op Operation, err error)
ConsoleInstanceDynamic(instanceName string, console api.InstanceConsolePost, args *InstanceConsoleArgs) (Operation, func(io.ReadWriteCloser) error, error)
CreateInstanceBitmap(name string, bitmap api.StorageVolumeBitmapsPost) error
GetInstanceConsoleLog(instanceName string, args *InstanceConsoleLogArgs) (content io.ReadCloser, err error)
DeleteInstanceConsoleLog(instanceName string, args *InstanceConsoleLogArgs) (err error)
@ -136,10 +134,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 +172,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 +380,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)
@ -417,13 +400,6 @@ type InstanceServer interface {
CreateStorageVolumeBackupStream(pool string, volName string, backup api.StorageVolumeBackupsPost, req *BackupFileRequest) (err error)
CreateStoragePoolVolumeFromBackup(pool string, args StorageVolumeBackupArgs) (op Operation, err error)
// 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
// Storage volume ISO import function ("custom_volume_iso" API extension)
CreateStoragePoolVolumeFromISO(pool string, args StorageVolumeBackupArgs) (op Operation, err error)
CreateStoragePoolVolumeFromMigration(pool string, volume api.StorageVolumesPost) (op Operation, err error)
@ -433,9 +409,6 @@ type InstanceServer interface {
CreateStorageVolumeFile(pool string, volumeType string, volumeName string, filePath string, args InstanceFileArgs) (err error)
DeleteStorageVolumeFile(pool string, volumeType string, volumeName string, filePath string) (err error)
// Storage volume NBD functions ("storage_volume_nbd" API extension)
GetStoragePoolVolumeBlockNBDConn(pool string, volType string, volName string, args StorageVolumeNBDPost) (net.Conn, error)
// Storage volume SFTP functions ("custom_volume_sftp" API extension)
GetStoragePoolVolumeFileSFTPConn(pool string, volType string, volName string) (net.Conn, error)
GetStoragePoolVolumeFileSFTP(pool string, volType string, volName string) (*sftp.Client, error)
@ -734,13 +707,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
@ -768,10 +734,3 @@ type StoragePoolBucketBackupArgs struct {
// Name to import backup as
Name string
}
// The StorageVolumeNBDPost struct is used when connecting to a storage volume over NBD.
// API extension: storage_volume_nbd.
type StorageVolumeNBDPost struct {
// Writable
Writable bool
}

View File

@ -15,9 +15,6 @@ type ProtocolOCI struct {
// Cache for images.
cache map[string]ociInfo
// Error tracking for images.
errors map[string]error
tempPath string
}

View File

@ -1,6 +1,7 @@
package incus
import (
"compress/gzip"
"context"
"crypto/sha256"
"encoding/base64"
@ -16,16 +17,12 @@ 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"
"github.com/lxc/incus/v7/shared/subprocess"
"github.com/lxc/incus/v7/shared/units"
"github.com/lxc/incus/v7/shared/util"
"github.com/lxc/incus/v6/shared/api"
"github.com/lxc/incus/v6/shared/ioprogress"
"github.com/lxc/incus/v6/shared/logger"
"github.com/lxc/incus/v6/shared/osarch"
"github.com/lxc/incus/v6/shared/subprocess"
"github.com/lxc/incus/v6/shared/units"
)
type ociInfo struct {
@ -91,11 +88,6 @@ func (r *ProtocolOCI) GetImage(fingerprint string) (*api.Image, string, error) {
return nil, "", errors.New("OCI container handling requires \"skopeo\" be present on the system")
}
err, ok := r.errors[fingerprint]
if ok {
return nil, "", err
}
return nil, "", errors.New("Image not found")
}
@ -141,11 +133,6 @@ func (r *ProtocolOCI) GetImageFile(fingerprint string, req ImageFileRequest) (*I
return nil, errors.New("OCI container handling requires \"skopeo\" be present on the system")
}
err, ok := r.errors[fingerprint]
if ok {
return nil, err
}
return nil, errors.New("Image not found")
}
@ -164,7 +151,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 +173,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 +222,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 +236,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,11 +245,11 @@ 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)
size, err := io.Copy(req.MetaFile, pipeRead)
if err != nil {
return nil, err
}
@ -277,8 +258,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 +272,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,11 +281,11 @@ 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)
size, err = io.Copy(req.RootfsFile, pipeRead)
if err != nil {
return nil, err
}
@ -383,7 +359,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 +387,7 @@ func (r *ProtocolOCI) runSkopeo(action string, image string, args ...string) (st
env,
nil,
"skopeo",
args...,
)
args...)
if err != nil {
return "", err
}
@ -430,11 +405,9 @@ 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
return nil, "", err
}
@ -442,8 +415,6 @@ func (r *ProtocolOCI) GetImageAlias(name string) (*api.ImageAliasesEntry, string
var info ociInfo
err = json.Unmarshal([]byte(stdout), &info)
if err != nil {
r.errors[name] = err
return nil, "", err
}
@ -452,15 +423,11 @@ func (r *ProtocolOCI) GetImageAlias(name string) (*api.ImageAliasesEntry, string
archID, err := osarch.ArchitectureID(info.Architecture)
if err != nil {
r.errors[name] = err
return nil, "", err
}
archName, err := osarch.ArchitectureName(archID)
if err != nil {
r.errors[name] = err
return nil, "", err
}

View File

@ -11,7 +11,7 @@ import (
"github.com/opencontainers/umoci/oci/casext"
"github.com/opencontainers/umoci/oci/layer"
"github.com/lxc/incus/v7/shared/logger"
"github.com/lxc/incus/v6/shared/logger"
)
func init() {
@ -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

@ -9,7 +9,7 @@ import (
"github.com/gorilla/websocket"
"github.com/lxc/incus/v7/shared/api"
"github.com/lxc/incus/v6/shared/api"
)
// The Operation type represents an ongoing Incus operation (asynchronous processing).
@ -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

@ -4,7 +4,7 @@ import (
"errors"
"net/http"
"github.com/lxc/incus/v7/shared/simplestreams"
"github.com/lxc/incus/v6/shared/simplestreams"
)
// ProtocolSimpleStreams implements a SimpleStreams API client.

View File

@ -13,11 +13,11 @@ import (
"strings"
"time"
"github.com/lxc/incus/v7/shared/api"
"github.com/lxc/incus/v7/shared/logger"
"github.com/lxc/incus/v7/shared/simplestreams"
"github.com/lxc/incus/v7/shared/subprocess"
"github.com/lxc/incus/v7/shared/util"
"github.com/lxc/incus/v6/shared/api"
"github.com/lxc/incus/v6/shared/logger"
"github.com/lxc/incus/v6/shared/simplestreams"
"github.com/lxc/incus/v6/shared/subprocess"
"github.com/lxc/incus/v6/shared/util"
)
// Image handling functions
@ -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())
@ -203,7 +203,7 @@ func (r *ProtocolSimpleStreams) GetImageFile(fingerprint string, req ImageFileRe
}
// Copy to the target
size, err := util.SafeCopy(req.RootfsFile, patchedFile)
size, err := io.Copy(req.RootfsFile, patchedFile)
if err != nil {
return -1, err
}
@ -263,7 +263,7 @@ func (r *ProtocolSimpleStreams) GetImageFile(fingerprint string, req ImageFileRe
return nil, err
}
_, err := util.SafeCopy(hash256, req.MetaFile)
_, err := io.Copy(hash256, req.MetaFile)
if err != nil {
return nil, err
}
@ -275,7 +275,7 @@ func (r *ProtocolSimpleStreams) GetImageFile(fingerprint string, req ImageFileRe
return nil, err
}
_, err := util.SafeCopy(hash256, req.RootfsFile)
_, err := io.Copy(hash256, req.RootfsFile)
if err != nil {
return nil, err
}
@ -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

@ -13,8 +13,8 @@ import (
"strings"
"time"
"github.com/lxc/incus/v7/shared/proxy"
localtls "github.com/lxc/incus/v7/shared/tls"
"github.com/lxc/incus/v6/shared/proxy"
localtls "github.com/lxc/incus/v6/shared/tls"
)
// tlsHTTPClient creates an HTTP client with a specified Transport Layer Security (TLS) configuration.
@ -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

@ -5,7 +5,7 @@ import (
"github.com/spf13/cobra"
"github.com/lxc/incus/v7/internal/version"
"github.com/lxc/incus/v6/internal/version"
)
type cmdGlobal struct {
@ -16,7 +16,7 @@ type cmdGlobal struct {
func main() {
// shift command (main)
shiftCmd := cmdShift{}
app := shiftCmd.command()
app := shiftCmd.Command()
app.SilenceUsage = true
app.CompletionOptions = cobra.CompletionOptions{DisableDefaultCmd: true}

View File

@ -7,7 +7,7 @@ import (
"github.com/spf13/cobra"
"github.com/lxc/incus/v7/shared/idmap"
"github.com/lxc/incus/v6/shared/idmap"
)
type cmdShift struct {
@ -17,7 +17,7 @@ type cmdShift struct {
flagTestMode bool
}
func (c *cmdShift) command() *cobra.Command {
func (c *cmdShift) Command() *cobra.Command {
cmd := &cobra.Command{}
cmd.Use = "fuidshift <directory> <range> [<range>...]"
cmd.Short = "UID/GID shifter"
@ -37,14 +37,14 @@ func (c *cmdShift) command() *cobra.Command {
Where "u" means shift uid, "g" means shift gid and "b" means shift uid and gid.
`
cmd.Example = ` fuidshift my-dir/ b:0:100000:65536 u:10000:1000:1`
cmd.RunE = c.run
cmd.RunE = c.Run
cmd.Flags().BoolVarP(&c.flagTestMode, "test", "t", false, "Test mode (no change to files)")
cmd.Flags().BoolVarP(&c.flagReverse, "reverse", "r", false, "Perform a reverse mapping")
return cmd
}
func (c *cmdShift) run(cmd *cobra.Command, args []string) error {
func (c *cmdShift) Run(cmd *cobra.Command, args []string) error {
// Help and usage
if len(args) == 0 {
return cmd.Help()

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

@ -14,9 +14,9 @@ import (
"github.com/spf13/pflag"
"golang.org/x/tools/go/packages"
"github.com/lxc/incus/v7/cmd/generate-database/db"
"github.com/lxc/incus/v7/cmd/generate-database/file"
"github.com/lxc/incus/v7/cmd/generate-database/lex"
"github.com/lxc/incus/v6/cmd/generate-database/db"
"github.com/lxc/incus/v6/cmd/generate-database/file"
"github.com/lxc/incus/v6/cmd/generate-database/lex"
)
// Return a new db command.

View File

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

View File

@ -4,8 +4,8 @@ import (
"fmt"
"strings"
"github.com/lxc/incus/v7/cmd/generate-database/lex"
"github.com/lxc/incus/v7/shared/util"
"github.com/lxc/incus/v6/cmd/generate-database/lex"
"github.com/lxc/incus/v6/shared/util"
)
// Return the table name for the given database entity.

View File

@ -8,8 +8,8 @@ import (
"slices"
"strings"
"github.com/lxc/incus/v7/cmd/generate-database/lex"
"github.com/lxc/incus/v7/shared/util"
"github.com/lxc/incus/v6/cmd/generate-database/lex"
"github.com/lxc/incus/v6/shared/util"
)
// Mapping holds information for mapping database tables to a Go structure.
@ -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,14 +5,13 @@ package db
import (
"fmt"
"go/types"
"slices"
"strings"
"golang.org/x/tools/go/packages"
"github.com/lxc/incus/v7/cmd/generate-database/file"
"github.com/lxc/incus/v7/cmd/generate-database/lex"
"github.com/lxc/incus/v7/shared/util"
"github.com/lxc/incus/v6/cmd/generate-database/file"
"github.com/lxc/incus/v6/cmd/generate-database/lex"
"github.com/lxc/incus/v6/shared/util"
)
// Method generates a code snippet for a particular database query method.
@ -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

@ -15,8 +15,8 @@ import (
"golang.org/x/tools/go/packages"
"github.com/lxc/incus/v7/cmd/generate-database/lex"
"github.com/lxc/incus/v7/shared/util"
"github.com/lxc/incus/v6/cmd/generate-database/lex"
"github.com/lxc/incus/v6/shared/util"
)
// FiltersFromStmt parses all filtering statement defined for the given entity. It
@ -578,7 +578,6 @@ func validateFieldConfig(config url.Values) error {
"join",
"leftjoin",
"joinon",
"joinas",
"omit":
_, err := exactlyOneValue(tag, values)

View File

@ -9,7 +9,7 @@ import (
"github.com/stretchr/testify/require"
"golang.org/x/tools/go/packages"
"github.com/lxc/incus/v7/cmd/generate-database/db"
"github.com/lxc/incus/v6/cmd/generate-database/db"
)
type Person struct {

View File

@ -3,8 +3,8 @@ package db
import (
"fmt"
"github.com/lxc/incus/v7/internal/server/db/cluster"
"github.com/lxc/incus/v7/internal/server/db/node"
"github.com/lxc/incus/v6/internal/server/db/cluster"
"github.com/lxc/incus/v6/internal/server/db/node"
)
// UpdateSchema updates the schema.go file of the cluster and node databases.

View File

@ -12,8 +12,8 @@ import (
"golang.org/x/tools/go/packages"
"github.com/lxc/incus/v7/cmd/generate-database/file"
"github.com/lxc/incus/v7/cmd/generate-database/lex"
"github.com/lxc/incus/v6/cmd/generate-database/file"
"github.com/lxc/incus/v6/cmd/generate-database/lex"
)
// Stmt generates a particular database query statement.
@ -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

@ -7,8 +7,7 @@ import (
"os"
"strings"
"github.com/lxc/incus/v7/cmd/generate-database/lex"
"github.com/lxc/incus/v7/shared/logger"
"github.com/lxc/incus/v6/cmd/generate-database/lex"
)
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

@ -3,7 +3,7 @@ package main
import (
"net/http"
"github.com/lxc/incus/v7/internal/server/response"
"github.com/lxc/incus/v6/internal/server/response"
)
// APIEndpoint represents a URL in our API.

View File

@ -9,14 +9,14 @@ import (
"os"
"path/filepath"
incus "github.com/lxc/incus/v7/client"
"github.com/lxc/incus/v7/internal/ports"
"github.com/lxc/incus/v7/internal/server/response"
localvsock "github.com/lxc/incus/v7/internal/server/vsock"
"github.com/lxc/incus/v7/internal/version"
"github.com/lxc/incus/v7/shared/api"
agentAPI "github.com/lxc/incus/v7/shared/api/agent"
localtls "github.com/lxc/incus/v7/shared/tls"
incus "github.com/lxc/incus/v6/client"
"github.com/lxc/incus/v6/internal/ports"
"github.com/lxc/incus/v6/internal/server/response"
localvsock "github.com/lxc/incus/v6/internal/server/vsock"
"github.com/lxc/incus/v6/internal/version"
"github.com/lxc/incus/v6/shared/api"
agentAPI "github.com/lxc/incus/v6/shared/api/agent"
localtls "github.com/lxc/incus/v6/shared/tls"
)
var api10Cmd = APIEndpoint{
@ -33,7 +33,6 @@ var api10 = []APIEndpoint{
operationCmd,
operationWebsocket,
operationWait,
portForwardCmd,
sftpCmd,
stateCmd,
}

View File

@ -5,7 +5,7 @@ import (
"io/fs"
"os"
"go.yaml.in/yaml/v4"
"gopkg.in/yaml.v2"
)
type agentConfig struct {
@ -23,7 +23,7 @@ func loadAgentConfig(d *Daemon) error {
}
cfg := agentConfig{}
err = yaml.Load(data, &cfg)
err = yaml.Unmarshal(data, &cfg)
if err != nil {
return err
}

View File

@ -3,7 +3,7 @@ package main
import (
"sync"
"github.com/lxc/incus/v7/internal/server/events"
"github.com/lxc/incus/v6/internal/server/events"
)
// A Daemon can respond to requests from a shared client.

View File

@ -9,13 +9,13 @@ import (
"strings"
"time"
incus "github.com/lxc/incus/v7/client"
"github.com/lxc/incus/v7/internal/server/daemon"
"github.com/lxc/incus/v7/internal/server/device/config"
localUtil "github.com/lxc/incus/v7/internal/server/util"
api "github.com/lxc/incus/v7/shared/api/guest"
"github.com/lxc/incus/v7/shared/logger"
"github.com/lxc/incus/v7/shared/util"
incus "github.com/lxc/incus/v6/client"
"github.com/lxc/incus/v6/internal/server/daemon"
"github.com/lxc/incus/v6/internal/server/device/config"
localUtil "github.com/lxc/incus/v6/internal/server/util"
api "github.com/lxc/incus/v6/shared/api/guest"
"github.com/lxc/incus/v6/shared/logger"
"github.com/lxc/incus/v6/shared/util"
)
// DevIncusServer creates an http.Server capable of handling requests against the

View File

@ -7,11 +7,11 @@ import (
"strings"
"time"
"github.com/lxc/incus/v7/internal/server/events"
"github.com/lxc/incus/v7/internal/server/response"
"github.com/lxc/incus/v7/shared/api"
"github.com/lxc/incus/v7/shared/logger"
"github.com/lxc/incus/v7/shared/ws"
"github.com/lxc/incus/v6/internal/server/events"
"github.com/lxc/incus/v6/internal/server/response"
"github.com/lxc/incus/v6/shared/api"
"github.com/lxc/incus/v6/shared/logger"
"github.com/lxc/incus/v6/shared/ws"
)
var eventsCmd = APIEndpoint{
@ -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

@ -17,14 +17,14 @@ import (
"github.com/gorilla/websocket"
"github.com/lxc/incus/v7/internal/jmap"
"github.com/lxc/incus/v7/internal/server/db/operationtype"
"github.com/lxc/incus/v7/internal/server/operations"
"github.com/lxc/incus/v7/internal/server/response"
internalUtil "github.com/lxc/incus/v7/internal/util"
"github.com/lxc/incus/v7/shared/api"
"github.com/lxc/incus/v7/shared/logger"
"github.com/lxc/incus/v7/shared/ws"
"github.com/lxc/incus/v6/internal/jmap"
"github.com/lxc/incus/v6/internal/server/db/operationtype"
"github.com/lxc/incus/v6/internal/server/operations"
"github.com/lxc/incus/v6/internal/server/response"
internalUtil "github.com/lxc/incus/v6/internal/util"
"github.com/lxc/incus/v6/shared/api"
"github.com/lxc/incus/v6/shared/logger"
"github.com/lxc/incus/v6/shared/ws"
)
const (
@ -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

@ -1,5 +1,3 @@
//go:debug httpmuxgo121=0
package main
import (
@ -9,7 +7,7 @@ import (
"github.com/spf13/cobra"
"github.com/lxc/incus/v7/internal/version"
"github.com/lxc/incus/v6/internal/version"
)
type cmdGlobal struct {
@ -25,7 +23,7 @@ type cmdGlobal struct {
func main() {
// agent command (main)
agentCmd := cmdAgent{}
app := agentCmd.command()
app := agentCmd.Command()
app.SilenceUsage = true
app.CompletionOptions = cobra.CompletionOptions{DisableDefaultCmd: true}

View File

@ -4,20 +4,20 @@ import (
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"os/signal"
"slices"
"strings"
"sync"
"time"
"github.com/spf13/cobra"
"github.com/lxc/incus/v7/internal/server/instance/instancetype"
"github.com/lxc/incus/v7/shared/logger"
"github.com/lxc/incus/v7/shared/subprocess"
"github.com/lxc/incus/v7/shared/util"
"github.com/lxc/incus/v6/internal/server/instance/instancetype"
"github.com/lxc/incus/v6/shared/logger"
"github.com/lxc/incus/v6/shared/subprocess"
"github.com/lxc/incus/v6/shared/util"
)
var (
@ -29,7 +29,7 @@ type cmdAgent struct {
global *cmdGlobal
}
func (c *cmdAgent) command() *cobra.Command {
func (c *cmdAgent) Command() *cobra.Command {
cmd := &cobra.Command{}
cmd.Use = "incus-agent [--debug]"
cmd.Short = "Incus virtual machine agent"
@ -40,12 +40,12 @@ func (c *cmdAgent) command() *cobra.Command {
It will normally be started through init scripts present or injected
into the virtual machine.
`
cmd.RunE = c.run
cmd.RunE = c.Run
return cmd
}
func (c *cmdAgent) run(cmd *cobra.Command, args []string) error {
func (c *cmdAgent) Run(cmd *cobra.Command, args []string) error {
if c.global.flagService {
return runService("Incus-Agent", c)
}
@ -79,7 +79,7 @@ func (c *cmdAgent) run(cmd *cobra.Command, args []string) error {
}
// Copy the data.
_, err = util.SafeCopy(dst, src)
_, err = io.Copy(dst, src)
if err != nil {
return err
}
@ -123,11 +123,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 +139,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 +210,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 +220,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 +232,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

@ -7,9 +7,9 @@ import (
"github.com/shirou/gopsutil/v4/host"
"github.com/lxc/incus/v7/internal/server/metrics"
"github.com/lxc/incus/v7/internal/server/response"
"github.com/lxc/incus/v7/shared/logger"
"github.com/lxc/incus/v6/internal/server/metrics"
"github.com/lxc/incus/v6/internal/server/response"
"github.com/lxc/incus/v6/shared/logger"
)
var metricsCmd = APIEndpoint{

View File

@ -5,8 +5,8 @@ import (
"net"
"sync"
"github.com/lxc/incus/v7/internal/server/util"
localtls "github.com/lxc/incus/v7/shared/tls"
"github.com/lxc/incus/v6/internal/server/util"
localtls "github.com/lxc/incus/v6/shared/tls"
)
// A variation of the standard tls.Listener that supports atomically swapping

View File

@ -9,11 +9,11 @@ import (
"strings"
"time"
"github.com/lxc/incus/v7/internal/jmap"
"github.com/lxc/incus/v7/internal/server/operations"
"github.com/lxc/incus/v7/internal/server/response"
localUtil "github.com/lxc/incus/v7/internal/server/util"
"github.com/lxc/incus/v7/shared/api"
"github.com/lxc/incus/v6/internal/jmap"
"github.com/lxc/incus/v6/internal/server/operations"
"github.com/lxc/incus/v6/internal/server/response"
localUtil "github.com/lxc/incus/v6/internal/server/util"
"github.com/lxc/incus/v6/shared/api"
)
var operationCmd = APIEndpoint{

View File

@ -16,9 +16,9 @@ import (
"golang.org/x/sys/unix"
"github.com/lxc/incus/v7/internal/version"
"github.com/lxc/incus/v7/shared/api"
"github.com/lxc/incus/v7/shared/logger"
"github.com/lxc/incus/v6/internal/version"
"github.com/lxc/incus/v6/shared/api"
"github.com/lxc/incus/v6/shared/logger"
)
var (

View File

@ -17,8 +17,8 @@ import (
psUtilNet "github.com/shirou/gopsutil/v4/net"
"github.com/shirou/gopsutil/v4/process"
"github.com/lxc/incus/v7/internal/server/metrics"
"github.com/lxc/incus/v7/shared/api"
"github.com/lxc/incus/v6/internal/server/metrics"
"github.com/lxc/incus/v6/shared/api"
)
var (
@ -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

@ -16,17 +16,12 @@ import (
"github.com/shirou/gopsutil/v4/disk"
"golang.org/x/sys/unix"
"github.com/lxc/incus/v7/internal/server/metrics"
"github.com/lxc/incus/v7/shared/api"
"github.com/lxc/incus/v7/shared/revert"
"github.com/lxc/incus/v7/shared/subprocess"
"github.com/lxc/incus/v6/internal/server/metrics"
"github.com/lxc/incus/v6/shared/api"
"github.com/lxc/incus/v6/shared/revert"
"github.com/lxc/incus/v6/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

@ -17,19 +17,14 @@ import (
"github.com/shirou/gopsutil/v4/disk"
"golang.org/x/sys/unix"
"github.com/lxc/incus/v7/internal/server/metrics"
"github.com/lxc/incus/v7/shared/api"
"github.com/lxc/incus/v7/shared/osarch"
"github.com/lxc/incus/v7/shared/revert"
"github.com/lxc/incus/v7/shared/subprocess"
"github.com/lxc/incus/v7/shared/util"
"github.com/lxc/incus/v6/internal/server/metrics"
"github.com/lxc/incus/v6/shared/api"
"github.com/lxc/incus/v6/shared/osarch"
"github.com/lxc/incus/v6/shared/revert"
"github.com/lxc/incus/v6/shared/subprocess"
"github.com/lxc/incus/v6/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

@ -23,21 +23,20 @@ import (
"time"
"github.com/mdlayher/vsock"
"github.com/shirou/gopsutil/v4/process"
"golang.org/x/sys/unix"
"github.com/lxc/incus/v7/internal/linux"
"github.com/lxc/incus/v7/internal/ports"
deviceConfig "github.com/lxc/incus/v7/internal/server/device/config"
"github.com/lxc/incus/v7/internal/server/ip"
"github.com/lxc/incus/v7/internal/server/metrics"
"github.com/lxc/incus/v7/internal/version"
"github.com/lxc/incus/v7/shared/api"
"github.com/lxc/incus/v7/shared/logger"
"github.com/lxc/incus/v7/shared/osarch"
"github.com/lxc/incus/v7/shared/revert"
"github.com/lxc/incus/v7/shared/subprocess"
"github.com/lxc/incus/v7/shared/util"
"github.com/lxc/incus/v6/internal/linux"
"github.com/lxc/incus/v6/internal/ports"
deviceConfig "github.com/lxc/incus/v6/internal/server/device/config"
"github.com/lxc/incus/v6/internal/server/ip"
"github.com/lxc/incus/v6/internal/server/metrics"
"github.com/lxc/incus/v6/internal/version"
"github.com/lxc/incus/v6/shared/api"
"github.com/lxc/incus/v6/shared/logger"
"github.com/lxc/incus/v6/shared/osarch"
"github.com/lxc/incus/v6/shared/revert"
"github.com/lxc/incus/v6/shared/subprocess"
"github.com/lxc/incus/v6/shared/util"
)
var (
@ -580,9 +579,24 @@ func osGetNetworkState() map[string]api.InstanceStateNetwork {
}
func osGetProcessesState() int64 {
pids, err := process.Pids()
if err != nil {
return -1
pids := []int64{1}
// Go through the pid list, adding new pids at the end so we go through them all.
for i := range pids {
fname := fmt.Sprintf("/proc/%d/task/%d/children", pids[i], pids[i])
fcont, err := os.ReadFile(fname)
if err != nil {
// The process terminated during execution of this loop.
continue
}
content := strings.Split(string(fcont), " ")
for j := range content {
pid, err := strconv.ParseInt(content[j], 10, 64)
if err == nil {
pids = append(pids, pid)
}
}
}
return int64(len(pids))

View File

@ -22,13 +22,12 @@ 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"
"github.com/lxc/incus/v7/internal/version"
"github.com/lxc/incus/v7/shared/api"
"github.com/lxc/incus/v7/shared/logger"
"github.com/lxc/incus/v6/internal/ports"
"github.com/lxc/incus/v6/internal/server/metrics"
"github.com/lxc/incus/v6/internal/version"
"github.com/lxc/incus/v6/shared/api"
"github.com/lxc/incus/v6/shared/logger"
)
var (
@ -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

@ -3,7 +3,7 @@ package main
import (
"net/http"
"github.com/lxc/incus/v7/shared/api"
"github.com/lxc/incus/v6/shared/api"
)
type devIncusResponse struct {

View File

@ -10,11 +10,10 @@ import (
"net/http"
"time"
internalIO "github.com/lxc/incus/v7/internal/io"
"github.com/lxc/incus/v7/internal/server/response"
localUtil "github.com/lxc/incus/v7/internal/server/util"
"github.com/lxc/incus/v7/shared/logger"
"github.com/lxc/incus/v7/shared/util"
internalIO "github.com/lxc/incus/v6/internal/io"
"github.com/lxc/incus/v6/internal/server/response"
localUtil "github.com/lxc/incus/v6/internal/server/util"
"github.com/lxc/incus/v6/shared/logger"
)
func restServer(tlsConfig *tls.Config, cert *x509.Certificate, debug bool, d *Daemon) *http.Server {
@ -59,7 +58,7 @@ func createCmd(restAPI *http.ServeMux, version string, c APIEndpoint, cert *x509
newBody := &bytes.Buffer{}
captured := &bytes.Buffer{}
multiW := io.MultiWriter(newBody, captured)
_, err := util.SafeCopy(multiW, r.Body)
_, err := io.Copy(multiW, r.Body)
if err != nil {
_ = response.InternalError(err).Render(w)
return

View File

@ -7,8 +7,7 @@ import (
"github.com/pkg/sftp"
"github.com/lxc/incus/v7/internal/server/response"
"github.com/lxc/incus/v7/shared/logger"
"github.com/lxc/incus/v6/internal/server/response"
)
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

@ -4,8 +4,8 @@ import (
"errors"
"net/http"
"github.com/lxc/incus/v7/internal/server/response"
"github.com/lxc/incus/v7/shared/api"
"github.com/lxc/incus/v6/internal/server/response"
"github.com/lxc/incus/v6/shared/api"
)
var stateCmd = APIEndpoint{

View File

@ -2,16 +2,16 @@ package main
import (
"fmt"
"io"
"io/fs"
"os"
"path/filepath"
"strconv"
"go.yaml.in/yaml/v4"
"gopkg.in/yaml.v2"
"github.com/lxc/incus/v7/shared/api"
"github.com/lxc/incus/v7/shared/logger"
"github.com/lxc/incus/v7/shared/util"
"github.com/lxc/incus/v6/shared/api"
"github.com/lxc/incus/v6/shared/util"
)
func templatesApply(path string) ([]string, error) {
@ -27,7 +27,7 @@ func templatesApply(path string) ([]string, error) {
}
metadata := &api.ImageMetadata{}
err = yaml.Load(content, metadata)
err = yaml.Unmarshal(content, metadata)
if err != nil {
return nil, fmt.Errorf("Could not parse metadata.yaml: %w", err)
}
@ -115,7 +115,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,9 +123,9 @@ 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)
_, err = io.Copy(w, src)
if err != nil {
return err
}

View File

@ -6,16 +6,16 @@ import (
"sync"
"time"
incus "github.com/lxc/incus/v7/client"
"github.com/lxc/incus/v7/internal/version"
"github.com/lxc/incus/v7/shared/api"
config "github.com/lxc/incus/v7/shared/cliconfig"
incus "github.com/lxc/incus/v6/client"
"github.com/lxc/incus/v6/internal/version"
"github.com/lxc/incus/v6/shared/api"
config "github.com/lxc/incus/v6/shared/cliconfig"
)
const userConfigKey = "user.incus-benchmark"
// printServerInfo prints out information about the server.
func printServerInfo(c incus.InstanceServer) error {
// PrintServerInfo prints out information about the server.
func PrintServerInfo(c incus.InstanceServer) error {
server, _, err := c.GetServer()
if err != nil {
return err
@ -36,8 +36,8 @@ func printServerInfo(c incus.InstanceServer) error {
return nil
}
// launchContainers launches a set of containers.
func launchContainers(c incus.InstanceServer, count int, parallel int, image string, privileged bool, start bool, freeze bool) (time.Duration, error) {
// LaunchContainers launches a set of containers.
func LaunchContainers(c incus.InstanceServer, count int, parallel int, image string, privileged bool, start bool, freeze bool) (time.Duration, error) {
var duration time.Duration
batchSize, err := getBatchSize(parallel)
@ -84,8 +84,34 @@ func launchContainers(c incus.InstanceServer, count int, parallel int, image str
return duration, nil
}
// getContainers returns containers created by the benchmark.
func getContainers(c incus.InstanceServer) ([]api.Instance, error) {
// CreateContainers create the specified number of containers.
func CreateContainers(c incus.InstanceServer, count int, parallel int, fingerprint string, privileged bool) (time.Duration, error) {
var duration time.Duration
batchSize, err := getBatchSize(parallel)
if err != nil {
return duration, err
}
batchCreate := func(index int, wg *sync.WaitGroup) {
defer wg.Done()
name := getContainerName(count, index)
err := createContainer(c, fingerprint, name, privileged)
if err != nil {
logf("Failed to launch container '%s': %s", name, err)
return
}
}
duration = processBatch(count, batchSize, batchCreate)
return duration, nil
}
// GetContainers returns containers created by the benchmark.
func GetContainers(c incus.InstanceServer) ([]api.Instance, error) {
containers := []api.Instance{}
allContainers, err := c.GetInstances(api.InstanceTypeContainer)
@ -102,8 +128,8 @@ func getContainers(c incus.InstanceServer) ([]api.Instance, error) {
return containers, nil
}
// startContainers starts containers created by the benchmark.
func startContainers(c incus.InstanceServer, containers []api.Instance, parallel int) (time.Duration, error) {
// StartContainers starts containers created by the benchmark.
func StartContainers(c incus.InstanceServer, containers []api.Instance, parallel int) (time.Duration, error) {
var duration time.Duration
batchSize, err := getBatchSize(parallel)
@ -131,8 +157,8 @@ func startContainers(c incus.InstanceServer, containers []api.Instance, parallel
return duration, nil
}
// stopContainers stops containers created by the benchmark.
func stopContainers(c incus.InstanceServer, containers []api.Instance, parallel int) (time.Duration, error) {
// StopContainers stops containers created by the benchmark.
func StopContainers(c incus.InstanceServer, containers []api.Instance, parallel int) (time.Duration, error) {
var duration time.Duration
batchSize, err := getBatchSize(parallel)
@ -160,8 +186,8 @@ func stopContainers(c incus.InstanceServer, containers []api.Instance, parallel
return duration, nil
}
// deleteContainers removes containers created by the benchmark.
func deleteContainers(c incus.InstanceServer, containers []api.Instance, parallel int) (time.Duration, error) {
// DeleteContainers removes containers created by the benchmark.
func DeleteContainers(c incus.InstanceServer, containers []api.Instance, parallel int) (time.Duration, error) {
var duration time.Duration
batchSize, err := getBatchSize(parallel)

View File

@ -1,8 +1,8 @@
package main
import (
incus "github.com/lxc/incus/v7/client"
"github.com/lxc/incus/v7/shared/api"
incus "github.com/lxc/incus/v6/client"
"github.com/lxc/incus/v6/shared/api"
)
func createContainer(c incus.InstanceServer, fingerprint string, name string, privileged bool) error {
@ -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
@ -29,13 +27,13 @@ type CSVReport struct {
}
// Load reads current content of the filename and loads records.
func (r *CSVReport) load() error {
func (r *CSVReport) Load() error {
file, err := os.Open(r.Filename)
if err != nil {
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++ {
@ -46,7 +44,7 @@ func (r *CSVReport) load() error {
return err
}
err = r.appendRecord(record)
err = r.addRecord(record)
if err != nil {
return err
}
@ -56,13 +54,13 @@ func (r *CSVReport) load() error {
}
// Write writes current records to file.
func (r *CSVReport) write() error {
func (r *CSVReport) Write() error {
file, err := os.OpenFile(r.Filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o640)
if err != nil {
return err
}
defer logger.WarnOnError(file.Close, "Failed to close file")
defer func() { _ = file.Close() }()
writer := csv.NewWriter(file)
err = writer.WriteAll(r.records)
@ -75,9 +73,9 @@ func (r *CSVReport) write() error {
}
// AddRecord adds a record to the report.
func (r *CSVReport) addRecord(label string, elapsed time.Duration) error {
func (r *CSVReport) AddRecord(label string, elapsed time.Duration) error {
if len(r.records) == 0 {
err := r.appendRecord(csvFields)
err := r.addRecord(csvFields)
if err != nil {
return err
}
@ -91,10 +89,10 @@ func (r *CSVReport) addRecord(label string, elapsed time.Duration) error {
"true", // success"
}
return r.appendRecord(record)
return r.addRecord(record)
}
func (r *CSVReport) appendRecord(record []string) error {
func (r *CSVReport) addRecord(record []string) error {
if len(record) != len(csvFields) {
return fmt.Errorf("Invalid number of fields : %q", record)
}

View File

@ -6,10 +6,10 @@ import (
"github.com/spf13/cobra"
incus "github.com/lxc/incus/v7/client"
"github.com/lxc/incus/v7/internal/version"
"github.com/lxc/incus/v7/shared/api"
"github.com/lxc/incus/v7/shared/util"
incus "github.com/lxc/incus/v6/client"
"github.com/lxc/incus/v6/internal/version"
"github.com/lxc/incus/v6/shared/api"
"github.com/lxc/incus/v6/shared/util"
)
type cmdGlobal struct {
@ -25,7 +25,7 @@ type cmdGlobal struct {
reportDuration time.Duration
}
func (c *cmdGlobal) run(cmd *cobra.Command, args []string) error {
func (c *cmdGlobal) Run(cmd *cobra.Command, args []string) error {
// Connect to the daemon
srv, err := incus.ConnectIncusUnix("", nil)
if err != nil {
@ -35,7 +35,7 @@ func (c *cmdGlobal) run(cmd *cobra.Command, args []string) error {
c.srv = srv.UseProject(c.flagProject)
// Print the initial header
err = printServerInfo(srv)
err = PrintServerInfo(srv)
if err != nil {
return err
}
@ -44,7 +44,7 @@ func (c *cmdGlobal) run(cmd *cobra.Command, args []string) error {
if c.flagReportFile != "" {
c.report = &CSVReport{Filename: c.flagReportFile}
if util.PathExists(c.flagReportFile) {
err := c.report.load()
err := c.report.Load()
if err != nil {
return err
}
@ -54,7 +54,7 @@ func (c *cmdGlobal) run(cmd *cobra.Command, args []string) error {
return nil
}
func (c *cmdGlobal) teardown(cmd *cobra.Command, args []string) error {
func (c *cmdGlobal) Teardown(cmd *cobra.Command, args []string) error {
// Nothing to do with not reporting
if c.report == nil {
return nil
@ -65,12 +65,12 @@ func (c *cmdGlobal) teardown(cmd *cobra.Command, args []string) error {
label = c.flagReportLabel
}
err := c.report.addRecord(label, c.reportDuration)
err := c.report.AddRecord(label, c.reportDuration)
if err != nil {
return err
}
err = c.report.write()
err = c.report.Write()
if err != nil {
return err
}
@ -106,8 +106,8 @@ func main() {
// Global flags
globalCmd := cmdGlobal{}
app.PersistentPreRunE = globalCmd.run
app.PersistentPostRunE = globalCmd.teardown
app.PersistentPreRunE = globalCmd.Run
app.PersistentPostRunE = globalCmd.Teardown
app.PersistentFlags().BoolVar(&globalCmd.flagVersion, "version", false, "Print version number")
app.PersistentFlags().BoolVarP(&globalCmd.flagHelp, "help", "h", false, "Print help")
app.PersistentFlags().IntVarP(&globalCmd.flagParallel, "parallel", "P", -1, "Number of threads to use"+"``")
@ -121,23 +121,23 @@ func main() {
// init sub-command
initCmd := cmdInit{global: &globalCmd}
app.AddCommand(initCmd.command())
app.AddCommand(initCmd.Command())
// launch sub-command
launchCmd := cmdLaunch{global: &globalCmd, init: &initCmd}
app.AddCommand(launchCmd.command())
app.AddCommand(launchCmd.Command())
// start sub-command
startCmd := cmdStart{global: &globalCmd}
app.AddCommand(startCmd.command())
app.AddCommand(startCmd.Command())
// stop sub-command
stopCmd := cmdStop{global: &globalCmd}
app.AddCommand(stopCmd.command())
app.AddCommand(stopCmd.Command())
// delete sub-command
deleteCmd := cmdDelete{global: &globalCmd}
app.AddCommand(deleteCmd.command())
app.AddCommand(deleteCmd.Command())
// Run the main command and handle errors
err := app.Execute()

View File

@ -8,24 +8,24 @@ type cmdDelete struct {
global *cmdGlobal
}
func (c *cmdDelete) command() *cobra.Command {
func (c *cmdDelete) Command() *cobra.Command {
cmd := &cobra.Command{}
cmd.Use = "delete"
cmd.Short = "Delete containers"
cmd.RunE = c.run
cmd.RunE = c.Run
return cmd
}
func (c *cmdDelete) run(cmd *cobra.Command, args []string) error {
func (c *cmdDelete) Run(cmd *cobra.Command, args []string) error {
// Get the containers
containers, err := getContainers(c.global.srv)
containers, err := GetContainers(c.global.srv)
if err != nil {
return err
}
// Run the test
duration, err := deleteContainers(c.global.srv, containers, c.global.flagParallel)
duration, err := DeleteContainers(c.global.srv, containers, c.global.flagParallel)
if err != nil {
return err
}

View File

@ -11,18 +11,18 @@ type cmdInit struct {
flagPrivileged bool
}
func (c *cmdInit) command() *cobra.Command {
func (c *cmdInit) Command() *cobra.Command {
cmd := &cobra.Command{}
cmd.Use = "init [[<remote>:]<image>]"
cmd.Short = "Create containers"
cmd.RunE = c.run
cmd.RunE = c.Run
cmd.Flags().IntVarP(&c.flagCount, "count", "C", 1, "Number of containers to create"+"``")
cmd.Flags().BoolVar(&c.flagPrivileged, "privileged", false, "Use privileged containers")
return cmd
}
func (c *cmdInit) run(cmd *cobra.Command, args []string) error {
func (c *cmdInit) Run(cmd *cobra.Command, args []string) error {
// Choose the image
image := "images:debian/12"
if len(args) > 0 {
@ -30,7 +30,7 @@ func (c *cmdInit) run(cmd *cobra.Command, args []string) error {
}
// Run the test
duration, err := launchContainers(c.global.srv, c.flagCount, c.global.flagParallel, image, c.flagPrivileged, false, false)
duration, err := LaunchContainers(c.global.srv, c.flagCount, c.global.flagParallel, image, c.flagPrivileged, false, false)
if err != nil {
return err
}

View File

@ -11,18 +11,18 @@ type cmdLaunch struct {
flagFreeze bool
}
func (c *cmdLaunch) command() *cobra.Command {
func (c *cmdLaunch) Command() *cobra.Command {
cmd := &cobra.Command{}
cmd.Use = "launch [[<remote>:]<image>]"
cmd.Short = "Create and start containers"
cmd.RunE = c.run
cmd.Flags().AddFlagSet(c.init.command().Flags())
cmd.RunE = c.Run
cmd.Flags().AddFlagSet(c.init.Command().Flags())
cmd.Flags().BoolVarP(&c.flagFreeze, "freeze", "F", false, "Freeze the container right after start")
return cmd
}
func (c *cmdLaunch) run(cmd *cobra.Command, args []string) error {
func (c *cmdLaunch) Run(cmd *cobra.Command, args []string) error {
// Choose the image
image := "images:debian/12"
if len(args) > 0 {
@ -30,7 +30,7 @@ func (c *cmdLaunch) run(cmd *cobra.Command, args []string) error {
}
// Run the test
duration, err := launchContainers(c.global.srv, c.init.flagCount, c.global.flagParallel, image, c.init.flagPrivileged, true, c.flagFreeze)
duration, err := LaunchContainers(c.global.srv, c.init.flagCount, c.global.flagParallel, image, c.init.flagPrivileged, true, c.flagFreeze)
if err != nil {
return err
}

Some files were not shown because too many files have changed in this diff Show More