Compare commits
12 Commits
fd0be6d493
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
d6953c8a78
|
|||
|
557948a633
|
|||
|
727013e42a
|
|||
|
821f0e9f59
|
|||
|
b8b1bbcdf7
|
|||
|
48451c6717
|
|||
|
7f84ceaafd
|
|||
|
959257eac8
|
|||
|
476906fe97
|
|||
|
6fd95c24fb
|
|||
|
638e9f42d5
|
|||
|
417c304f2d
|
198
.github/workflows/ci.yml
vendored
198
.github/workflows/ci.yml
vendored
@@ -1,198 +0,0 @@
|
|||||||
name: CI
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [main]
|
|
||||||
pull_request:
|
|
||||||
branches: [main]
|
|
||||||
schedule:
|
|
||||||
- cron: '0 3 * * *' # Nightly at 3 AM UTC
|
|
||||||
workflow_dispatch:
|
|
||||||
inputs:
|
|
||||||
run_conformance:
|
|
||||||
description: 'Run conformance tests'
|
|
||||||
required: false
|
|
||||||
default: 'false'
|
|
||||||
type: boolean
|
|
||||||
formae_version:
|
|
||||||
description: 'Formae version to test against (default: latest)'
|
|
||||||
required: false
|
|
||||||
default: 'latest'
|
|
||||||
type: string
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Set up Go
|
|
||||||
uses: actions/setup-go@v5
|
|
||||||
with:
|
|
||||||
go-version: "1.25"
|
|
||||||
|
|
||||||
- name: Set up Pkl
|
|
||||||
uses: pkl-community/setup-pkl@v0
|
|
||||||
with:
|
|
||||||
pkl-version: 0.30.0
|
|
||||||
|
|
||||||
- name: Build
|
|
||||||
run: make build
|
|
||||||
|
|
||||||
- name: Test
|
|
||||||
run: make test-unit
|
|
||||||
|
|
||||||
lint:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Set up Go
|
|
||||||
uses: actions/setup-go@v5
|
|
||||||
with:
|
|
||||||
go-version: "1.25"
|
|
||||||
|
|
||||||
- name: golangci-lint
|
|
||||||
uses: golangci/golangci-lint-action@v8.0.0
|
|
||||||
with:
|
|
||||||
version: latest
|
|
||||||
|
|
||||||
pkl-validate:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Set up Pkl
|
|
||||||
uses: pkl-community/setup-pkl@v0
|
|
||||||
with:
|
|
||||||
pkl-version: 0.30.0
|
|
||||||
|
|
||||||
- name: Validate Pkl schemas
|
|
||||||
run: |
|
|
||||||
pkl eval formae-plugin.pkl --format json > /dev/null
|
|
||||||
echo "Manifest validated successfully"
|
|
||||||
|
|
||||||
# Integration tests run against real infrastructure.
|
|
||||||
# This job is disabled by default - enable it after configuring credentials.
|
|
||||||
integration-tests:
|
|
||||||
needs: [build, lint]
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
# Disabled by default - remove this condition after configuring credentials
|
|
||||||
if: false
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Set up Go
|
|
||||||
uses: actions/setup-go@v5
|
|
||||||
with:
|
|
||||||
go-version: "1.25"
|
|
||||||
|
|
||||||
- name: Set up Pkl
|
|
||||||
uses: pkl-community/setup-pkl@v0
|
|
||||||
with:
|
|
||||||
pkl-version: 0.30.0
|
|
||||||
|
|
||||||
# Configure credentials for your infrastructure here
|
|
||||||
# Example:
|
|
||||||
# env:
|
|
||||||
# MY_API_KEY: ${{ secrets.MY_API_KEY }}
|
|
||||||
|
|
||||||
- name: Run integration tests
|
|
||||||
run: make test-integration
|
|
||||||
|
|
||||||
# Conformance tests run against real cloud resources.
|
|
||||||
# This job is disabled by default - enable it after configuring credentials.
|
|
||||||
#
|
|
||||||
# To enable:
|
|
||||||
# 1. Configure credentials for your cloud provider (see below)
|
|
||||||
# 2. Implement scripts/ci/setup-credentials.sh for local credential verification
|
|
||||||
# 3. Implement scripts/ci/clean-environment.sh for test resource cleanup
|
|
||||||
# 4. Change the 'if' condition to enable the job
|
|
||||||
conformance-tests:
|
|
||||||
needs: [build, lint, pkl-validate]
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
# Disabled by default - change condition after configuring credentials:
|
|
||||||
# if: github.event_name == 'schedule' || github.event_name == 'push' || github.event.inputs.run_conformance == 'true'
|
|
||||||
if: ${{ github.event.inputs.run_conformance == 'true' }}
|
|
||||||
timeout-minutes: 60
|
|
||||||
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
# Test against these formae versions. Expand as needed:
|
|
||||||
# formae_version: ['0.78.0', '0.79.0', 'latest']
|
|
||||||
formae_version: ['latest']
|
|
||||||
|
|
||||||
# Uncomment and configure for your cloud provider:
|
|
||||||
# permissions:
|
|
||||||
# id-token: write # Required for OIDC
|
|
||||||
# contents: read
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Set up Go
|
|
||||||
uses: actions/setup-go@v5
|
|
||||||
with:
|
|
||||||
go-version: "1.25"
|
|
||||||
|
|
||||||
- name: Set up Pkl
|
|
||||||
uses: pkl-community/setup-pkl@v0
|
|
||||||
with:
|
|
||||||
pkl-version: 0.30.0
|
|
||||||
|
|
||||||
# =================================================================
|
|
||||||
# CREDENTIAL SETUP - Uncomment and configure for your provider
|
|
||||||
# =================================================================
|
|
||||||
|
|
||||||
# AWS (OIDC - recommended)
|
|
||||||
# - name: Configure AWS Credentials
|
|
||||||
# uses: aws-actions/configure-aws-credentials@v4
|
|
||||||
# with:
|
|
||||||
# aws-region: us-east-1
|
|
||||||
# role-to-assume: arn:aws:iam::ACCOUNT_ID:role/ROLE_NAME
|
|
||||||
# role-session-name: ConformanceTests
|
|
||||||
|
|
||||||
# Azure (OIDC - recommended)
|
|
||||||
# - name: Configure Azure Credentials
|
|
||||||
# uses: azure/login@v2
|
|
||||||
# with:
|
|
||||||
# client-id: ${{ secrets.AZURE_CLIENT_ID }}
|
|
||||||
# tenant-id: ${{ secrets.AZURE_TENANT_ID }}
|
|
||||||
# subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
|
|
||||||
|
|
||||||
# GCP (OIDC - recommended)
|
|
||||||
# - name: Configure GCP Credentials
|
|
||||||
# uses: google-github-actions/auth@v2
|
|
||||||
# with:
|
|
||||||
# workload_identity_provider: projects/PROJECT_ID/locations/global/workloadIdentityPools/POOL/providers/PROVIDER
|
|
||||||
# service_account: SA_NAME@PROJECT_ID.iam.gserviceaccount.com
|
|
||||||
|
|
||||||
# OpenStack (secrets-based)
|
|
||||||
# - name: Configure OpenStack Credentials
|
|
||||||
# run: |
|
|
||||||
# echo "OS_AUTH_URL=${{ secrets.OS_AUTH_URL }}" >> $GITHUB_ENV
|
|
||||||
# echo "OS_USERNAME=${{ secrets.OS_USERNAME }}" >> $GITHUB_ENV
|
|
||||||
# echo "OS_PASSWORD=${{ secrets.OS_PASSWORD }}" >> $GITHUB_ENV
|
|
||||||
# echo "OS_PROJECT_ID=${{ secrets.OS_PROJECT_ID }}" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
# =================================================================
|
|
||||||
|
|
||||||
- name: Install plugin
|
|
||||||
run: make install
|
|
||||||
|
|
||||||
- name: Run conformance tests
|
|
||||||
env:
|
|
||||||
FORMAE_TEST_RUN_ID: ${{ github.run_id }}-${{ github.run_attempt }}
|
|
||||||
run: |
|
|
||||||
# Use input version if provided, otherwise use matrix version
|
|
||||||
VERSION="${{ inputs.formae_version }}"
|
|
||||||
if [ "$VERSION" = "" ] || [ "$VERSION" = "latest" ]; then
|
|
||||||
VERSION="${{ matrix.formae_version }}"
|
|
||||||
fi
|
|
||||||
make conformance-test VERSION="$VERSION"
|
|
||||||
3
Makefile
3
Makefile
@@ -123,3 +123,6 @@ conformance-test-discovery: install
|
|||||||
test-request:
|
test-request:
|
||||||
curl -k -H "Authorization: PVEAPIToken=$$PROXMOX_USERNAME=$$PROXMOX_TOKEN" "https://proxmox.mid:8006/api2/json/nodes/proxmox/lxc" | jq
|
curl -k -H "Authorization: PVEAPIToken=$$PROXMOX_USERNAME=$$PROXMOX_TOKEN" "https://proxmox.mid:8006/api2/json/nodes/proxmox/lxc" | jq
|
||||||
curl -k -H "Authorization: PVEAPIToken=$$PROXMOX_USERNAME=$$PROXMOX_TOKEN" "https://proxmox.mid:8006/api2/json/nodes/proxmox/lxc/200/config" | jq
|
curl -k -H "Authorization: PVEAPIToken=$$PROXMOX_USERNAME=$$PROXMOX_TOKEN" "https://proxmox.mid:8006/api2/json/nodes/proxmox/lxc/200/config" | jq
|
||||||
|
|
||||||
|
test-delete:
|
||||||
|
curl -k -H "Authorization: PVEAPIToken=$$PROXMOX_USERNAME=$$PROXMOX_TOKEN" "https://proxmox.mid:8006/api2/json/nodes/proxmox/lxc/210/config" -X PUT --data "delete=net0,net1,net2" | jq
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ description = "Proxmox Plugin"
|
|||||||
|
|
||||||
license = "Apache-2.0"
|
license = "Apache-2.0"
|
||||||
|
|
||||||
minFormaeVersion = "0.80.1"
|
minFormaeVersion = "0.82.2"
|
||||||
|
|
||||||
output {
|
output {
|
||||||
renderer = new JsonRenderer {}
|
renderer = new JsonRenderer {}
|
||||||
|
|||||||
52
go.mod
52
go.mod
@@ -1,53 +1,63 @@
|
|||||||
module github.com/platform-engineering-labs/formae-plugin-proxmox
|
module github.com/platform-engineering-labs/formae-plugin-proxmox
|
||||||
|
|
||||||
go 1.25
|
go 1.25.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/platform-engineering-labs/formae/pkg/plugin v0.1.8
|
github.com/platform-engineering-labs/formae/pkg/plugin v0.1.14
|
||||||
github.com/platform-engineering-labs/formae/pkg/plugin-conformance-tests v0.1.9
|
github.com/platform-engineering-labs/formae/pkg/plugin-conformance-tests v0.1.21
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
|
github.com/ebitengine/purego v0.10.0 // indirect
|
||||||
|
github.com/go-ole/go-ole v1.3.0 // indirect
|
||||||
|
github.com/lufia/plan9stats v0.0.0-20260216142805-b3301c5f2a88 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
|
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
|
||||||
|
github.com/shirou/gopsutil/v4 v4.26.1 // indirect
|
||||||
|
github.com/tklauser/go-sysconf v0.3.16 // indirect
|
||||||
|
github.com/tklauser/numcpus v0.11.0 // indirect
|
||||||
|
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||||
|
go.opentelemetry.io/contrib/instrumentation/host v0.65.0 // indirect
|
||||||
|
go.opentelemetry.io/contrib/instrumentation/runtime v0.65.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
ergo.services/actor/statemachine v0.0.0-20251202053101-c0aa08b403e5 // indirect
|
ergo.services/actor/statemachine v0.0.0-20251202053101-c0aa08b403e5 // indirect
|
||||||
ergo.services/ergo v1.999.310 // indirect
|
ergo.services/ergo v1.999.310 // indirect
|
||||||
github.com/apple/pkl-go v0.12.0 // indirect
|
github.com/apple/pkl-go v0.13.0 // indirect
|
||||||
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
|
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
|
||||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||||
github.com/go-logr/logr v1.4.3 // indirect
|
github.com/go-logr/logr v1.4.3 // indirect
|
||||||
github.com/go-logr/stdr v1.2.2 // indirect
|
github.com/go-logr/stdr v1.2.2 // indirect
|
||||||
github.com/google/uuid v1.6.0 // indirect
|
github.com/google/uuid v1.6.0 // indirect
|
||||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 // indirect
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 // indirect
|
||||||
github.com/masterminds/semver v1.5.0 // indirect
|
github.com/masterminds/semver v1.5.0 // indirect
|
||||||
github.com/platform-engineering-labs/formae/pkg/api/model v0.1.1 // indirect
|
github.com/platform-engineering-labs/formae/pkg/api/model v0.1.1 // indirect
|
||||||
github.com/platform-engineering-labs/formae/pkg/model v0.1.2 // indirect
|
github.com/platform-engineering-labs/formae/pkg/model v0.1.5 // indirect
|
||||||
github.com/stretchr/testify v1.11.1
|
github.com/stretchr/testify v1.11.1
|
||||||
github.com/theory/jsonpath v0.10.2 // indirect
|
github.com/theory/jsonpath v0.10.2 // indirect
|
||||||
github.com/tidwall/gjson v1.18.0 // indirect
|
github.com/tidwall/gjson v1.18.0 // indirect
|
||||||
github.com/tidwall/match v1.1.1 // indirect
|
github.com/tidwall/match v1.2.0 // indirect
|
||||||
github.com/tidwall/pretty v1.2.1 // indirect
|
github.com/tidwall/pretty v1.2.1 // indirect
|
||||||
github.com/tidwall/sjson v1.2.5 // indirect
|
github.com/tidwall/sjson v1.2.5 // indirect
|
||||||
github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect
|
github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect
|
||||||
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
|
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
|
||||||
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
|
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
|
||||||
go.opentelemetry.io/otel v1.39.0 // indirect
|
go.opentelemetry.io/otel v1.40.0 // indirect
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.39.0 // indirect
|
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.40.0 // indirect
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.39.0 // indirect
|
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.40.0 // indirect
|
||||||
go.opentelemetry.io/otel/metric v1.39.0 // indirect
|
go.opentelemetry.io/otel/metric v1.40.0 // indirect
|
||||||
go.opentelemetry.io/otel/sdk v1.39.0 // indirect
|
go.opentelemetry.io/otel/sdk v1.40.0 // indirect
|
||||||
go.opentelemetry.io/otel/sdk/metric v1.39.0 // indirect
|
go.opentelemetry.io/otel/sdk/metric v1.40.0 // indirect
|
||||||
go.opentelemetry.io/otel/trace v1.39.0 // indirect
|
go.opentelemetry.io/otel/trace v1.40.0 // indirect
|
||||||
go.opentelemetry.io/proto/otlp v1.9.0 // indirect
|
go.opentelemetry.io/proto/otlp v1.9.0 // indirect
|
||||||
golang.org/x/net v0.47.0 // indirect
|
golang.org/x/net v0.51.0 // indirect
|
||||||
golang.org/x/sys v0.39.0 // indirect
|
golang.org/x/sys v0.41.0 // indirect
|
||||||
golang.org/x/text v0.31.0 // indirect
|
golang.org/x/text v0.34.0 // indirect
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 // indirect
|
google.golang.org/genproto/googleapis/api v0.0.0-20260226221140-a57be14db171 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 // indirect
|
||||||
google.golang.org/grpc v1.77.0 // indirect
|
google.golang.org/grpc v1.79.1 // indirect
|
||||||
google.golang.org/protobuf v1.36.10 // indirect
|
google.golang.org/protobuf v1.36.11 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
111
go.sum
111
go.sum
@@ -4,39 +4,56 @@ ergo.services/ergo v1.999.310 h1:qHx35J5UxCheNJaFrOK/1K6/p7jir680+NTPZo6bhbI=
|
|||||||
ergo.services/ergo v1.999.310/go.mod h1:bLQ6PoO6Mz/8gVuzvPv3xfMfo1P9w6rZV1WnMXMeMdg=
|
ergo.services/ergo v1.999.310/go.mod h1:bLQ6PoO6Mz/8gVuzvPv3xfMfo1P9w6rZV1WnMXMeMdg=
|
||||||
github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww=
|
github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww=
|
||||||
github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
|
github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
|
||||||
github.com/apple/pkl-go v0.12.0 h1:0gnhEIXo6coSHPpxdOESfGn2GrSkBSaeitkZLwZAcWE=
|
github.com/apple/pkl-go v0.13.0 h1:dWaTtgVtatyiECJbdsVmZ/n3JJZTDOyTum/QBHEFv5o=
|
||||||
github.com/apple/pkl-go v0.12.0/go.mod h1:EDQmYVtFBok/eLI+9rT0EoBBXNtMM1THwR+rwBcAH3I=
|
github.com/apple/pkl-go v0.13.0/go.mod h1:Ko3AgXOKd/vVYtsRZgoCZhymymz9RxqCIcfdZhOX85I=
|
||||||
github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM=
|
github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM=
|
||||||
github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
|
github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
|
||||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/ebitengine/purego v0.10.0 h1:QIw4xfpWT6GWTzaW5XEKy3HXoqrJGx1ijYHzTF0/ISU=
|
||||||
|
github.com/ebitengine/purego v0.10.0/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
||||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||||
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
||||||
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||||
|
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||||
|
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
|
||||||
|
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
|
||||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 h1:NmZ1PKzSTQbuGHw9DGPFomqkkLWMC+vZCkfs+FHv1Vg=
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 h1:HWRh5R2+9EifMyIHV7ZV+MIZqgz+PMpZ14Jynv3O2Zs=
|
||||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3/go.mod h1:zQrxl1YP88HQlA6i9c63DSVPFklWpGX4OWAc9bFuaH4=
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0/go.mod h1:JfhWUomR1baixubs02l85lZYYOm7LV6om4ceouMv45c=
|
||||||
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
|
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||||
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
|
github.com/lufia/plan9stats v0.0.0-20260216142805-b3301c5f2a88 h1:PTw+yKnXcOFCR6+8hHTyWBeQ/P4Nb7dd4/0ohEcWQuM=
|
||||||
|
github.com/lufia/plan9stats v0.0.0-20260216142805-b3301c5f2a88/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg=
|
||||||
github.com/masterminds/semver v1.5.0 h1:hTxJTTY7tjvnWMrl08O6u3G6BLlKVwxSz01lVac9P8U=
|
github.com/masterminds/semver v1.5.0 h1:hTxJTTY7tjvnWMrl08O6u3G6BLlKVwxSz01lVac9P8U=
|
||||||
github.com/masterminds/semver v1.5.0/go.mod h1:s7KNT9fnd7edGzwwP7RBX4H0v/CYd5qdOLfkL1V75yg=
|
github.com/masterminds/semver v1.5.0/go.mod h1:s7KNT9fnd7edGzwwP7RBX4H0v/CYd5qdOLfkL1V75yg=
|
||||||
github.com/platform-engineering-labs/formae/pkg/api/model v0.1.1 h1:ZMTgKwSomy2cVcl/+NivSqopbWeHbmYeQ+BxoYq8bVY=
|
github.com/platform-engineering-labs/formae/pkg/api/model v0.1.1 h1:ZMTgKwSomy2cVcl/+NivSqopbWeHbmYeQ+BxoYq8bVY=
|
||||||
github.com/platform-engineering-labs/formae/pkg/api/model v0.1.1/go.mod h1:0ncHFCsGA6b0w1kBm6m+QwJ823qAY2vL47GvoR0BTyU=
|
github.com/platform-engineering-labs/formae/pkg/api/model v0.1.1/go.mod h1:0ncHFCsGA6b0w1kBm6m+QwJ823qAY2vL47GvoR0BTyU=
|
||||||
github.com/platform-engineering-labs/formae/pkg/model v0.1.2 h1:FgjN2mS/9bLAlRIbAfIiD1PYeZ8fxNFVP4JyTQF6FXc=
|
github.com/platform-engineering-labs/formae/pkg/model v0.1.5 h1:MNOLv5gXT3hCcVXgpJWwQz8c3ORH3e1c5jXhpuVCVVo=
|
||||||
github.com/platform-engineering-labs/formae/pkg/model v0.1.2/go.mod h1:XmGJA7jNPX9cEGc8TxTiEDitBuEVJOddNakdTZ/bH4U=
|
github.com/platform-engineering-labs/formae/pkg/model v0.1.5/go.mod h1:XmGJA7jNPX9cEGc8TxTiEDitBuEVJOddNakdTZ/bH4U=
|
||||||
github.com/platform-engineering-labs/formae/pkg/plugin v0.1.8 h1:bECZEPHo6I6P8Qmpes5kDW/RFco3P7Z5xB3vvEuDGTo=
|
github.com/platform-engineering-labs/formae/pkg/plugin v0.1.14 h1:pzTjKuNtFZ/3I+f8F6U7tJtV7jMo64YdAY6v589ZPOA=
|
||||||
github.com/platform-engineering-labs/formae/pkg/plugin v0.1.8/go.mod h1:KzNzkc67phbeqs61ji+r8Y1WCD5QnDEpU7rfUHkmmuQ=
|
github.com/platform-engineering-labs/formae/pkg/plugin v0.1.14/go.mod h1:heydN5suh9RwjdCmZhttA9Yni0pRJswSx/tHfzwBERk=
|
||||||
github.com/platform-engineering-labs/formae/pkg/plugin-conformance-tests v0.1.9 h1:5geWOQgor+GR4nR3vEbU0DMxdOisyIpo18zgYwJ0ngE=
|
github.com/platform-engineering-labs/formae/pkg/plugin-conformance-tests v0.1.21 h1:H117sydtz0RG1eIMLTb+1QW6IpwxdJSK/lic/fiXKS4=
|
||||||
github.com/platform-engineering-labs/formae/pkg/plugin-conformance-tests v0.1.9/go.mod h1:NuoGHFF4WCI73scZ4odspPzZiKCWRvrKWTvy0rgoNec=
|
github.com/platform-engineering-labs/formae/pkg/plugin-conformance-tests v0.1.21/go.mod h1:GbypGizmbHw2Tl6TvLPuu3v7CqOmKZxIMx+NMgUvUkw=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU=
|
||||||
|
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||||
|
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||||
|
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||||
|
github.com/shirou/gopsutil/v4 v4.26.1 h1:TOkEyriIXk2HX9d4isZJtbjXbEjf5qyKPAzbzY0JWSo=
|
||||||
|
github.com/shirou/gopsutil/v4 v4.26.1/go.mod h1:medLI9/UNAb0dOI9Q3/7yWSqKkj00u+1tgY8nvv41pc=
|
||||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||||
github.com/theory/jsonpath v0.10.2 h1:i8GeMxnD6ftNWeSeaGb/Eb8XghGjsas1eDizaQNupuE=
|
github.com/theory/jsonpath v0.10.2 h1:i8GeMxnD6ftNWeSeaGb/Eb8XghGjsas1eDizaQNupuE=
|
||||||
@@ -44,51 +61,67 @@ github.com/theory/jsonpath v0.10.2/go.mod h1:ZOz+y6MxTEDcN/FOxf9AOgeHSoKHx2B+E0n
|
|||||||
github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||||
github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
|
github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
|
||||||
github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||||
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
|
|
||||||
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||||
|
github.com/tidwall/match v1.2.0 h1:0pt8FlkOwjN2fPt4bIl4BoNxb98gGHN2ObFEDkrfZnM=
|
||||||
|
github.com/tidwall/match v1.2.0/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||||
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||||
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
|
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
|
||||||
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||||
github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
|
github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
|
||||||
github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
|
github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
|
||||||
|
github.com/tklauser/go-sysconf v0.3.16 h1:frioLaCQSsF5Cy1jgRBrzr6t502KIIwQ0MArYICU0nA=
|
||||||
|
github.com/tklauser/go-sysconf v0.3.16/go.mod h1:/qNL9xxDhc7tx3HSRsLWNnuzbVfh3e7gh/BmM179nYI=
|
||||||
|
github.com/tklauser/numcpus v0.11.0 h1:nSTwhKH5e1dMNsCdVBukSZrURJRoHbSEQjdEbY+9RXw=
|
||||||
|
github.com/tklauser/numcpus v0.11.0/go.mod h1:z+LwcLq54uWZTX0u/bGobaV34u6V7KNlTZejzM6/3MQ=
|
||||||
github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8=
|
github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8=
|
||||||
github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok=
|
github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok=
|
||||||
github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g=
|
github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g=
|
||||||
github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=
|
github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=
|
||||||
|
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
|
||||||
|
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||||
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
|
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
|
||||||
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
||||||
go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48=
|
go.opentelemetry.io/contrib/instrumentation/host v0.65.0 h1:cR4LpCn/2xDNdW3saBLrGJW7vWmrYlHYIhfuklhrlUc=
|
||||||
go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8=
|
go.opentelemetry.io/contrib/instrumentation/host v0.65.0/go.mod h1:laAqufqDgLYaaewUBpolv8GePmhIVqIeHyudbmi9KYk=
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.39.0 h1:cEf8jF6WbuGQWUVcqgyWtTR0kOOAWY1DYZ+UhvdmQPw=
|
go.opentelemetry.io/contrib/instrumentation/runtime v0.65.0 h1:n8qdwrebNEHF/zHpueuZ4OacdJ8CdSaP7xef9WRZXTQ=
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.39.0/go.mod h1:k1lzV5n5U3HkGvTCJHraTAGJ7MqsgL1wrGwTj1Isfiw=
|
go.opentelemetry.io/contrib/instrumentation/runtime v0.65.0/go.mod h1:Z1pjGxUL3nJ/IbDDfL6rBD0Xbz7ZOViRqrIUg4l1CYE=
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.39.0 h1:nKP4Z2ejtHn3yShBb+2KawiXgpn8In5cT7aO2wXuOTE=
|
go.opentelemetry.io/otel v1.40.0 h1:oA5YeOcpRTXq6NN7frwmwFR0Cn3RhTVZvXsP4duvCms=
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.39.0/go.mod h1:NwjeBbNigsO4Aj9WgM0C+cKIrxsZUaRmZUO7A8I7u8o=
|
go.opentelemetry.io/otel v1.40.0/go.mod h1:IMb+uXZUKkMXdPddhwAHm6UfOwJyh4ct1ybIlV14J0g=
|
||||||
go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0=
|
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.40.0 h1:NOyNnS19BF2SUDApbOKbDtWZ0IK7b8FJ2uAGdIWOGb0=
|
||||||
go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs=
|
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.40.0/go.mod h1:VL6EgVikRLcJa9ftukrHu/ZkkhFBSo1lzvdBC9CF1ss=
|
||||||
go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18=
|
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.40.0 h1:9y5sHvAxWzft1WQ4BwqcvA+IFVUJ1Ya75mSAUnFEVwE=
|
||||||
go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE=
|
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.40.0/go.mod h1:eQqT90eR3X5Dbs1g9YSM30RavwLF725Ris5/XSXWvqE=
|
||||||
go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8=
|
go.opentelemetry.io/otel/metric v1.40.0 h1:rcZe317KPftE2rstWIBitCdVp89A2HqjkxR3c11+p9g=
|
||||||
go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew=
|
go.opentelemetry.io/otel/metric v1.40.0/go.mod h1:ib/crwQH7N3r5kfiBZQbwrTge743UDc7DTFVZrrXnqc=
|
||||||
go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI=
|
go.opentelemetry.io/otel/sdk v1.40.0 h1:KHW/jUzgo6wsPh9At46+h4upjtccTmuZCFAc9OJ71f8=
|
||||||
go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA=
|
go.opentelemetry.io/otel/sdk v1.40.0/go.mod h1:Ph7EFdYvxq72Y8Li9q8KebuYUr2KoeyHx0DRMKrYBUE=
|
||||||
|
go.opentelemetry.io/otel/sdk/metric v1.40.0 h1:mtmdVqgQkeRxHgRv4qhyJduP3fYJRMX4AtAlbuWdCYw=
|
||||||
|
go.opentelemetry.io/otel/sdk/metric v1.40.0/go.mod h1:4Z2bGMf0KSK3uRjlczMOeMhKU2rhUqdWNoKcYrtcBPg=
|
||||||
|
go.opentelemetry.io/otel/trace v1.40.0 h1:WA4etStDttCSYuhwvEa8OP8I5EWu24lkOzp+ZYblVjw=
|
||||||
|
go.opentelemetry.io/otel/trace v1.40.0/go.mod h1:zeAhriXecNGP/s2SEG3+Y8X9ujcJOTqQ5RgdEJcawiA=
|
||||||
go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A=
|
go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A=
|
||||||
go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4=
|
go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4=
|
||||||
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
|
golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
|
||||||
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
|
golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
|
||||||
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
|
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
|
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
|
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
|
||||||
|
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||||
|
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
|
||||||
|
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
|
||||||
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
||||||
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 h1:fCvbg86sFXwdrl5LgVcTEvNC+2txB5mgROGmRL5mrls=
|
google.golang.org/genproto/googleapis/api v0.0.0-20260226221140-a57be14db171 h1:tu/dtnW1o3wfaxCOjSLn5IRX4YDcJrtlpzYkhHhGaC4=
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto=
|
google.golang.org/genproto/googleapis/api v0.0.0-20260226221140-a57be14db171/go.mod h1:M5krXqk4GhBKvB596udGL3UyjL4I1+cTbK0orROM9ng=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 h1:gRkg/vSppuSQoDjxyiGfN4Upv/h/DQmIR10ZU8dh4Ww=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 h1:ggcbiqK8WWh6l1dnltU4BgWGIGo+EVYxCaAPih/zQXQ=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
|
||||||
google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM=
|
google.golang.org/grpc v1.79.1 h1:zGhSi45ODB9/p3VAawt9a+O/MULLl9dpizzNNpq7flY=
|
||||||
google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig=
|
google.golang.org/grpc v1.79.1/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
|
||||||
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
|
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||||
google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
|||||||
29
helper.go
29
helper.go
@@ -5,7 +5,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log/slog"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
@@ -37,18 +37,13 @@ func getCredentials() (username, token string, err error) {
|
|||||||
return username, token, nil
|
return username, token, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseLXCProperties(data json.RawMessage) (*LXCProperties, error) {
|
func setupLogging() {
|
||||||
var props LXCProperties
|
programLevel := new(slog.LevelVar)
|
||||||
if err := json.Unmarshal(data, &props); err != nil {
|
env := os.Getenv("PROXMOX_LOG_LEVEL")
|
||||||
return nil, fmt.Errorf("invalid file properties: %w", err)
|
programLevel.UnmarshalText([]byte(env))
|
||||||
}
|
logger := slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: programLevel}))
|
||||||
if props.VMID == "" {
|
slog.Info("Set log level", "level", programLevel)
|
||||||
return nil, fmt.Errorf("vmid missing")
|
slog.SetDefault(logger)
|
||||||
}
|
|
||||||
if props.Hostname == "" {
|
|
||||||
return nil, fmt.Errorf("name missing")
|
|
||||||
}
|
|
||||||
return &props, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func createAuthorizationString(username, token string) string {
|
func createAuthorizationString(username, token string) string {
|
||||||
@@ -64,24 +59,24 @@ func authenticatedRequest(method, url, authorization string, urlparams url.Value
|
|||||||
|
|
||||||
request, err := http.NewRequest(method, url, body)
|
request, err := http.NewRequest(method, url, body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("Error: ", err)
|
slog.Error("Error creating request", "err", err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
request.Header.Set("Authorization", authorization)
|
request.Header.Set("Authorization", authorization)
|
||||||
|
|
||||||
resp, err := client.Do(request)
|
resp, err := client.Do(request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("Error: ", err)
|
slog.Error("Error executing request", "err", err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
data, err := io.ReadAll(resp.Body)
|
data, err := io.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("Error: ", err)
|
slog.Error("Error reading response", "err", err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Println("URL: ", method, "Status Code:", resp.Status, "Body: ", string(data))
|
slog.Debug("Executed Request", "url", method, "params", urlparams, "status code", resp.Status, "body", string(data))
|
||||||
|
|
||||||
return data, nil
|
return data, nil
|
||||||
}
|
}
|
||||||
|
|||||||
512
lxc.go
Normal file
512
lxc.go
Normal file
@@ -0,0 +1,512 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/platform-engineering-labs/formae/pkg/plugin/resource"
|
||||||
|
)
|
||||||
|
|
||||||
|
const MAX_NETWORK_COUNT = 10
|
||||||
|
|
||||||
|
func parseLXCProperties(data json.RawMessage) (*LXCProperties, error) {
|
||||||
|
var props LXCProperties
|
||||||
|
if err := json.Unmarshal(data, &props); err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid file properties: %w", err)
|
||||||
|
}
|
||||||
|
if props.VMID == "" {
|
||||||
|
return nil, fmt.Errorf("vmid missing")
|
||||||
|
}
|
||||||
|
if props.Hostname == "" {
|
||||||
|
return nil, fmt.Errorf("name missing")
|
||||||
|
}
|
||||||
|
return &props, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Plugin) CreateLXC(ctx context.Context, req *resource.CreateRequest) (*resource.CreateResult, error) {
|
||||||
|
props, err := parseLXCProperties(req.Properties)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error(err.Error())
|
||||||
|
return &resource.CreateResult{
|
||||||
|
ProgressResult: &resource.ProgressResult{
|
||||||
|
Operation: resource.OperationCreate,
|
||||||
|
OperationStatus: resource.OperationStatusFailure,
|
||||||
|
ErrorCode: resource.OperationErrorCodeInvalidRequest,
|
||||||
|
StatusMessage: err.Error(),
|
||||||
|
},
|
||||||
|
}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
config, err := parseTargetConfig(req.TargetConfig)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error(err.Error())
|
||||||
|
return &resource.CreateResult{
|
||||||
|
ProgressResult: &resource.ProgressResult{
|
||||||
|
Operation: resource.OperationCreate,
|
||||||
|
OperationStatus: resource.OperationStatusFailure,
|
||||||
|
ErrorCode: resource.OperationErrorCodeInternalFailure,
|
||||||
|
StatusMessage: err.Error(),
|
||||||
|
},
|
||||||
|
}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
username, token, err := getCredentials()
|
||||||
|
if err != nil {
|
||||||
|
slog.Error(err.Error())
|
||||||
|
return &resource.CreateResult{
|
||||||
|
ProgressResult: &resource.ProgressResult{
|
||||||
|
Operation: resource.OperationCreate,
|
||||||
|
OperationStatus: resource.OperationStatusFailure,
|
||||||
|
ErrorCode: resource.OperationErrorCodeInternalFailure,
|
||||||
|
StatusMessage: err.Error(),
|
||||||
|
},
|
||||||
|
}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
urlparams := url.Values{
|
||||||
|
"vmid": {props.VMID},
|
||||||
|
"ostemplate": {props.OSTemplate},
|
||||||
|
"password": {props.Password},
|
||||||
|
"hostname": {props.Hostname},
|
||||||
|
"cores": {strconv.Itoa(props.Cores)},
|
||||||
|
"memory": {strconv.Itoa(props.Memory)},
|
||||||
|
"ssh-public-keys": {strings.Join(props.SSHKeys, "\n")},
|
||||||
|
}
|
||||||
|
if props.Description != "" {
|
||||||
|
urlparams.Add("description", props.Description)
|
||||||
|
}
|
||||||
|
if props.OnBoot != 0 {
|
||||||
|
urlparams.Add("onboot", strconv.Itoa(props.OnBoot))
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range min(MAX_NETWORK_COUNT, len(props.Networks)) {
|
||||||
|
urlparams.Add(fmt.Sprintf("net%d", i), props.Networks[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := authenticatedRequest(http.MethodPost, config.URL+"/api2/json/nodes/"+config.NODE+"/lxc", createAuthorizationString(username, token), urlparams)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
slog.Error(err.Error())
|
||||||
|
return &resource.CreateResult{
|
||||||
|
ProgressResult: &resource.ProgressResult{
|
||||||
|
Operation: resource.OperationCreate,
|
||||||
|
OperationStatus: resource.OperationStatusFailure,
|
||||||
|
ErrorCode: resource.OperationErrorCodeInternalFailure,
|
||||||
|
StatusMessage: err.Error(),
|
||||||
|
},
|
||||||
|
}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var taskData ProxmoxDataResponse
|
||||||
|
|
||||||
|
err = json.Unmarshal(data, &taskData)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
slog.Error(err.Error())
|
||||||
|
return &resource.CreateResult{
|
||||||
|
ProgressResult: &resource.ProgressResult{
|
||||||
|
Operation: resource.OperationCreate,
|
||||||
|
OperationStatus: resource.OperationStatusFailure,
|
||||||
|
ErrorCode: resource.OperationErrorCodeInternalFailure,
|
||||||
|
StatusMessage: err.Error(),
|
||||||
|
},
|
||||||
|
}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &resource.CreateResult{
|
||||||
|
ProgressResult: &resource.ProgressResult{
|
||||||
|
Operation: resource.OperationCreate,
|
||||||
|
OperationStatus: resource.OperationStatusSuccess,
|
||||||
|
RequestID: taskData.Data,
|
||||||
|
NativeID: props.VMID,
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Plugin) ReadLXC(ctx context.Context, req *resource.ReadRequest) (*resource.ReadResult, error) {
|
||||||
|
username, token, err := getCredentials()
|
||||||
|
if err != nil {
|
||||||
|
slog.Error(err.Error())
|
||||||
|
return &resource.ReadResult{
|
||||||
|
ErrorCode: resource.OperationErrorCodeInvalidRequest,
|
||||||
|
}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
config, err := parseTargetConfig(req.TargetConfig)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error(err.Error())
|
||||||
|
return &resource.ReadResult{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := authenticatedRequest(http.MethodGet, config.URL+"/api2/json/nodes/"+config.NODE+"/lxc/"+req.NativeID+"/config", createAuthorizationString(username, token), nil)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error(err.Error())
|
||||||
|
return &resource.ReadResult{
|
||||||
|
ErrorCode: resource.OperationErrorCodeNetworkFailure,
|
||||||
|
}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var props StatusLXCConfigResponse
|
||||||
|
|
||||||
|
err = json.Unmarshal(data, &props)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("Error unmarshalling json", "data", data, "err", err.Error())
|
||||||
|
return &resource.ReadResult{
|
||||||
|
ErrorCode: resource.OperationErrorCodeInvalidRequest,
|
||||||
|
}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
lxcdata := props.Data
|
||||||
|
|
||||||
|
networks := []string{}
|
||||||
|
|
||||||
|
if len(lxcdata.Net0) > 0 {
|
||||||
|
networks = append(networks, lxcdata.Net0)
|
||||||
|
}
|
||||||
|
if len(lxcdata.Net1) > 0 {
|
||||||
|
networks = append(networks, lxcdata.Net1)
|
||||||
|
}
|
||||||
|
if len(lxcdata.Net2) > 0 {
|
||||||
|
networks = append(networks, lxcdata.Net2)
|
||||||
|
}
|
||||||
|
if len(lxcdata.Net3) > 0 {
|
||||||
|
networks = append(networks, lxcdata.Net3)
|
||||||
|
}
|
||||||
|
if len(lxcdata.Net4) > 0 {
|
||||||
|
networks = append(networks, lxcdata.Net4)
|
||||||
|
}
|
||||||
|
if len(lxcdata.Net5) > 0 {
|
||||||
|
networks = append(networks, lxcdata.Net5)
|
||||||
|
}
|
||||||
|
if len(lxcdata.Net6) > 0 {
|
||||||
|
networks = append(networks, lxcdata.Net6)
|
||||||
|
}
|
||||||
|
if len(lxcdata.Net7) > 0 {
|
||||||
|
networks = append(networks, lxcdata.Net7)
|
||||||
|
}
|
||||||
|
if len(lxcdata.Net8) > 0 {
|
||||||
|
networks = append(networks, lxcdata.Net8)
|
||||||
|
}
|
||||||
|
if len(lxcdata.Net9) > 0 {
|
||||||
|
networks = append(networks, lxcdata.Net9)
|
||||||
|
}
|
||||||
|
|
||||||
|
properties := LXCProperties{
|
||||||
|
VMID: req.NativeID,
|
||||||
|
Hostname: lxcdata.Hostname,
|
||||||
|
Description: lxcdata.Description,
|
||||||
|
Cores: lxcdata.Cores,
|
||||||
|
Memory: lxcdata.Memory,
|
||||||
|
OnBoot: lxcdata.OnBoot,
|
||||||
|
Networks: networks,
|
||||||
|
}
|
||||||
|
|
||||||
|
propsJSON, err := json.Marshal(properties)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error(err.Error())
|
||||||
|
return &resource.ReadResult{
|
||||||
|
ErrorCode: resource.OperationErrorCodeInternalFailure,
|
||||||
|
}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &resource.ReadResult{
|
||||||
|
ResourceType: req.ResourceType,
|
||||||
|
Properties: string(propsJSON),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Plugin) UpdateLXC(ctx context.Context, req *resource.UpdateRequest) (*resource.UpdateResult, error) {
|
||||||
|
prior, err := parseLXCProperties(req.PriorProperties)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error(err.Error())
|
||||||
|
return &resource.UpdateResult{
|
||||||
|
ProgressResult: &resource.ProgressResult{
|
||||||
|
Operation: resource.OperationUpdate,
|
||||||
|
OperationStatus: resource.OperationStatusFailure,
|
||||||
|
ErrorCode: resource.OperationErrorCodeInvalidRequest,
|
||||||
|
StatusMessage: err.Error(),
|
||||||
|
},
|
||||||
|
}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
desir, err := parseLXCProperties(req.DesiredProperties)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error(err.Error())
|
||||||
|
return &resource.UpdateResult{
|
||||||
|
ProgressResult: &resource.ProgressResult{
|
||||||
|
Operation: resource.OperationUpdate,
|
||||||
|
OperationStatus: resource.OperationStatusFailure,
|
||||||
|
ErrorCode: resource.OperationErrorCodeInvalidRequest,
|
||||||
|
StatusMessage: err.Error(),
|
||||||
|
},
|
||||||
|
}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if prior == nil {
|
||||||
|
p.Create(ctx, &resource.CreateRequest{
|
||||||
|
ResourceType: req.ResourceType,
|
||||||
|
Properties: req.DesiredProperties,
|
||||||
|
TargetConfig: req.TargetConfig,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if prior.VMID != desir.VMID {
|
||||||
|
return &resource.UpdateResult{
|
||||||
|
ProgressResult: &resource.ProgressResult{
|
||||||
|
Operation: resource.OperationUpdate,
|
||||||
|
OperationStatus: resource.OperationStatusFailure,
|
||||||
|
ErrorCode: resource.OperationErrorCodeInvalidRequest,
|
||||||
|
StatusMessage: "can't change vmid",
|
||||||
|
},
|
||||||
|
}, fmt.Errorf("can't change vmid")
|
||||||
|
}
|
||||||
|
|
||||||
|
config, err := parseTargetConfig(req.TargetConfig)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error(err.Error())
|
||||||
|
return &resource.UpdateResult{
|
||||||
|
ProgressResult: &resource.ProgressResult{
|
||||||
|
Operation: resource.OperationCreate,
|
||||||
|
OperationStatus: resource.OperationStatusFailure,
|
||||||
|
ErrorCode: resource.OperationErrorCodeInternalFailure,
|
||||||
|
StatusMessage: err.Error(),
|
||||||
|
},
|
||||||
|
}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
username, token, err := getCredentials()
|
||||||
|
if err != nil {
|
||||||
|
slog.Error(err.Error())
|
||||||
|
return &resource.UpdateResult{
|
||||||
|
ProgressResult: &resource.ProgressResult{
|
||||||
|
Operation: resource.OperationUpdate,
|
||||||
|
OperationStatus: resource.OperationStatusFailure,
|
||||||
|
ErrorCode: resource.OperationErrorCodeAccessDenied,
|
||||||
|
StatusMessage: err.Error(),
|
||||||
|
},
|
||||||
|
}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
urlparams := url.Values{
|
||||||
|
"vmid": {desir.VMID},
|
||||||
|
"hostname": {desir.Hostname},
|
||||||
|
"cores": {strconv.Itoa(desir.Cores)},
|
||||||
|
"memory": {strconv.Itoa(desir.Memory)},
|
||||||
|
"description": {desir.Description},
|
||||||
|
}
|
||||||
|
if prior.OnBoot != desir.OnBoot {
|
||||||
|
urlparams.Add("onboot", strconv.Itoa(desir.OnBoot))
|
||||||
|
}
|
||||||
|
|
||||||
|
toDelete := []string{}
|
||||||
|
for i := range min(MAX_NETWORK_COUNT, max(len(desir.Networks), len(prior.Networks))) {
|
||||||
|
if i < len(desir.Networks) {
|
||||||
|
urlparams.Add(fmt.Sprintf("net%d", i), desir.Networks[i])
|
||||||
|
} else {
|
||||||
|
toDelete = append(toDelete, fmt.Sprintf("net%d", i))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
urlparams.Add("delete", strings.Join(toDelete, ","))
|
||||||
|
|
||||||
|
_, err = authenticatedRequest("PUT", config.URL+"/api2/json/nodes/"+config.NODE+"/lxc/"+desir.VMID+"/config", createAuthorizationString(username, token), urlparams)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
slog.Error(err.Error())
|
||||||
|
return &resource.UpdateResult{
|
||||||
|
ProgressResult: &resource.ProgressResult{
|
||||||
|
Operation: resource.OperationCreate,
|
||||||
|
OperationStatus: resource.OperationStatusFailure,
|
||||||
|
ErrorCode: resource.OperationErrorCodeInternalFailure,
|
||||||
|
StatusMessage: err.Error(),
|
||||||
|
},
|
||||||
|
}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := p.Read(ctx, &resource.ReadRequest{
|
||||||
|
NativeID: req.NativeID,
|
||||||
|
ResourceType: req.ResourceType,
|
||||||
|
TargetConfig: req.TargetConfig,
|
||||||
|
})
|
||||||
|
|
||||||
|
return &resource.UpdateResult{
|
||||||
|
ProgressResult: &resource.ProgressResult{
|
||||||
|
Operation: resource.OperationUpdate,
|
||||||
|
OperationStatus: resource.OperationStatusSuccess,
|
||||||
|
NativeID: req.NativeID,
|
||||||
|
ResourceProperties: json.RawMessage(result.Properties),
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Plugin) DeleteLXC(ctx context.Context, req *resource.DeleteRequest) (*resource.DeleteResult, error) {
|
||||||
|
config, err := parseTargetConfig(req.TargetConfig)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error(err.Error())
|
||||||
|
return &resource.DeleteResult{
|
||||||
|
ProgressResult: &resource.ProgressResult{
|
||||||
|
Operation: resource.OperationCreate,
|
||||||
|
OperationStatus: resource.OperationStatusFailure,
|
||||||
|
ErrorCode: resource.OperationErrorCodeInternalFailure,
|
||||||
|
StatusMessage: err.Error(),
|
||||||
|
},
|
||||||
|
}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
username, token, err := getCredentials()
|
||||||
|
if err != nil {
|
||||||
|
slog.Error(err.Error())
|
||||||
|
return &resource.DeleteResult{
|
||||||
|
ProgressResult: &resource.ProgressResult{
|
||||||
|
Operation: resource.OperationCreate,
|
||||||
|
OperationStatus: resource.OperationStatusFailure,
|
||||||
|
ErrorCode: resource.OperationErrorCodeInternalFailure,
|
||||||
|
StatusMessage: err.Error(),
|
||||||
|
},
|
||||||
|
}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = authenticatedRequest(http.MethodDelete, config.URL+"/api2/json/nodes/"+config.NODE+"/lxc/"+req.NativeID+"?force=1&purge=1", createAuthorizationString(username, token), nil)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
slog.Error(err.Error())
|
||||||
|
return &resource.DeleteResult{
|
||||||
|
ProgressResult: &resource.ProgressResult{
|
||||||
|
Operation: resource.OperationCreate,
|
||||||
|
OperationStatus: resource.OperationStatusFailure,
|
||||||
|
ErrorCode: resource.OperationErrorCodeInternalFailure,
|
||||||
|
StatusMessage: err.Error(),
|
||||||
|
},
|
||||||
|
}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &resource.DeleteResult{
|
||||||
|
ProgressResult: &resource.ProgressResult{
|
||||||
|
Operation: resource.OperationCreate,
|
||||||
|
OperationStatus: resource.OperationStatusSuccess,
|
||||||
|
NativeID: req.NativeID,
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
type RequestStatusProxmoxResponse struct {
|
||||||
|
PId int `json:"pid"`
|
||||||
|
UpId string `json:"upid"`
|
||||||
|
Node string `json:"node"`
|
||||||
|
PStart int `json:"pstart"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
Id string `json:"id"`
|
||||||
|
StartTime int `json:"starttime"`
|
||||||
|
ExitStatus string `json:"exitstatus"`
|
||||||
|
User string `json:"user"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Plugin) StatusLXC(ctx context.Context, req *resource.StatusRequest) (*resource.StatusResult, error) {
|
||||||
|
config, err := parseTargetConfig(req.TargetConfig)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error(err.Error())
|
||||||
|
return &resource.StatusResult{
|
||||||
|
ProgressResult: &resource.ProgressResult{
|
||||||
|
Operation: resource.OperationCheckStatus,
|
||||||
|
OperationStatus: resource.OperationStatusFailure,
|
||||||
|
ErrorCode: resource.OperationErrorCodeInternalFailure,
|
||||||
|
StatusMessage: err.Error(),
|
||||||
|
},
|
||||||
|
}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
username, token, err := getCredentials()
|
||||||
|
if err != nil {
|
||||||
|
slog.Error(err.Error())
|
||||||
|
return &resource.StatusResult{
|
||||||
|
ProgressResult: &resource.ProgressResult{
|
||||||
|
Operation: resource.OperationCheckStatus,
|
||||||
|
OperationStatus: resource.OperationStatusFailure,
|
||||||
|
ErrorCode: resource.OperationErrorCodeInternalFailure,
|
||||||
|
StatusMessage: err.Error(),
|
||||||
|
},
|
||||||
|
}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var proxmoxResponse RequestStatusProxmoxResponse
|
||||||
|
|
||||||
|
data, err := authenticatedRequest(http.MethodDelete, config.URL+"/api2/json/nodes/"+config.NODE+"/tasks/"+req.RequestID+"/status", createAuthorizationString(username, token), nil)
|
||||||
|
|
||||||
|
err = json.Unmarshal(data, &proxmoxResponse)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error(err.Error())
|
||||||
|
return &resource.StatusResult{
|
||||||
|
ProgressResult: &resource.ProgressResult{
|
||||||
|
Operation: resource.OperationCheckStatus,
|
||||||
|
OperationStatus: resource.OperationStatusFailure,
|
||||||
|
ErrorCode: resource.OperationErrorCodeInternalFailure,
|
||||||
|
StatusMessage: err.Error(),
|
||||||
|
},
|
||||||
|
}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var status resource.OperationStatus
|
||||||
|
|
||||||
|
switch proxmoxResponse.Status {
|
||||||
|
case "running":
|
||||||
|
status = resource.OperationStatusInProgress
|
||||||
|
case "stopped":
|
||||||
|
status = resource.OperationStatusSuccess
|
||||||
|
}
|
||||||
|
|
||||||
|
return &resource.StatusResult{
|
||||||
|
ProgressResult: &resource.ProgressResult{
|
||||||
|
Operation: resource.OperationCheckStatus,
|
||||||
|
OperationStatus: status,
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Plugin) ListLXC(ctx context.Context, req *resource.ListRequest) (*resource.ListResult, error) {
|
||||||
|
|
||||||
|
username, token, err := getCredentials()
|
||||||
|
if err != nil {
|
||||||
|
slog.Error(err.Error())
|
||||||
|
return &resource.ListResult{
|
||||||
|
NativeIDs: []string{},
|
||||||
|
}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
config, err := parseTargetConfig(req.TargetConfig)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error(err.Error())
|
||||||
|
return &resource.ListResult{
|
||||||
|
NativeIDs: []string{},
|
||||||
|
}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var props StatusGeneralResponse
|
||||||
|
|
||||||
|
data, err := authenticatedRequest(http.MethodGet, config.URL+"/api2/json/nodes/"+config.NODE+"/lxc", createAuthorizationString(username, token), nil)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error(err.Error())
|
||||||
|
return &resource.ListResult{
|
||||||
|
NativeIDs: []string{},
|
||||||
|
}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
json.Unmarshal(data, &props)
|
||||||
|
|
||||||
|
nativeIds := make([]string, 0, len(props.Data))
|
||||||
|
|
||||||
|
for _, value := range props.Data {
|
||||||
|
nativeIds = append(nativeIds, strconv.Itoa(value.VMID))
|
||||||
|
}
|
||||||
|
|
||||||
|
return &resource.ListResult{
|
||||||
|
NativeIDs: nativeIds,
|
||||||
|
NextPageToken: nil,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
376
proxmox.go
376
proxmox.go
@@ -6,13 +6,7 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/platform-engineering-labs/formae/pkg/plugin"
|
"github.com/platform-engineering-labs/formae/pkg/plugin"
|
||||||
"github.com/platform-engineering-labs/formae/pkg/plugin/resource"
|
"github.com/platform-engineering-labs/formae/pkg/plugin/resource"
|
||||||
@@ -20,7 +14,6 @@ import (
|
|||||||
|
|
||||||
// https://pve.proxmox.com/pve-docs/api-viewer/
|
// https://pve.proxmox.com/pve-docs/api-viewer/
|
||||||
|
|
||||||
// ErrNotImplemented is returned by stub methods that need implementation.
|
|
||||||
var ErrNotImplemented = errors.New("not implemented")
|
var ErrNotImplemented = errors.New("not implemented")
|
||||||
|
|
||||||
// Plugin implements the Formae ResourcePlugin interface.
|
// Plugin implements the Formae ResourcePlugin interface.
|
||||||
@@ -35,28 +28,14 @@ var _ plugin.ResourcePlugin = &Plugin{}
|
|||||||
// Configuration Methods
|
// Configuration Methods
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
|
||||||
// RateLimit returns the rate limiting configuration for this plugin.
|
|
||||||
// Adjust MaxRequestsPerSecondForNamespace based on your provider's API limits.
|
|
||||||
func (p *Plugin) RateLimit() plugin.RateLimitConfig {
|
func (p *Plugin) RateLimit() plugin.RateLimitConfig {
|
||||||
return plugin.RateLimitConfig{
|
return plugin.RateLimitConfig{
|
||||||
Scope: plugin.RateLimitScopeNamespace,
|
Scope: plugin.RateLimitScopeNamespace,
|
||||||
MaxRequestsPerSecondForNamespace: 10, // TODO: Adjust based on provider limits
|
MaxRequestsPerSecondForNamespace: 10,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// DiscoveryFilters returns filters to exclude certain resources from discovery.
|
|
||||||
// Resources matching ALL conditions in a filter are excluded.
|
|
||||||
// Return nil if you want to discover all resources.
|
|
||||||
func (p *Plugin) DiscoveryFilters() []plugin.MatchFilter {
|
func (p *Plugin) DiscoveryFilters() []plugin.MatchFilter {
|
||||||
// Example: exclude resources with a specific tag
|
|
||||||
// return []plugin.MatchFilter{
|
|
||||||
// {
|
|
||||||
// ResourceTypes: []string{"PROXMOX::Service::Resource"},
|
|
||||||
// Conditions: []plugin.FilterCondition{
|
|
||||||
// {PropertyPath: "$.Tags[?(@.Key=='skip-discovery')].Value", PropertyValue: "true"},
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
// }
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,11 +43,7 @@ func (p *Plugin) DiscoveryFilters() []plugin.MatchFilter {
|
|||||||
// from discovered resources.
|
// from discovered resources.
|
||||||
func (p *Plugin) LabelConfig() plugin.LabelConfig {
|
func (p *Plugin) LabelConfig() plugin.LabelConfig {
|
||||||
return plugin.LabelConfig{
|
return plugin.LabelConfig{
|
||||||
// Default JSONPath query to extract label from resources
|
|
||||||
// Example for tagged resources: $.Tags[?(@.Key=='Name')].Value
|
|
||||||
DefaultQuery: "$.hostname",
|
DefaultQuery: "$.hostname",
|
||||||
|
|
||||||
// Override for specific resource types
|
|
||||||
ResourceOverrides: map[string]string{
|
ResourceOverrides: map[string]string{
|
||||||
// "PROXMOX::Service::SpecialResource": "$.DisplayName",
|
// "PROXMOX::Service::SpecialResource": "$.DisplayName",
|
||||||
},
|
},
|
||||||
@@ -79,358 +54,33 @@ func (p *Plugin) LabelConfig() plugin.LabelConfig {
|
|||||||
// CRUD Operations
|
// CRUD Operations
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
|
||||||
// Create provisions a new resource.
|
|
||||||
func (p *Plugin) Create(ctx context.Context, req *resource.CreateRequest) (*resource.CreateResult, error) {
|
func (p *Plugin) Create(ctx context.Context, req *resource.CreateRequest) (*resource.CreateResult, error) {
|
||||||
|
setupLogging()
|
||||||
props, err := parseLXCProperties(req.Properties)
|
return p.CreateLXC(ctx, req)
|
||||||
if err != nil {
|
|
||||||
log.Println(err.Error())
|
|
||||||
return &resource.CreateResult{
|
|
||||||
ProgressResult: &resource.ProgressResult{
|
|
||||||
Operation: resource.OperationCreate,
|
|
||||||
OperationStatus: resource.OperationStatusFailure,
|
|
||||||
ErrorCode: resource.OperationErrorCodeInvalidRequest,
|
|
||||||
StatusMessage: err.Error(),
|
|
||||||
},
|
|
||||||
}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
config, err := parseTargetConfig(req.TargetConfig)
|
|
||||||
if err != nil {
|
|
||||||
log.Println(err.Error())
|
|
||||||
return &resource.CreateResult{
|
|
||||||
ProgressResult: &resource.ProgressResult{
|
|
||||||
Operation: resource.OperationCreate,
|
|
||||||
OperationStatus: resource.OperationStatusFailure,
|
|
||||||
ErrorCode: resource.OperationErrorCodeInternalFailure,
|
|
||||||
StatusMessage: err.Error(),
|
|
||||||
},
|
|
||||||
}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
username, token, err := getCredentials()
|
|
||||||
if err != nil {
|
|
||||||
log.Println(err.Error())
|
|
||||||
return &resource.CreateResult{
|
|
||||||
ProgressResult: &resource.ProgressResult{
|
|
||||||
Operation: resource.OperationCreate,
|
|
||||||
OperationStatus: resource.OperationStatusFailure,
|
|
||||||
ErrorCode: resource.OperationErrorCodeInternalFailure,
|
|
||||||
StatusMessage: err.Error(),
|
|
||||||
},
|
|
||||||
}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
urlparams := url.Values{
|
|
||||||
"vmid": {props.VMID},
|
|
||||||
"ostemplate": {props.OSTemplate},
|
|
||||||
"hostname": {props.Hostname},
|
|
||||||
"cores": {strconv.Itoa(props.Cores)},
|
|
||||||
"memory": {strconv.Itoa(props.Memory)},
|
|
||||||
}
|
|
||||||
if props.Description != "" {
|
|
||||||
urlparams.Add("description", props.Description)
|
|
||||||
}
|
|
||||||
if props.OnBoot != 0 {
|
|
||||||
urlparams.Add("onboot", strconv.Itoa(props.OnBoot))
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = authenticatedRequest(http.MethodPost, config.URL+"/api2/json/nodes/"+config.NODE+"/lxc", createAuthorizationString(username, token), urlparams)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return &resource.CreateResult{
|
|
||||||
ProgressResult: &resource.ProgressResult{
|
|
||||||
Operation: resource.OperationCreate,
|
|
||||||
OperationStatus: resource.OperationStatusFailure,
|
|
||||||
ErrorCode: resource.OperationErrorCodeInternalFailure,
|
|
||||||
StatusMessage: err.Error(),
|
|
||||||
},
|
|
||||||
}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &resource.CreateResult{
|
|
||||||
ProgressResult: &resource.ProgressResult{
|
|
||||||
Operation: resource.OperationCreate,
|
|
||||||
OperationStatus: resource.OperationStatusSuccess,
|
|
||||||
NativeID: props.VMID,
|
|
||||||
},
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Plugin) Read(ctx context.Context, req *resource.ReadRequest) (*resource.ReadResult, error) {
|
func (p *Plugin) Read(ctx context.Context, req *resource.ReadRequest) (*resource.ReadResult, error) {
|
||||||
username, token, err := getCredentials()
|
setupLogging()
|
||||||
if err != nil {
|
return p.ReadLXC(ctx, req)
|
||||||
return &resource.ReadResult{
|
|
||||||
ErrorCode: resource.OperationErrorCodeInvalidRequest,
|
|
||||||
}, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
config, err := parseTargetConfig(req.TargetConfig)
|
|
||||||
if err != nil {
|
|
||||||
return &resource.ReadResult{}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
data, err := authenticatedRequest(http.MethodGet, config.URL+"/api2/json/nodes/"+config.NODE+"/lxc/"+req.NativeID+"/config", createAuthorizationString(username, token), nil)
|
|
||||||
if err != nil {
|
|
||||||
return &resource.ReadResult{
|
|
||||||
ErrorCode: resource.OperationErrorCodeNetworkFailure,
|
|
||||||
}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var props StatusLXCConfigResponse
|
|
||||||
|
|
||||||
err = json.Unmarshal(data, &props)
|
|
||||||
if err != nil {
|
|
||||||
log.Println("Error unmarshaling json: ", data)
|
|
||||||
return &resource.ReadResult{
|
|
||||||
ErrorCode: resource.OperationErrorCodeInvalidRequest,
|
|
||||||
}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
lxcdata := props.Data
|
|
||||||
|
|
||||||
properties := LXCProperties{
|
|
||||||
VMID: req.NativeID,
|
|
||||||
Hostname: lxcdata.Hostname,
|
|
||||||
Description: lxcdata.Description,
|
|
||||||
Cores: lxcdata.Cores,
|
|
||||||
Memory: lxcdata.Memory,
|
|
||||||
OnBoot: lxcdata.OnBoot,
|
|
||||||
}
|
|
||||||
|
|
||||||
propsJSON, err := json.Marshal(properties)
|
|
||||||
if err != nil {
|
|
||||||
return &resource.ReadResult{
|
|
||||||
ErrorCode: resource.OperationErrorCodeInternalFailure,
|
|
||||||
}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &resource.ReadResult{
|
|
||||||
ResourceType: req.ResourceType,
|
|
||||||
Properties: string(propsJSON),
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update modifies an existing resource.
|
|
||||||
func (p *Plugin) Update(ctx context.Context, req *resource.UpdateRequest) (*resource.UpdateResult, error) {
|
func (p *Plugin) Update(ctx context.Context, req *resource.UpdateRequest) (*resource.UpdateResult, error) {
|
||||||
|
setupLogging()
|
||||||
prior, err := parseLXCProperties(req.PriorProperties)
|
return p.UpdateLXC(ctx, req)
|
||||||
if err != nil {
|
|
||||||
return &resource.UpdateResult{
|
|
||||||
ProgressResult: &resource.ProgressResult{
|
|
||||||
Operation: resource.OperationUpdate,
|
|
||||||
OperationStatus: resource.OperationStatusFailure,
|
|
||||||
ErrorCode: resource.OperationErrorCodeInvalidRequest,
|
|
||||||
StatusMessage: err.Error(),
|
|
||||||
},
|
|
||||||
}, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
desir, err := parseLXCProperties(req.DesiredProperties)
|
|
||||||
if err != nil {
|
|
||||||
return &resource.UpdateResult{
|
|
||||||
ProgressResult: &resource.ProgressResult{
|
|
||||||
Operation: resource.OperationUpdate,
|
|
||||||
OperationStatus: resource.OperationStatusFailure,
|
|
||||||
ErrorCode: resource.OperationErrorCodeInvalidRequest,
|
|
||||||
StatusMessage: err.Error(),
|
|
||||||
},
|
|
||||||
}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if prior == nil {
|
|
||||||
p.Create(ctx, &resource.CreateRequest{
|
|
||||||
ResourceType: req.ResourceType,
|
|
||||||
Properties: req.DesiredProperties,
|
|
||||||
TargetConfig: req.TargetConfig,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if prior.VMID != desir.VMID {
|
|
||||||
return &resource.UpdateResult{
|
|
||||||
ProgressResult: &resource.ProgressResult{
|
|
||||||
Operation: resource.OperationUpdate,
|
|
||||||
OperationStatus: resource.OperationStatusFailure,
|
|
||||||
ErrorCode: resource.OperationErrorCodeInvalidRequest,
|
|
||||||
StatusMessage: "can't change vmid",
|
|
||||||
},
|
|
||||||
}, fmt.Errorf("can't change vmid")
|
|
||||||
}
|
|
||||||
|
|
||||||
config, err := parseTargetConfig(req.TargetConfig)
|
|
||||||
if err != nil {
|
|
||||||
log.Println(err.Error())
|
|
||||||
return &resource.UpdateResult{
|
|
||||||
ProgressResult: &resource.ProgressResult{
|
|
||||||
Operation: resource.OperationCreate,
|
|
||||||
OperationStatus: resource.OperationStatusFailure,
|
|
||||||
ErrorCode: resource.OperationErrorCodeInternalFailure,
|
|
||||||
StatusMessage: err.Error(),
|
|
||||||
},
|
|
||||||
}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
username, token, err := getCredentials()
|
|
||||||
if err != nil {
|
|
||||||
return &resource.UpdateResult{
|
|
||||||
ProgressResult: &resource.ProgressResult{
|
|
||||||
Operation: resource.OperationUpdate,
|
|
||||||
OperationStatus: resource.OperationStatusFailure,
|
|
||||||
ErrorCode: resource.OperationErrorCodeAccessDenied,
|
|
||||||
StatusMessage: err.Error(),
|
|
||||||
},
|
|
||||||
}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
urlparams := url.Values{
|
|
||||||
"vmid": {desir.VMID},
|
|
||||||
"hostname": {desir.Hostname},
|
|
||||||
"cores": {strconv.Itoa(desir.Cores)},
|
|
||||||
"memory": {strconv.Itoa(desir.Memory)},
|
|
||||||
"description": {desir.Description},
|
|
||||||
}
|
|
||||||
if prior.OnBoot != desir.OnBoot {
|
|
||||||
urlparams.Add("onboot", strconv.Itoa(desir.OnBoot))
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = authenticatedRequest("PUT", config.URL+"/api2/json/nodes/"+config.NODE+"/lxc/"+desir.VMID+"/config", createAuthorizationString(username, token), urlparams)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return &resource.UpdateResult{
|
|
||||||
ProgressResult: &resource.ProgressResult{
|
|
||||||
Operation: resource.OperationCreate,
|
|
||||||
OperationStatus: resource.OperationStatusFailure,
|
|
||||||
ErrorCode: resource.OperationErrorCodeInternalFailure,
|
|
||||||
StatusMessage: err.Error(),
|
|
||||||
},
|
|
||||||
}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
result, err := p.Read(ctx, &resource.ReadRequest{
|
|
||||||
NativeID: req.NativeID,
|
|
||||||
ResourceType: req.ResourceType,
|
|
||||||
TargetConfig: req.TargetConfig,
|
|
||||||
})
|
|
||||||
|
|
||||||
return &resource.UpdateResult{
|
|
||||||
ProgressResult: &resource.ProgressResult{
|
|
||||||
Operation: resource.OperationUpdate,
|
|
||||||
OperationStatus: resource.OperationStatusSuccess,
|
|
||||||
NativeID: req.NativeID,
|
|
||||||
ResourceProperties: json.RawMessage(result.Properties),
|
|
||||||
},
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete removes a resource.
|
|
||||||
func (p *Plugin) Delete(ctx context.Context, req *resource.DeleteRequest) (*resource.DeleteResult, error) {
|
func (p *Plugin) Delete(ctx context.Context, req *resource.DeleteRequest) (*resource.DeleteResult, error) {
|
||||||
config, err := parseTargetConfig(req.TargetConfig)
|
setupLogging()
|
||||||
if err != nil {
|
return p.DeleteLXC(ctx, req)
|
||||||
log.Println(err.Error())
|
|
||||||
return &resource.DeleteResult{
|
|
||||||
ProgressResult: &resource.ProgressResult{
|
|
||||||
Operation: resource.OperationCreate,
|
|
||||||
OperationStatus: resource.OperationStatusFailure,
|
|
||||||
ErrorCode: resource.OperationErrorCodeInternalFailure,
|
|
||||||
StatusMessage: err.Error(),
|
|
||||||
},
|
|
||||||
}, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
username, token, err := getCredentials()
|
|
||||||
if err != nil {
|
|
||||||
log.Println(err.Error())
|
|
||||||
return &resource.DeleteResult{
|
|
||||||
ProgressResult: &resource.ProgressResult{
|
|
||||||
Operation: resource.OperationCreate,
|
|
||||||
OperationStatus: resource.OperationStatusFailure,
|
|
||||||
ErrorCode: resource.OperationErrorCodeInternalFailure,
|
|
||||||
StatusMessage: err.Error(),
|
|
||||||
},
|
|
||||||
}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = authenticatedRequest(http.MethodDelete, config.URL+"/api2/json/nodes/"+config.NODE+"/lxc/"+req.NativeID, createAuthorizationString(username, token), nil)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return &resource.DeleteResult{
|
|
||||||
ProgressResult: &resource.ProgressResult{
|
|
||||||
Operation: resource.OperationCreate,
|
|
||||||
OperationStatus: resource.OperationStatusFailure,
|
|
||||||
ErrorCode: resource.OperationErrorCodeInternalFailure,
|
|
||||||
StatusMessage: err.Error(),
|
|
||||||
},
|
|
||||||
}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &resource.DeleteResult{
|
|
||||||
ProgressResult: &resource.ProgressResult{
|
|
||||||
Operation: resource.OperationCreate,
|
|
||||||
OperationStatus: resource.OperationStatusSuccess,
|
|
||||||
NativeID: req.NativeID,
|
|
||||||
},
|
|
||||||
}, nil
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Status checks the progress of an async operation.
|
|
||||||
// Called when Create/Update/Delete return InProgress status.
|
|
||||||
func (p *Plugin) Status(ctx context.Context, req *resource.StatusRequest) (*resource.StatusResult, error) {
|
func (p *Plugin) Status(ctx context.Context, req *resource.StatusRequest) (*resource.StatusResult, error) {
|
||||||
// TODO: Implement status checking for async operations
|
setupLogging()
|
||||||
//
|
return p.StatusLXC(ctx, req)
|
||||||
// 1. Use req.RequestID to identify the operation
|
|
||||||
// 2. Call your provider's API to check operation status
|
|
||||||
// 3. Return ProgressResult with current status
|
|
||||||
//
|
|
||||||
// If your provider's operations are synchronous, return Success immediately.
|
|
||||||
|
|
||||||
return &resource.StatusResult{
|
|
||||||
ProgressResult: &resource.ProgressResult{
|
|
||||||
Operation: resource.OperationCheckStatus,
|
|
||||||
OperationStatus: resource.OperationStatusFailure,
|
|
||||||
ErrorCode: resource.OperationErrorCodeInternalFailure,
|
|
||||||
StatusMessage: "Status not implemented",
|
|
||||||
},
|
|
||||||
}, ErrNotImplemented
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// List returns all resource identifiers of a given type.
|
|
||||||
// Called during discovery to find unmanaged resources.
|
// Called during discovery to find unmanaged resources.
|
||||||
func (p *Plugin) List(ctx context.Context, req *resource.ListRequest) (*resource.ListResult, error) {
|
func (p *Plugin) List(ctx context.Context, req *resource.ListRequest) (*resource.ListResult, error) {
|
||||||
|
setupLogging()
|
||||||
username, token, err := getCredentials()
|
return p.ListLXC(ctx, req)
|
||||||
if err != nil {
|
|
||||||
return &resource.ListResult{
|
|
||||||
NativeIDs: []string{},
|
|
||||||
}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
config, err := parseTargetConfig(req.TargetConfig)
|
|
||||||
if err != nil {
|
|
||||||
return &resource.ListResult{
|
|
||||||
NativeIDs: []string{},
|
|
||||||
}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var props StatusGeneralResponse
|
|
||||||
|
|
||||||
data, err := authenticatedRequest(http.MethodGet, config.URL+"/api2/json/nodes/"+config.NODE+"/lxc", createAuthorizationString(username, token), nil)
|
|
||||||
if err != nil {
|
|
||||||
return &resource.ListResult{
|
|
||||||
NativeIDs: []string{},
|
|
||||||
}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
json.Unmarshal(data, &props)
|
|
||||||
|
|
||||||
nativeIds := make([]string, 0, len(props.Data))
|
|
||||||
|
|
||||||
for _, value := range props.Data {
|
|
||||||
nativeIds = append(nativeIds, strconv.Itoa(value.VMID))
|
|
||||||
}
|
|
||||||
|
|
||||||
return &resource.ListResult{
|
|
||||||
NativeIDs: nativeIds,
|
|
||||||
NextPageToken: nil,
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,10 +3,9 @@ package main
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"log"
|
"log/slog"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -32,8 +31,10 @@ func TestCreate(t *testing.T) {
|
|||||||
"hostname": "testlxc",
|
"hostname": "testlxc",
|
||||||
"description": "none",
|
"description": "none",
|
||||||
"ostemplate": "local:vztmpl/alpine-3.22-default_20250617_amd64.tar.xz",
|
"ostemplate": "local:vztmpl/alpine-3.22-default_20250617_amd64.tar.xz",
|
||||||
|
"password": "password",
|
||||||
"cores": 1,
|
"cores": 1,
|
||||||
"memory": 512,
|
"memory": 512,
|
||||||
|
"networks": []string{"name=test,hwaddr=BC:24:11:FD:90:BF,bridge=internal,firewall=1"},
|
||||||
}
|
}
|
||||||
|
|
||||||
propertiesJSON, err := json.Marshal(properties)
|
propertiesJSON, err := json.Marshal(properties)
|
||||||
@@ -103,6 +104,7 @@ func TestRead(t *testing.T) {
|
|||||||
require.Equal(t, mem_num, props["memory"], "memory should match")
|
require.Equal(t, mem_num, props["memory"], "memory should match")
|
||||||
const onboot float64 = 0
|
const onboot float64 = 0
|
||||||
require.Equal(t, onboot, props["onboot"], "memory should match")
|
require.Equal(t, onboot, props["onboot"], "memory should match")
|
||||||
|
require.NotEqual(t, nil, props["networks"], "network should be defined")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUpdate(t *testing.T) {
|
func TestUpdate(t *testing.T) {
|
||||||
@@ -116,6 +118,7 @@ func TestUpdate(t *testing.T) {
|
|||||||
"ostemplate": "local:vztmpl/alpine-3.22-default_20250617_amd64.tar.xz",
|
"ostemplate": "local:vztmpl/alpine-3.22-default_20250617_amd64.tar.xz",
|
||||||
"cores": 1,
|
"cores": 1,
|
||||||
"memory": 512,
|
"memory": 512,
|
||||||
|
"networks": []string{"name=test,hwaddr=BC:24:11:FD:90:BF,bridge=internal,firewall=1"},
|
||||||
})
|
})
|
||||||
|
|
||||||
desiredProperties, _ := json.Marshal(map[string]any{
|
desiredProperties, _ := json.Marshal(map[string]any{
|
||||||
@@ -126,6 +129,7 @@ func TestUpdate(t *testing.T) {
|
|||||||
"cores": 2,
|
"cores": 2,
|
||||||
"memory": 1024,
|
"memory": 1024,
|
||||||
"onboot": 1,
|
"onboot": 1,
|
||||||
|
"networks": []string{"name=test1,hwaddr=BC:24:11:FD:90:BF,bridge=internal,firewall=1"},
|
||||||
})
|
})
|
||||||
|
|
||||||
req := &resource.UpdateRequest{
|
req := &resource.UpdateRequest{
|
||||||
@@ -170,7 +174,7 @@ func TestList(t *testing.T) {
|
|||||||
})
|
})
|
||||||
require.NoError(t, err, "ListRequest should not return an error")
|
require.NoError(t, err, "ListRequest should not return an error")
|
||||||
|
|
||||||
log.Printf("Received Ids: %s", strings.Join(result.NativeIDs, ", "))
|
slog.Info("Received Ids", "ids", result.NativeIDs)
|
||||||
|
|
||||||
require.Contains(t, result.NativeIDs, "200", "List should include created LXC")
|
require.Contains(t, result.NativeIDs, "200", "List should include created LXC")
|
||||||
}
|
}
|
||||||
@@ -195,8 +199,8 @@ func TestDelete(t *testing.T) {
|
|||||||
NativeID: "200",
|
NativeID: "200",
|
||||||
})
|
})
|
||||||
|
|
||||||
require.NoError(t, err, "Create should not return error")
|
require.NoError(t, err, "Delete should not return error")
|
||||||
require.NotNil(t, result.ProgressResult, "Create should return ProgressResult")
|
require.NotNil(t, result.ProgressResult, "Delete should return ProgressResult")
|
||||||
|
|
||||||
require.Eventually(t, func() bool {
|
require.Eventually(t, func() bool {
|
||||||
var props StatusGeneralResponse
|
var props StatusGeneralResponse
|
||||||
@@ -213,5 +217,5 @@ func TestDelete(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}, 10*time.Second, time.Second, "Create operation should complete successfully")
|
}, 10*time.Second, time.Second, "Delete operation should complete successfully")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,6 @@ package {
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
["formae"] {
|
["formae"] {
|
||||||
uri = "package://hub.platform.engineering/plugins/pkl/schema/pkl/formae/formae@0.80.1"
|
uri = "package://hub.platform.engineering/plugins/pkl/schema/pkl/formae/formae@0.82.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,6 +28,9 @@ class LXC extends formae.Resource {
|
|||||||
@formae.FieldHint {}
|
@formae.FieldHint {}
|
||||||
ostemplate: String?
|
ostemplate: String?
|
||||||
|
|
||||||
|
@formae.FieldHint {}
|
||||||
|
password: String?
|
||||||
|
|
||||||
@formae.FieldHint {}
|
@formae.FieldHint {}
|
||||||
hostname: String
|
hostname: String
|
||||||
|
|
||||||
@@ -43,4 +46,10 @@ class LXC extends formae.Resource {
|
|||||||
@formae.FieldHint {}
|
@formae.FieldHint {}
|
||||||
onboot: Int = 0
|
onboot: Int = 0
|
||||||
|
|
||||||
|
@formae.FieldHint {}
|
||||||
|
sshkeys: Listing<String>?
|
||||||
|
|
||||||
|
@formae.FieldHint {}
|
||||||
|
networks: Listing<String>?
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
2
testdata/PklProject
vendored
2
testdata/PklProject
vendored
@@ -7,6 +7,6 @@ dependencies {
|
|||||||
|
|
||||||
// Formae schema - fetched from public registry
|
// Formae schema - fetched from public registry
|
||||||
["formae"] {
|
["formae"] {
|
||||||
uri = "package://hub.platform.engineering/plugins/pkl/schema/pkl/formae/formae@0.80.1"
|
uri = "package://hub.platform.engineering/plugins/pkl/schema/pkl/formae/formae@0.82.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
1
testdata/resource-replace.pkl
vendored
1
testdata/resource-replace.pkl
vendored
@@ -31,6 +31,7 @@ forma {
|
|||||||
hostname = "test-lxc"
|
hostname = "test-lxc"
|
||||||
description = "some other description"
|
description = "some other description"
|
||||||
ostemplate = "local:vztmpl/alpine-3.22-default_20250617_amd64.tar.xz"
|
ostemplate = "local:vztmpl/alpine-3.22-default_20250617_amd64.tar.xz"
|
||||||
|
password = "test"
|
||||||
cores = 1
|
cores = 1
|
||||||
memory = 512
|
memory = 512
|
||||||
}
|
}
|
||||||
|
|||||||
3
testdata/resource-update.pkl
vendored
3
testdata/resource-update.pkl
vendored
@@ -34,5 +34,8 @@ forma {
|
|||||||
cores = 2
|
cores = 2
|
||||||
memory = 1024
|
memory = 1024
|
||||||
onboot = 1
|
onboot = 1
|
||||||
|
networks = new Listing<String> {
|
||||||
|
"name=first,hwaddr=BC:24:11:FD:90:BF,bridge=internal"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
5
testdata/resource.pkl
vendored
5
testdata/resource.pkl
vendored
@@ -33,7 +33,12 @@ forma {
|
|||||||
hostname = "test-lxc"
|
hostname = "test-lxc"
|
||||||
description = "no description provided"
|
description = "no description provided"
|
||||||
ostemplate = "local:vztmpl/alpine-3.22-default_20250617_amd64.tar.xz"
|
ostemplate = "local:vztmpl/alpine-3.22-default_20250617_amd64.tar.xz"
|
||||||
|
password = "abcd"
|
||||||
cores = 1
|
cores = 1
|
||||||
memory = 512
|
memory = 512
|
||||||
|
networks = new Listing<String> {
|
||||||
|
"name=first,hwaddr=BC:24:11:FD:90:BF,bridge=internal"
|
||||||
|
"name=second,hwaddr=BC:24:11:FD:90:BF,bridge=internal"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
17
types.go
17
types.go
@@ -12,9 +12,12 @@ type LXCProperties struct {
|
|||||||
Hostname string `json:"hostname"`
|
Hostname string `json:"hostname"`
|
||||||
Description string `json:"description,omitempty"`
|
Description string `json:"description,omitempty"`
|
||||||
OSTemplate string `json:"ostemplate,omitempty"`
|
OSTemplate string `json:"ostemplate,omitempty"`
|
||||||
|
Password string `json:"password,omitempty"`
|
||||||
Cores int `json:"cores"`
|
Cores int `json:"cores"`
|
||||||
Memory int `json:"memory"`
|
Memory int `json:"memory"`
|
||||||
OnBoot int `json:"onboot"`
|
OnBoot int `json:"onboot"`
|
||||||
|
SSHKeys []string `json:"sshkeys"`
|
||||||
|
Networks []string `json:"networks"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ReadRequest struct {
|
type ReadRequest struct {
|
||||||
@@ -73,8 +76,22 @@ type StatusLXCConfig struct {
|
|||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
Digest string `json:"digest"`
|
Digest string `json:"digest"`
|
||||||
OnBoot int `json:"onboot"`
|
OnBoot int `json:"onboot"`
|
||||||
|
Net0 string `json:"net0,omitempty"`
|
||||||
|
Net1 string `json:"net1,omitempty"`
|
||||||
|
Net2 string `json:"net2,omitempty"`
|
||||||
|
Net3 string `json:"net3,omitempty"`
|
||||||
|
Net4 string `json:"net4,omitempty"`
|
||||||
|
Net5 string `json:"net5,omitempty"`
|
||||||
|
Net6 string `json:"net6,omitempty"`
|
||||||
|
Net7 string `json:"net7,omitempty"`
|
||||||
|
Net8 string `json:"net8,omitempty"`
|
||||||
|
Net9 string `json:"net9,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type StatusLXCConfigResponse struct {
|
type StatusLXCConfigResponse struct {
|
||||||
Data StatusLXCConfig `json:"data"`
|
Data StatusLXCConfig `json:"data"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ProxmoxDataResponse struct {
|
||||||
|
Data string `json:"data"`
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user