mirror of
https://github.com/lxc/incus
synced 2026-08-03 13:53:22 +00:00
Compare commits
No commits in common. "main" and "v6.23.0" have entirely different histories.
@ -97,7 +97,7 @@ RUN go install -v github.com/google/go-licenses@latest && \
|
||||
go install -v golang.org/x/tools/cmd/goimports@latest && \
|
||||
go install -v golang.org/x/vuln/cmd/govulncheck@latest && \
|
||||
go install -v mvdan.cc/gofumpt@latest && \
|
||||
curl -sSfL https://golangci-lint.run/install.sh | sh -s -- -b $(go env GOPATH)/bin
|
||||
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin
|
||||
|
||||
# Make dependencies
|
||||
COPY Makefile /home/vscode
|
||||
|
||||
6
.github/ISSUE_TEMPLATE/bug-reports.yml
vendored
6
.github/ISSUE_TEMPLATE/bug-reports.yml
vendored
@ -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?
|
||||
|
||||
30
.github/packaging/macos/build.sh
vendored
30
.github/packaging/macos/build.sh
vendored
@ -1,30 +0,0 @@
|
||||
#!/bin/bash
|
||||
# Build a universal Incus client PKG installer.
|
||||
set -eu
|
||||
|
||||
VERSION="${VERSION:?VERSION must be set}"
|
||||
here="$(dirname "$0")"
|
||||
out="installers"
|
||||
root="pkgroot"
|
||||
res="pkgresources"
|
||||
|
||||
mkdir -p "${out}" "${root}/usr/local/bin" "${res}"
|
||||
cp COPYING "${res}/LICENSE.txt"
|
||||
|
||||
CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -o incus.amd64 ./cmd/incus
|
||||
CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 go build -o incus.arm64 ./cmd/incus
|
||||
lipo -create -output "${root}/usr/local/bin/incus" incus.amd64 incus.arm64
|
||||
chmod 0755 "${root}/usr/local/bin/incus"
|
||||
|
||||
pkgbuild --root "${root}" \
|
||||
--identifier org.linuxcontainers.incus \
|
||||
--version "${VERSION}" \
|
||||
--install-location / \
|
||||
incus-component.pkg
|
||||
|
||||
productbuild --distribution "${here}/distribution.xml" \
|
||||
--package-path . \
|
||||
--resources "${res}" \
|
||||
"${out}/incus.macos.pkg"
|
||||
|
||||
rm -rf incus.amd64 incus.arm64 incus-component.pkg "${root}" "${res}"
|
||||
16
.github/packaging/macos/distribution.xml
vendored
16
.github/packaging/macos/distribution.xml
vendored
@ -1,16 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<installer-gui-script minSpecVersion="2">
|
||||
<title>Incus</title>
|
||||
<license file="LICENSE.txt" />
|
||||
<options customize="never" require-scripts="false" hostArchitectures="x86_64,arm64" />
|
||||
<choices-outline>
|
||||
<line choice="default">
|
||||
<line choice="org.linuxcontainers.incus" />
|
||||
</line>
|
||||
</choices-outline>
|
||||
<choice id="default" />
|
||||
<choice id="org.linuxcontainers.incus" visible="false">
|
||||
<pkg-ref id="org.linuxcontainers.incus" />
|
||||
</choice>
|
||||
<pkg-ref id="org.linuxcontainers.incus" version="0" onConclusion="none">incus-component.pkg</pkg-ref>
|
||||
</installer-gui-script>
|
||||
30
.github/packaging/windows/build.sh
vendored
30
.github/packaging/windows/build.sh
vendored
@ -1,30 +0,0 @@
|
||||
#!/bin/bash
|
||||
# Build per-architecture Incus client MSI installers.
|
||||
set -eu
|
||||
|
||||
VERSION="${VERSION:?VERSION must be set}"
|
||||
here="$(dirname "$0")"
|
||||
out="installers"
|
||||
|
||||
mkdir -p "${out}"
|
||||
bash "${here}/make-license-rtf.sh" COPYING "${out}/license.rtf"
|
||||
|
||||
build_one() {
|
||||
goarch="$1"
|
||||
wixarch="$2"
|
||||
name="$3"
|
||||
|
||||
CGO_ENABLED=0 GOOS=windows GOARCH="${goarch}" go build -o "${out}/incus.exe" ./cmd/incus
|
||||
wix build -arch "${wixarch}" \
|
||||
-ext WixToolset.UI.wixext \
|
||||
-d Version="${VERSION}" \
|
||||
-d BinPath="${out}/incus.exe" \
|
||||
-d LicenseRtf="${out}/license.rtf" \
|
||||
-o "${out}/incus.windows.${name}.msi" \
|
||||
"${here}/incus.wxs"
|
||||
}
|
||||
|
||||
build_one amd64 x64 x86_64
|
||||
build_one arm64 arm64 aarch64
|
||||
|
||||
rm -f "${out}/incus.exe" "${out}/license.rtf"
|
||||
33
.github/packaging/windows/incus.wxs
vendored
33
.github/packaging/windows/incus.wxs
vendored
@ -1,33 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"
|
||||
xmlns:ui="http://wixtoolset.org/schemas/v4/wxs/ui">
|
||||
<Package Name="Incus"
|
||||
Manufacturer="Linux Containers"
|
||||
Version="$(var.Version)"
|
||||
UpgradeCode="7E2B5C1D-4A3F-4B8E-9C6D-1F0A2B3C4D5E"
|
||||
Scope="perMachine">
|
||||
<MajorUpgrade DowngradeErrorMessage="A newer version of Incus is already installed." />
|
||||
<MediaTemplate EmbedCab="yes" />
|
||||
|
||||
<StandardDirectory Id="ProgramFiles6432Folder">
|
||||
<Directory Id="INSTALLFOLDER" Name="Incus">
|
||||
<Directory Id="BINFOLDER" Name="bin" />
|
||||
</Directory>
|
||||
</StandardDirectory>
|
||||
|
||||
<ComponentGroup Id="ProductComponents" Directory="BINFOLDER">
|
||||
<Component Id="IncusExe" Guid="*">
|
||||
<File Id="IncusExe" Name="incus.exe" Source="$(var.BinPath)" KeyPath="yes" />
|
||||
<Environment Id="UpdatePath" Name="PATH" Value="[BINFOLDER]"
|
||||
Permanent="no" Part="last" Action="set" System="yes" />
|
||||
</Component>
|
||||
</ComponentGroup>
|
||||
|
||||
<Feature Id="Main">
|
||||
<ComponentGroupRef Id="ProductComponents" />
|
||||
</Feature>
|
||||
|
||||
<ui:WixUI Id="WixUI_Minimal" />
|
||||
<WixVariable Id="WixUILicenseRtf" Value="$(var.LicenseRtf)" />
|
||||
</Package>
|
||||
</Wix>
|
||||
12
.github/packaging/windows/make-license-rtf.sh
vendored
12
.github/packaging/windows/make-license-rtf.sh
vendored
@ -1,12 +0,0 @@
|
||||
#!/bin/bash
|
||||
# Wrap a plain-text license into a minimal RTF for the WiX license dialog.
|
||||
set -eu
|
||||
|
||||
src="$1"
|
||||
dst="$2"
|
||||
|
||||
{
|
||||
printf '{\\rtf1\\ansi\\deff0{\\fonttbl{\\f0 Courier New;}}\\fs16\n'
|
||||
sed -e 's/\\/\\\\/g' -e 's/{/\\{/g' -e 's/}/\\}/g' -e 's/$/\\par/' "$src"
|
||||
printf '}\n'
|
||||
} > "$dst"
|
||||
93
.github/workflows/build.yml
vendored
93
.github/workflows/build.yml
vendored
@ -1,93 +0,0 @@
|
||||
name: Build
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- stable-*
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build (${{ matrix.architecture }})
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
architecture:
|
||||
- amd64
|
||||
- arm64
|
||||
runs-on:
|
||||
- self-hosted
|
||||
- lxc-incus-build
|
||||
- arch-${{ matrix.architecture }}
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v7
|
||||
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v7
|
||||
with:
|
||||
go-version: stable
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install --no-install-recommends -y \
|
||||
autoconf \
|
||||
automake \
|
||||
curl \
|
||||
git \
|
||||
libacl1-dev \
|
||||
libcap-dev \
|
||||
libdbus-1-dev \
|
||||
liblz4-dev \
|
||||
libseccomp-dev \
|
||||
libselinux-dev \
|
||||
libsqlite3-dev \
|
||||
libtool \
|
||||
libudev-dev \
|
||||
libuv1-dev \
|
||||
lxc-dev \
|
||||
make \
|
||||
pkg-config \
|
||||
zip
|
||||
|
||||
- name: Download go dependencies
|
||||
run: |
|
||||
go mod download
|
||||
|
||||
- name: Build cowsql and raft
|
||||
run: |
|
||||
set -x
|
||||
make deps
|
||||
|
||||
raft_path="$(go env GOPATH)/deps/raft"
|
||||
cowsql_path="$(go env GOPATH)/deps/cowsql"
|
||||
{
|
||||
echo "CGO_CFLAGS=-I${raft_path}/include/ -I${cowsql_path}/include/"
|
||||
echo "CGO_LDFLAGS=-L${raft_path}/.libs -L${cowsql_path}/.libs/"
|
||||
echo "LD_LIBRARY_PATH=${raft_path}/.libs/:${cowsql_path}/.libs/"
|
||||
echo "CGO_LDFLAGS_ALLOW=(-Wl,-wrap,pthread_create)|(-Wl,-z,now)"
|
||||
} >> "$GITHUB_ENV"
|
||||
|
||||
- name: Build incusd and incus
|
||||
run: |
|
||||
make
|
||||
|
||||
- name: Collect binaries
|
||||
run: |
|
||||
mkdir -p build
|
||||
cp "$(go env GOPATH)/bin/incusd" build/
|
||||
cp "$(go env GOPATH)/bin/incus" build/
|
||||
|
||||
- name: Upload build artifacts
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: build.${{ matrix.architecture }}
|
||||
path: build/
|
||||
38
.github/workflows/commits.yml
vendored
38
.github/workflows/commits.yml
vendored
@ -12,38 +12,16 @@ jobs:
|
||||
name: Signed-off-by (DCO)
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Check that all commits are signed-off
|
||||
uses: KineticCafe/actions-dco@v3.2.0
|
||||
|
||||
llm-commit-policy:
|
||||
permissions:
|
||||
contents: none
|
||||
name: LLM commit policy
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v7
|
||||
- name: Get PR Commits
|
||||
id: 'get-pr-commits'
|
||||
uses: tim-actions/get-pr-commits@master
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
fetch-depth: 0
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Check LLM commit policy
|
||||
run: |
|
||||
set -eu
|
||||
|
||||
# Inspired by https://github.com/yaml/go-yaml/pull/340
|
||||
agents="(aider|anthropic|claude|codex|copilot|devin|gemini|grok|openai)"
|
||||
|
||||
commits=$(git rev-list ${{ github.event.pull_request.base.sha }}..${{ github.event.pull_request.head.sha }})
|
||||
for commit in $commits; do
|
||||
if git log -n 1 "$commit" | tr '[:upper:]' '[:lower:]' | grep -qE "(author|assisted-by|co-authored-by|signed-off-by):.*$agents"; then
|
||||
echo "Error: The following commit appears to violate this repo's LLM/AI contribution policy:"
|
||||
echo ""
|
||||
git log -n 1 "$commit"
|
||||
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
- name: Check that all commits are signed-off
|
||||
uses: tim-actions/dco@master
|
||||
with:
|
||||
commits: ${{ steps.get-pr-commits.outputs.commits }}
|
||||
|
||||
target-branch:
|
||||
permissions:
|
||||
|
||||
107
.github/workflows/release.yml
vendored
107
.github/workflows/release.yml
vendored
@ -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
|
||||
|
||||
32
.github/workflows/slop.yml
vendored
32
.github/workflows/slop.yml
vendored
@ -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",
|
||||
});
|
||||
114
.github/workflows/tests.yml
vendored
114
.github/workflows/tests.yml
vendored
@ -26,13 +26,13 @@ jobs:
|
||||
- tip
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v7
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
# Differential ShellCheck requires full git history
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Dependency Review
|
||||
uses: actions/dependency-review-action@v5
|
||||
uses: actions/dependency-review-action@v4
|
||||
if: github.event_name == 'pull_request'
|
||||
with:
|
||||
allow-ghsas: GHSA-4p9m-8gc4-rw2h
|
||||
@ -55,13 +55,13 @@ jobs:
|
||||
if: github.event_name == 'pull_request' && matrix.go == 'stable'
|
||||
|
||||
- name: Install Go (${{ matrix.go }})
|
||||
uses: actions/setup-go@v7
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: ${{ matrix.go }}
|
||||
if: matrix.go != 'tip'
|
||||
|
||||
- name: Install Go (stable)
|
||||
uses: actions/setup-go@v7
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: stable
|
||||
if: matrix.go == 'tip'
|
||||
@ -241,23 +241,17 @@ jobs:
|
||||
sudo ip link delete docker0
|
||||
sudo nft flush ruleset
|
||||
|
||||
- name: Remove pre-installed Java
|
||||
run: |
|
||||
set -eux
|
||||
sudo apt-get update
|
||||
sudo apt-get remove --yes --purge temurin.*
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v7
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Install Go (${{ matrix.go }})
|
||||
uses: actions/setup-go@v7
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: ${{ matrix.go }}
|
||||
if: matrix.go != 'tip'
|
||||
|
||||
- name: Install Go (stable)
|
||||
uses: actions/setup-go@v7
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: stable
|
||||
if: matrix.go == 'tip'
|
||||
@ -271,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: |
|
||||
|
||||
2
.github/workflows/triage.yml
vendored
2
.github/workflows/triage.yml
vendored
@ -13,7 +13,7 @@ jobs:
|
||||
name: PR labels
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/labeler@v7
|
||||
- uses: actions/labeler@v6
|
||||
with:
|
||||
repo-token: "${{ secrets.GITHUB_TOKEN }}"
|
||||
sync-labels: true
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@ -2,7 +2,6 @@
|
||||
po/*.mo
|
||||
po/*.po~
|
||||
incus-*.tar.xz
|
||||
installers
|
||||
.vagrant
|
||||
*~
|
||||
tags
|
||||
@ -11,6 +10,7 @@ tags
|
||||
cmd/fuidshift/fuidshift
|
||||
cmd/incus/incus
|
||||
cmd/lxc-to-incus/lxc-to-incus
|
||||
cmd/lxd-to-incus/lxd-to-incus
|
||||
cmd/incus-agent/incus-agent
|
||||
cmd/incus-benchmark/incus-benchmark
|
||||
cmd/incus-migrate/incus-migrate
|
||||
|
||||
@ -79,10 +79,6 @@ linters:
|
||||
text: "avoid meaningless package names"
|
||||
- path: internal/server/util/
|
||||
text: "avoid meaningless package names"
|
||||
- path: shared/uefi/guid.go
|
||||
linters:
|
||||
- revive
|
||||
text: '^(var-naming|exported):'
|
||||
paths:
|
||||
- third_party$
|
||||
- builtin$
|
||||
|
||||
@ -66,6 +66,19 @@ archives:
|
||||
{{- else if eq .Arch "arm64" }}aarch64
|
||||
{{- else }}{{ .Arch }}{{ end }}
|
||||
|
||||
- id: lxd-to-incus
|
||||
ids:
|
||||
- lxd-to-incus
|
||||
formats:
|
||||
- binary
|
||||
name_template: >-
|
||||
bin.
|
||||
{{- if eq .Os "darwin" }}macos
|
||||
{{- else }}{{ .Os }}{{ end }}.lxd-to-incus.
|
||||
{{- if eq .Arch "amd64" }}x86_64
|
||||
{{- else if eq .Arch "arm64" }}aarch64
|
||||
{{- else }}{{ .Arch }}{{ end }}
|
||||
|
||||
before:
|
||||
hooks:
|
||||
- go mod download
|
||||
@ -139,6 +152,16 @@ builds:
|
||||
- amd64
|
||||
- arm64
|
||||
|
||||
- id: lxd-to-incus
|
||||
main: ./cmd/lxd-to-incus
|
||||
env:
|
||||
- CGO_ENABLED=0
|
||||
goos:
|
||||
- linux
|
||||
goarch:
|
||||
- amd64
|
||||
- arm64
|
||||
|
||||
changelog:
|
||||
use: "github-native"
|
||||
|
||||
@ -162,9 +185,6 @@ release:
|
||||
owner: lxc
|
||||
name: incus
|
||||
name_template: "Incus {{ .Env.INCUS_VERSION }}"
|
||||
extra_files:
|
||||
- glob: ./installers/*.msi
|
||||
- glob: ./installers/*.pkg
|
||||
|
||||
sboms:
|
||||
- id: archive
|
||||
@ -175,6 +195,7 @@ sboms:
|
||||
- incus-benchmark
|
||||
- incus-migrate
|
||||
- incus-simplestreams
|
||||
- lxd-to-incus
|
||||
- id: source
|
||||
artifacts: source
|
||||
|
||||
|
||||
20
AGENTS.md
20
AGENTS.md
@ -1,20 +0,0 @@
|
||||
# Legal
|
||||
|
||||
- All contributions to this repository must be compatible with the Apache 2.0 license.
|
||||
- Specifically (but not limited to), contributions cannot include code licensed under the terms of the GPL, AGPL or LGPL licenses.
|
||||
- Only human beings are allowed to sign the Developer Certificate of Ownership (DCO / Signed-off-by).
|
||||
- Only human beings can ever be credited within commit messages.
|
||||
|
||||
# Formatting
|
||||
|
||||
- Code comments should be no longer than one line, unless they are required to cover complex unintuitive logic.
|
||||
- Commit messages should similarly be kept as short and to the point as possible, no need to summarize the whole issue.
|
||||
- We don't use the define and test one line `if` syntax, instead splitting defintion and testing across two lines.
|
||||
|
||||
# Testing / validation
|
||||
|
||||
- The commit structure described in `CONTRIBUTING.md` should generally be followed.
|
||||
- All branches are expected to pass `make static-analysis` and `go test -v ./...`.
|
||||
- Excessive unit tests are generally discouraged.
|
||||
- When possible, existing system tests should be extended to cover new features.
|
||||
- A full local system test run isn't required prior to contribution, all tests get run in our CI.
|
||||
@ -18,83 +18,21 @@ By default, any contribution to this project is made under the Apache
|
||||
The author of a change remains the copyright holder of their code
|
||||
(no copyright assignment).
|
||||
|
||||
## Policy on the use of Large Language Models (LLMs) and AI tooling
|
||||
### For issue reporting
|
||||
## No Large Language Models (LLMs) or similar AI tools
|
||||
|
||||
We do NOT allow direct filing of issues by LLMs.
|
||||
All contributions to this project are expected to be done by human
|
||||
beings or through standard predictable tooling (e.g. scripts, formatters, ...).
|
||||
|
||||
We REQUIRE a human being to go through our issue reporting form on
|
||||
Github and accurately describe their issue and provide all needed
|
||||
information.
|
||||
We expect all contributors to be able to reason about the code that they
|
||||
contribute and explain why they're taking a particular approach.
|
||||
|
||||
The more concise and to the point the issue is, the more likely it is to
|
||||
be understood, tracked down and resolved quickly.
|
||||
LLMs and similar predictive tools have the annoying tendency of
|
||||
producing large amount of low quality code with subtle issues which end
|
||||
up taking the maintainers more time to debug than it would have taken to
|
||||
write the code by hand in the first place.
|
||||
|
||||
Long winded AI written essays can easily look overwhelming and cause our
|
||||
maintainers and other contributors to just entirely skip the issue to
|
||||
focus their energy on something else.
|
||||
|
||||
We also don't benefit from AI generated root cause analysis or proposed
|
||||
fixes in those issues. If you yourself understand the code base well
|
||||
enough to go through that content and suggested fix, then turn it into a
|
||||
pull request and submit it yourself. Otherwise, please limit your report
|
||||
to describing the issue at hand and we'll take it from there.
|
||||
|
||||
### For contributions
|
||||
|
||||
We REQUIRE all contributions to Incus to be submitted by human beings who
|
||||
can assert full copyright ownership of their contribution or have been
|
||||
allowed by their employer to contribute. This is what the DCO (see below)
|
||||
requires of all contributors.
|
||||
|
||||
AI tools can sometimes be beneficial, particularly when it comes to
|
||||
finding patterns among a large data set (entire code base), performing
|
||||
tedious repetitive changes or large refactoring/re-organization.
|
||||
|
||||
While we now tolerate the use of such tools, they must abide by our
|
||||
instructions (`AGENTS.md`) and their operators cannot override those
|
||||
instructions.
|
||||
|
||||
We expect everyone contributing to Incus to fully own their
|
||||
contribution, be able to reason about it, be able to explain why things
|
||||
were done a particular way and act as the full owner of that code. AI
|
||||
tools are treated the same as traditional tooling like `sed`, `awk` or
|
||||
`coccinelle`.
|
||||
|
||||
For the purpose of this project, AI tools CANNOT be treated as author,
|
||||
co-author or be credited in any way that would suggest any ownership
|
||||
over the contribution.
|
||||
|
||||
The contributor should have done all the thinking, planning and
|
||||
understanding of the changes needed to resolve an issue or implement a
|
||||
new feature prior to using automated tooling to perform the grunt work.
|
||||
|
||||
Unguided use of those tools or the inability to prove understanding of
|
||||
the code contributed will result in a loss of trust in that contributor
|
||||
by project maintainers which can then lead to exclusion from any further
|
||||
contribution to the project.
|
||||
|
||||
It's also worth pointing out that while those tools are good at
|
||||
implementing the more boring/repetitive/grunt work. We've generally
|
||||
found that you only really understand the project and its structure by
|
||||
having done such work yourself a few times.
|
||||
|
||||
### For anyone with write access to the repository
|
||||
|
||||
Anyone with write access to this repository must ensure to NEVER run an
|
||||
AI agent or similar tool on a system which holds repository credentials
|
||||
(SSH key, GPG key, web browser cookies, ...).
|
||||
|
||||
Any use of AI tooling should be done inside of a clean VM/container that
|
||||
itself cannot directly push to or alter this repository in any way.
|
||||
|
||||
The safest approach is to SSH into that environment and then extract the
|
||||
changes using `git format-patch`, then review and apply them to your
|
||||
actual tree, tweak them as needed, sign them off and then push and open
|
||||
the pull request.
|
||||
|
||||
Any potential credential compromise or loss of control should be
|
||||
immediately reported to `security@linuxcontainers.org`.
|
||||
Any attempt at hiding the use of LLMs or similar tools in Incus contributions
|
||||
will result in a revert of the affected changes and a ban from the project.
|
||||
|
||||
## Pull requests
|
||||
|
||||
|
||||
309
Makefile
309
Makefile
@ -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 ./...
|
||||
|
||||
@ -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 | [](https://github.com/lxc/incus/actions?query=event%3Apush+branch%3Amain)
|
||||
Go documentation | Godoc | [](https://godoc.org/github.com/lxc/incus/v7/client)
|
||||
Go documentation | Godoc | [](https://godoc.org/github.com/lxc/incus/v6/client)
|
||||
Static analysis | GoReport | [](https://goreportcard.com/report/github.com/lxc/incus)
|
||||
Translations | Weblate | [](https://hosted.weblate.org/projects/incus/)
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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{
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -15,9 +15,6 @@ type ProtocolOCI struct {
|
||||
// Cache for images.
|
||||
cache map[string]ociInfo
|
||||
|
||||
// Error tracking for images.
|
||||
errors map[string]error
|
||||
|
||||
tempPath string
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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 != "" {
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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}
|
||||
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -9,5 +9,4 @@ var Imports = []string{
|
||||
"fmt",
|
||||
"strings",
|
||||
"github.com/mattn/go-sqlite3",
|
||||
"github.com/google/uuid",
|
||||
}
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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)")
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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":
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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"
|
||||
}
|
||||
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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,
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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}
|
||||
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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{
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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{
|
||||
|
||||
@ -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 (
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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")
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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))
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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)
|
||||
}
|
||||
@ -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 {
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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{
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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
Loading…
Reference in New Issue
Block a user