almost empty
This commit is contained in:
13
.github/dependabot.yml
vendored
Normal file
13
.github/dependabot.yml
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "gomod"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
open-pull-requests-limit: 5
|
||||
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
open-pull-requests-limit: 5
|
||||
198
.github/workflows/ci.yml
vendored
Normal file
198
.github/workflows/ci.yml
vendored
Normal file
@@ -0,0 +1,198 @@
|
||||
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"
|
||||
34
.gitignore
vendored
Normal file
34
.gitignore
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
# Binaries
|
||||
bin/
|
||||
dist/
|
||||
*.so
|
||||
*.exe
|
||||
|
||||
# Go
|
||||
vendor/
|
||||
|
||||
# IDE
|
||||
.idea/
|
||||
.vscode/
|
||||
*.swp
|
||||
*.swo
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Test coverage
|
||||
coverage.out
|
||||
coverage.html
|
||||
|
||||
# GoReleaser
|
||||
dist/
|
||||
|
||||
# Pkl
|
||||
.pkl-cache/
|
||||
*.pkl-expected.pcf
|
||||
PklProject.deps.json
|
||||
|
||||
# Claude Code
|
||||
CLAUDE.md
|
||||
.claude/
|
||||
201
LICENSE
Normal file
201
LICENSE
Normal file
@@ -0,0 +1,201 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to the Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright 2026 ManInDark
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
121
Makefile
Normal file
121
Makefile
Normal file
@@ -0,0 +1,121 @@
|
||||
# Formae Plugin Makefile
|
||||
#
|
||||
# Targets:
|
||||
# build - Build the plugin binary
|
||||
# test - Run tests
|
||||
# lint - Run linter
|
||||
# clean - Remove build artifacts
|
||||
# install - Build and install plugin locally (binary + schema + manifest)
|
||||
|
||||
# Plugin metadata - extracted from formae-plugin.pkl
|
||||
PLUGIN_NAME := $(shell pkl eval -x 'name' formae-plugin.pkl 2>/dev/null || echo "example")
|
||||
PLUGIN_VERSION := $(shell pkl eval -x 'version' formae-plugin.pkl 2>/dev/null || echo "0.0.0")
|
||||
PLUGIN_NAMESPACE := $(shell pkl eval -x 'namespace' formae-plugin.pkl 2>/dev/null || echo "EXAMPLE")
|
||||
|
||||
# Build settings
|
||||
GO := go
|
||||
GOFLAGS := -trimpath
|
||||
BINARY := $(PLUGIN_NAME)
|
||||
|
||||
# Installation paths
|
||||
# Plugin discovery expects lowercase directory names matching the plugin name
|
||||
PLUGIN_BASE_DIR := $(HOME)/.pel/formae/plugins
|
||||
INSTALL_DIR := $(PLUGIN_BASE_DIR)/$(PLUGIN_NAME)/v$(PLUGIN_VERSION)
|
||||
|
||||
.PHONY: all build test test-unit test-integration lint verify-schema clean install help clean-environment conformance-test conformance-test-crud conformance-test-discovery
|
||||
|
||||
all: build
|
||||
|
||||
## build: Build the plugin binary
|
||||
build:
|
||||
$(GO) build $(GOFLAGS) -o bin/$(BINARY) .
|
||||
|
||||
## test: Run all tests
|
||||
test:
|
||||
$(GO) test -v ./...
|
||||
|
||||
## test-unit: Run unit tests only (tests with //go:build unit tag)
|
||||
test-unit:
|
||||
$(GO) test -v -tags=unit ./...
|
||||
|
||||
## test-integration: Run integration tests (requires cloud credentials)
|
||||
## Add tests with //go:build integration tag
|
||||
test-integration:
|
||||
$(GO) test -v -tags=integration ./...
|
||||
|
||||
## lint: Run golangci-lint
|
||||
lint:
|
||||
golangci-lint run
|
||||
|
||||
## verify-schema: Validate PKL schema files
|
||||
## Checks that schema files are well-formed and follow formae conventions.
|
||||
verify-schema:
|
||||
$(GO) run github.com/platform-engineering-labs/formae/pkg/plugin/testutil/cmd/verify-schema --namespace $(PLUGIN_NAMESPACE) ./schema/pkl
|
||||
|
||||
## clean: Remove build artifacts
|
||||
clean:
|
||||
rm -rf bin/ dist/
|
||||
|
||||
## install: Build and install plugin locally (binary + schema + manifest)
|
||||
## Installs to ~/.pel/formae/plugins/<name>/v<version>/
|
||||
## Removes any existing versions of the plugin first to ensure clean state.
|
||||
install: build
|
||||
@echo "Installing $(PLUGIN_NAME) v$(PLUGIN_VERSION) (namespace: $(PLUGIN_NAMESPACE))..."
|
||||
@rm -rf $(PLUGIN_BASE_DIR)/$(PLUGIN_NAME)
|
||||
@mkdir -p $(INSTALL_DIR)/schema/pkl
|
||||
@cp bin/$(BINARY) $(INSTALL_DIR)/$(BINARY)
|
||||
@cp -r schema/pkl/* $(INSTALL_DIR)/schema/pkl/
|
||||
@cp formae-plugin.pkl $(INSTALL_DIR)/
|
||||
@echo "Installed to $(INSTALL_DIR)"
|
||||
@echo " - Binary: $(INSTALL_DIR)/$(BINARY)"
|
||||
@echo " - Schema: $(INSTALL_DIR)/schema/pkl/"
|
||||
@echo " - Manifest: $(INSTALL_DIR)/formae-plugin.pkl"
|
||||
|
||||
## help: Show this help message
|
||||
help:
|
||||
@echo "Available targets:"
|
||||
@grep -E '^## ' $(MAKEFILE_LIST) | sed 's/## / /'
|
||||
|
||||
## clean-environment: Clean up test resources in cloud environment
|
||||
## Called before and after conformance tests. Edit scripts/ci/clean-environment.sh
|
||||
## to configure for your provider.
|
||||
clean-environment:
|
||||
@./scripts/ci/clean-environment.sh
|
||||
|
||||
## conformance-test: Run all conformance tests (CRUD + discovery)
|
||||
## Usage: make conformance-test [VERSION=0.80.0] [TEST=s3-bucket]
|
||||
## Downloads the specified formae version (or latest) and runs conformance tests.
|
||||
## Calls clean-environment before and after tests.
|
||||
##
|
||||
## Parameters:
|
||||
## VERSION - Formae version to test against (default: latest)
|
||||
## TEST - Filter tests by name pattern (e.g., TEST=s3-bucket)
|
||||
conformance-test: conformance-test-crud conformance-test-discovery
|
||||
|
||||
## conformance-test-crud: Run only CRUD lifecycle tests
|
||||
## Usage: make conformance-test-crud [VERSION=0.80.0] [TEST=s3-bucket]
|
||||
conformance-test-crud: install
|
||||
@echo "Pre-test cleanup..."
|
||||
@./scripts/ci/clean-environment.sh || true
|
||||
@echo ""
|
||||
@echo "Running CRUD conformance tests..."
|
||||
@FORMAE_TEST_FILTER="$(TEST)" FORMAE_TEST_TYPE=crud ./scripts/run-conformance-tests.sh $(VERSION); \
|
||||
TEST_EXIT=$$?; \
|
||||
echo ""; \
|
||||
echo "Post-test cleanup..."; \
|
||||
./scripts/ci/clean-environment.sh || true; \
|
||||
exit $$TEST_EXIT
|
||||
|
||||
## conformance-test-discovery: Run only discovery tests
|
||||
## Usage: make conformance-test-discovery [VERSION=0.80.0] [TEST=s3-bucket]
|
||||
conformance-test-discovery: install
|
||||
@echo "Pre-test cleanup..."
|
||||
@./scripts/ci/clean-environment.sh || true
|
||||
@echo ""
|
||||
@echo "Running discovery conformance tests..."
|
||||
@FORMAE_TEST_FILTER="$(TEST)" FORMAE_TEST_TYPE=discovery ./scripts/run-conformance-tests.sh $(VERSION); \
|
||||
TEST_EXIT=$$?; \
|
||||
echo ""; \
|
||||
echo "Post-test cleanup..."; \
|
||||
./scripts/ci/clean-environment.sh || true; \
|
||||
exit $$TEST_EXIT
|
||||
134
README.md
Normal file
134
README.md
Normal file
@@ -0,0 +1,134 @@
|
||||
> **⚠️ Do not clone this repository directly!**
|
||||
>
|
||||
> Use `formae plugin init` to create your plugin. This command scaffolds a new
|
||||
> plugin from this template with proper naming and configuration.
|
||||
>
|
||||
> ```bash
|
||||
> formae plugin init my-plugin
|
||||
> ```
|
||||
|
||||
---
|
||||
|
||||
## Setup Checklist
|
||||
|
||||
*Remove this section and the warning above after completing setup.*
|
||||
|
||||
After creating your plugin with `formae plugin init`, complete these steps:
|
||||
|
||||
- [X] Update `formae-plugin.pkl` with your plugin metadata (name, namespace, description)
|
||||
- [X] Define your resource types in `schema/pkl/*.pkl`
|
||||
- [ ] Implement CRUD operations in `plugin.go`
|
||||
- [ ] Update test fixtures in `testdata/*.pkl` to use your resources
|
||||
- [ ] Update this README (replace title, description, resources table, etc.)
|
||||
- [ ] Set up local credentials for testing
|
||||
- [ ] Run conformance tests locally: `make conformance-test`
|
||||
- [ ] Configure CI credentials in `.github/workflows/ci.yml` (optional)
|
||||
- [ ] Remove this checklist section and the warning box above
|
||||
|
||||
For detailed guidance, see the [Plugin SDK Documentation](https://docs.formae.io/plugin-sdk).
|
||||
|
||||
---
|
||||
|
||||
# Example Plugin for formae
|
||||
|
||||
*TODO: Update title and description for your plugin*
|
||||
|
||||
Example Formae plugin template - replace this with a description of what your plugin manages.
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
# Install the plugin
|
||||
make install
|
||||
```
|
||||
|
||||
## Supported Resources
|
||||
|
||||
*TODO: Document your supported resource types*
|
||||
|
||||
| Resource Type | Description |
|
||||
| ------------------------------ | ----------------------------------------------------- |
|
||||
| `PROXMOX::Service::Resource` | Example resource (replace with your actual resources) |
|
||||
|
||||
## Configuration
|
||||
|
||||
Configure a target in your Forma file:
|
||||
|
||||
```pkl
|
||||
new formae.Target {
|
||||
label = "my-target"
|
||||
namespace = "PROXMOX" // TODO: Update with your namespace
|
||||
config = new Mapping {
|
||||
["region"] = "us-east-1"
|
||||
// TODO: Add your provider-specific configuration
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
See the [examples/](examples/) directory for usage examples.
|
||||
|
||||
```bash
|
||||
# Evaluate an example
|
||||
formae eval examples/basic/main.pkl
|
||||
|
||||
# Apply resources
|
||||
formae apply --mode reconcile --watch examples/basic/main.pkl
|
||||
```
|
||||
|
||||
## Development
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Go 1.25+
|
||||
- [Pkl CLI](https://pkl-lang.org/main/current/pkl-cli/index.html)
|
||||
- Cloud provider credentials (for conformance testing)
|
||||
|
||||
### Building
|
||||
|
||||
```bash
|
||||
make build # Build plugin binary
|
||||
make test # Run unit tests
|
||||
make lint # Run linter
|
||||
make install # Build + install locally
|
||||
```
|
||||
|
||||
### Local Testing
|
||||
|
||||
```bash
|
||||
# Install plugin locally
|
||||
make install
|
||||
|
||||
# Start formae agent
|
||||
formae agent start
|
||||
|
||||
# Apply example resources
|
||||
formae apply --mode reconcile --watch examples/basic/main.pkl
|
||||
```
|
||||
|
||||
### Conformance Testing
|
||||
|
||||
Conformance tests validate your plugin's CRUD lifecycle using the test fixtures in `testdata/`:
|
||||
|
||||
| File | Purpose |
|
||||
| ------------------------ | -------------------------------- |
|
||||
| `resource.pkl` | Initial resource creation |
|
||||
| `resource-update.pkl` | In-place update (mutable fields) |
|
||||
| `resource-replace.pkl` | Replacement (createOnly fields) |
|
||||
|
||||
The test harness sets `FORMAE_TEST_RUN_ID` for unique resource naming between runs.
|
||||
|
||||
```bash
|
||||
make conformance-test # Latest formae version
|
||||
make conformance-test VERSION=0.80.0 # Specific version
|
||||
```
|
||||
|
||||
The `scripts/ci/clean-environment.sh` script cleans up test resources. It runs before and after conformance tests and should be idempotent.
|
||||
|
||||
## Licensing
|
||||
|
||||
Plugins are independent works and may be licensed under any license of the author’s choosing.
|
||||
|
||||
See the formae plugin policy:
|
||||
<https://docs.formae.io/plugin-sdk/
|
||||
22
conformance_test.go
Normal file
22
conformance_test.go
Normal file
@@ -0,0 +1,22 @@
|
||||
// © 2025 Platform Engineering Labs Inc.
|
||||
//
|
||||
// SPDX-License-Identifier: FSL-1.1-ALv2
|
||||
|
||||
//go:build conformance
|
||||
|
||||
// Conformance tests for the plugin. Run with: make conformance-test
|
||||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
conformance "github.com/platform-engineering-labs/formae/pkg/plugin-conformance-tests"
|
||||
)
|
||||
|
||||
func TestPluginConformance(t *testing.T) {
|
||||
conformance.RunCRUDTests(t)
|
||||
}
|
||||
|
||||
func TestPluginDiscovery(t *testing.T) {
|
||||
conformance.RunDiscoveryTests(t)
|
||||
}
|
||||
12
examples/basic/PklProject
Normal file
12
examples/basic/PklProject
Normal file
@@ -0,0 +1,12 @@
|
||||
amends "pkl:Project"
|
||||
|
||||
dependencies {
|
||||
// Reference local plugin schema during development
|
||||
// IMPORTANT: The alias must match the package name from the schema's PklProject
|
||||
["proxmox"] = import("../../schema/pkl/PklProject")
|
||||
|
||||
// Formae schema - fetched from public registry
|
||||
["formae"] {
|
||||
uri = "package://hub.platform.engineering/plugins/pkl/schema/pkl/formae/formae@0.80.0"
|
||||
}
|
||||
}
|
||||
46
examples/basic/main.pkl
Normal file
46
examples/basic/main.pkl
Normal file
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Basic Example
|
||||
*
|
||||
* This example shows how to define resources using your plugin.
|
||||
* Run with: formae apply --mode reconcile --watch examples/basic/main.pkl
|
||||
*/
|
||||
amends "@formae/forma.pkl"
|
||||
|
||||
import "@formae/formae.pkl"
|
||||
import "@proxmox/proxmox.pkl"
|
||||
|
||||
forma {
|
||||
new formae.Stack {
|
||||
label = "default"
|
||||
description = "Default stack for example resources"
|
||||
}
|
||||
|
||||
new formae.Target {
|
||||
label = "my-target"
|
||||
namespace = "PROXMOX"
|
||||
// Add your provider-specific configuration here
|
||||
// For typed config, create a Config class in your schema:
|
||||
// config = new example.Config { region = "us-east-1" }
|
||||
config = new Mapping {
|
||||
["region"] = "us-east-1"
|
||||
}
|
||||
}
|
||||
|
||||
new example.ExampleResource {
|
||||
label = "my-resource"
|
||||
name = "My Example Resource"
|
||||
description = "This is an example resource"
|
||||
region = "us-east-1"
|
||||
|
||||
endpoint = new example.Endpoint {
|
||||
url = "https://api.example.com"
|
||||
port = 8080
|
||||
protocol = "https"
|
||||
}
|
||||
|
||||
tags = new Listing {
|
||||
new example.Tag { key = "Environment"; value = "development" }
|
||||
new example.Tag { key = "Team"; value = "platform" }
|
||||
}
|
||||
}
|
||||
}
|
||||
22
formae-plugin.pkl
Normal file
22
formae-plugin.pkl
Normal file
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Formae Plugin Manifest
|
||||
*
|
||||
* This file defines metadata for your Formae plugin.
|
||||
* Update the values below to match your plugin.
|
||||
*/
|
||||
|
||||
name = "proxmox"
|
||||
|
||||
version = "0.1.0"
|
||||
|
||||
namespace = "PROXMOX"
|
||||
|
||||
description = "Proxmox Plugin"
|
||||
|
||||
license = "Apache-2.0"
|
||||
|
||||
minFormaeVersion = "0.80.1"
|
||||
|
||||
output {
|
||||
renderer = new JsonRenderer {}
|
||||
}
|
||||
46
go.mod
Normal file
46
go.mod
Normal file
@@ -0,0 +1,46 @@
|
||||
module github.com/platform-engineering-labs/formae-plugin-proxmox
|
||||
|
||||
go 1.25
|
||||
|
||||
require (
|
||||
github.com/platform-engineering-labs/formae/pkg/plugin v0.1.8
|
||||
github.com/platform-engineering-labs/formae/pkg/plugin-conformance-tests v0.1.9
|
||||
)
|
||||
|
||||
require (
|
||||
ergo.services/actor/statemachine v0.0.0-20251202053101-c0aa08b403e5 // indirect
|
||||
ergo.services/ergo v1.999.310 // indirect
|
||||
github.com/apple/pkl-go v0.12.0 // indirect
|
||||
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/go-logr/logr v1.4.3 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 // 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/model v0.1.2 // indirect
|
||||
github.com/theory/jsonpath v0.10.2 // indirect
|
||||
github.com/tidwall/gjson v1.18.0 // indirect
|
||||
github.com/tidwall/match v1.1.1 // indirect
|
||||
github.com/tidwall/pretty v1.2.1 // indirect
|
||||
github.com/tidwall/sjson v1.2.5 // indirect
|
||||
github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect
|
||||
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
|
||||
go.opentelemetry.io/otel v1.39.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.39.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.39.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.39.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v1.39.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk/metric v1.39.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.39.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v1.9.0 // indirect
|
||||
golang.org/x/net v0.47.0 // indirect
|
||||
golang.org/x/sys v0.39.0 // indirect
|
||||
golang.org/x/text v0.31.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect
|
||||
google.golang.org/grpc v1.77.0 // indirect
|
||||
google.golang.org/protobuf v1.36.10 // indirect
|
||||
)
|
||||
93
go.sum
Normal file
93
go.sum
Normal file
@@ -0,0 +1,93 @@
|
||||
ergo.services/actor/statemachine v0.0.0-20251202053101-c0aa08b403e5 h1:8b8Y8gLBvQrDIuKFSCwT089l1iltqsTEWXzhm/pSguE=
|
||||
ergo.services/actor/statemachine v0.0.0-20251202053101-c0aa08b403e5/go.mod h1:epgQDQTm93L3/plOnIejSYbl/SNhulI78aa1LC0tLn0=
|
||||
ergo.services/ergo v1.999.310 h1:qHx35J5UxCheNJaFrOK/1K6/p7jir680+NTPZo6bhbI=
|
||||
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/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
|
||||
github.com/apple/pkl-go v0.12.0 h1:0gnhEIXo6coSHPpxdOESfGn2GrSkBSaeitkZLwZAcWE=
|
||||
github.com/apple/pkl-go v0.12.0/go.mod h1:EDQmYVtFBok/eLI+9rT0EoBBXNtMM1THwR+rwBcAH3I=
|
||||
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/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
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/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
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/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
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/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
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/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
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/grpc-ecosystem/grpc-gateway/v2 v2.27.3 h1:NmZ1PKzSTQbuGHw9DGPFomqkkLWMC+vZCkfs+FHv1Vg=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3/go.mod h1:zQrxl1YP88HQlA6i9c63DSVPFklWpGX4OWAc9bFuaH4=
|
||||
github.com/masterminds/semver v1.5.0 h1:hTxJTTY7tjvnWMrl08O6u3G6BLlKVwxSz01lVac9P8U=
|
||||
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/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.2/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.8/go.mod h1:KzNzkc67phbeqs61ji+r8Y1WCD5QnDEpU7rfUHkmmuQ=
|
||||
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.9/go.mod h1:NuoGHFF4WCI73scZ4odspPzZiKCWRvrKWTvy0rgoNec=
|
||||
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/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
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/go.mod h1:ZOz+y6MxTEDcN/FOxf9AOgeHSoKHx2B+E0nD3HOtzGE=
|
||||
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/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/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/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||
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/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/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g=
|
||||
github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=
|
||||
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/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48=
|
||||
go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.39.0 h1:cEf8jF6WbuGQWUVcqgyWtTR0kOOAWY1DYZ+UhvdmQPw=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.39.0/go.mod h1:k1lzV5n5U3HkGvTCJHraTAGJ7MqsgL1wrGwTj1Isfiw=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.39.0 h1:nKP4Z2ejtHn3yShBb+2KawiXgpn8In5cT7aO2wXuOTE=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.39.0/go.mod h1:NwjeBbNigsO4Aj9WgM0C+cKIrxsZUaRmZUO7A8I7u8o=
|
||||
go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0=
|
||||
go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs=
|
||||
go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18=
|
||||
go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew=
|
||||
go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI=
|
||||
go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA=
|
||||
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=
|
||||
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
|
||||
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
|
||||
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
|
||||
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
|
||||
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
|
||||
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
||||
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-20251202230838-ff82c1b0f217/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto=
|
||||
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-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
|
||||
google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM=
|
||||
google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig=
|
||||
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
|
||||
google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
11
main.go
Normal file
11
main.go
Normal file
@@ -0,0 +1,11 @@
|
||||
// © 2025 Platform Engineering Labs Inc.
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package main
|
||||
|
||||
import "github.com/platform-engineering-labs/formae/pkg/plugin/sdk"
|
||||
|
||||
func main() {
|
||||
sdk.RunWithManifest(&Plugin{}, sdk.RunConfig{})
|
||||
}
|
||||
186
proxmox.go
Normal file
186
proxmox.go
Normal file
@@ -0,0 +1,186 @@
|
||||
// © 2025 Platform Engineering Labs Inc.
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"github.com/platform-engineering-labs/formae/pkg/plugin"
|
||||
"github.com/platform-engineering-labs/formae/pkg/plugin/resource"
|
||||
)
|
||||
|
||||
// ErrNotImplemented is returned by stub methods that need implementation.
|
||||
var ErrNotImplemented = errors.New("not implemented")
|
||||
|
||||
// Plugin implements the Formae ResourcePlugin interface.
|
||||
// The SDK automatically provides identity methods (Name, Version, Namespace)
|
||||
// by reading formae-plugin.pkl at startup.
|
||||
type Plugin struct{}
|
||||
|
||||
// Compile-time check: Plugin must satisfy ResourcePlugin interface.
|
||||
var _ plugin.ResourcePlugin = &Plugin{}
|
||||
|
||||
// =============================================================================
|
||||
// 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 {
|
||||
return plugin.RateLimitConfig{
|
||||
Scope: plugin.RateLimitScopeNamespace,
|
||||
MaxRequestsPerSecondForNamespace: 10, // TODO: Adjust based on provider limits
|
||||
}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
// 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
|
||||
}
|
||||
|
||||
// LabelConfig returns the configuration for extracting human-readable labels
|
||||
// from discovered resources.
|
||||
func (p *Plugin) LabelConfig() plugin.LabelConfig {
|
||||
return plugin.LabelConfig{
|
||||
// Default JSONPath query to extract label from resources
|
||||
// Example for tagged resources: $.Tags[?(@.Key=='Name')].Value
|
||||
DefaultQuery: "$.Name", // TODO: Adjust for your provider
|
||||
|
||||
// Override for specific resource types
|
||||
ResourceOverrides: map[string]string{
|
||||
// "PROXMOX::Service::SpecialResource": "$.DisplayName",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// CRUD Operations
|
||||
// =============================================================================
|
||||
|
||||
// Create provisions a new resource.
|
||||
func (p *Plugin) Create(ctx context.Context, req *resource.CreateRequest) (*resource.CreateResult, error) {
|
||||
// TODO: Implement resource creation
|
||||
//
|
||||
// 1. Parse req.Properties to get resource configuration (json.RawMessage)
|
||||
// 2. Parse req.TargetConfig to get provider credentials/config
|
||||
// 3. Call your provider's API to create the resource
|
||||
// 4. Return ProgressResult with:
|
||||
// - NativeID: the provider's identifier for the resource
|
||||
// - OperationStatus: Success, Failure, or InProgress
|
||||
// - If InProgress, set RequestID for status polling
|
||||
|
||||
return &resource.CreateResult{
|
||||
ProgressResult: &resource.ProgressResult{
|
||||
Operation: resource.OperationCreate,
|
||||
OperationStatus: resource.OperationStatusFailure,
|
||||
ErrorCode: resource.OperationErrorCodeInternalFailure,
|
||||
StatusMessage: "Create not implemented",
|
||||
},
|
||||
}, ErrNotImplemented
|
||||
}
|
||||
|
||||
// Read retrieves the current state of a resource.
|
||||
func (p *Plugin) Read(ctx context.Context, req *resource.ReadRequest) (*resource.ReadResult, error) {
|
||||
// TODO: Implement resource read
|
||||
//
|
||||
// 1. Use req.NativeID to identify the resource
|
||||
// 2. Parse req.TargetConfig for provider credentials
|
||||
// 3. Call your provider's API to get current state
|
||||
// 4. Return ReadResult with Properties as JSON string
|
||||
|
||||
return &resource.ReadResult{
|
||||
ResourceType: req.ResourceType,
|
||||
ErrorCode: resource.OperationErrorCodeInternalFailure,
|
||||
}, ErrNotImplemented
|
||||
}
|
||||
|
||||
// Update modifies an existing resource.
|
||||
func (p *Plugin) Update(ctx context.Context, req *resource.UpdateRequest) (*resource.UpdateResult, error) {
|
||||
// TODO: Implement resource update
|
||||
//
|
||||
// 1. Use req.NativeID to identify the resource
|
||||
// 2. Use req.PatchDocument for changes (JSON Patch format)
|
||||
// Or compare req.PriorProperties with req.DesiredProperties
|
||||
// 3. Call your provider's API to apply changes
|
||||
// 4. Return ProgressResult with status
|
||||
|
||||
return &resource.UpdateResult{
|
||||
ProgressResult: &resource.ProgressResult{
|
||||
Operation: resource.OperationUpdate,
|
||||
OperationStatus: resource.OperationStatusFailure,
|
||||
ErrorCode: resource.OperationErrorCodeInternalFailure,
|
||||
StatusMessage: "Update not implemented",
|
||||
},
|
||||
}, ErrNotImplemented
|
||||
}
|
||||
|
||||
// Delete removes a resource.
|
||||
func (p *Plugin) Delete(ctx context.Context, req *resource.DeleteRequest) (*resource.DeleteResult, error) {
|
||||
// TODO: Implement resource deletion
|
||||
//
|
||||
// 1. Use req.NativeID to identify the resource
|
||||
// 2. Parse req.TargetConfig for provider credentials
|
||||
// 3. Call your provider's API to delete the resource
|
||||
// 4. Return ProgressResult with status
|
||||
|
||||
return &resource.DeleteResult{
|
||||
ProgressResult: &resource.ProgressResult{
|
||||
Operation: resource.OperationDelete,
|
||||
OperationStatus: resource.OperationStatusFailure,
|
||||
ErrorCode: resource.OperationErrorCodeInternalFailure,
|
||||
StatusMessage: "Delete not implemented",
|
||||
},
|
||||
}, ErrNotImplemented
|
||||
}
|
||||
|
||||
// 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) {
|
||||
// TODO: Implement status checking for async operations
|
||||
//
|
||||
// 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.
|
||||
func (p *Plugin) List(ctx context.Context, req *resource.ListRequest) (*resource.ListResult, error) {
|
||||
// TODO: Implement resource listing for discovery
|
||||
//
|
||||
// 1. Use req.ResourceType to determine what to list
|
||||
// 2. Parse req.TargetConfig for provider credentials
|
||||
// 3. Use req.PageToken/PageSize for pagination
|
||||
// 4. Call your provider's API to list resources
|
||||
// 5. Return NativeIDs and NextPageToken (if more pages)
|
||||
|
||||
return &resource.ListResult{
|
||||
NativeIDs: []string{},
|
||||
NextPageToken: nil,
|
||||
}, ErrNotImplemented
|
||||
}
|
||||
16
schema/pkl/PklProject
Normal file
16
schema/pkl/PklProject
Normal file
@@ -0,0 +1,16 @@
|
||||
amends "pkl:Project"
|
||||
|
||||
// Package configuration for local development.
|
||||
// Update baseUri and packageZipUrl when publishing to a registry.
|
||||
package {
|
||||
name = "proxmox"
|
||||
baseUri = "package://localhost/plugins/example/schema/pkl/example"
|
||||
version = "0.1.0"
|
||||
packageZipUrl = "https://localhost/plugins/example/schema/pkl/example@\(version).zip"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
["formae"] {
|
||||
uri = "package://hub.platform.engineering/plugins/pkl/schema/pkl/formae/formae@0.80.1"
|
||||
}
|
||||
}
|
||||
21
schema/pkl/proxmox.pkl
Normal file
21
schema/pkl/proxmox.pkl
Normal file
@@ -0,0 +1,21 @@
|
||||
module proxmox.lxc
|
||||
|
||||
import "@formae/formae.pkl"
|
||||
|
||||
@formae.ResourceHint {
|
||||
type = "PROXMOX::Service::LXC"
|
||||
identifier = "$.vmid"
|
||||
}
|
||||
class ExampleResource extends formae.Resource {
|
||||
fixed hidden type: String = "PROXMOX::Service::LXC"
|
||||
|
||||
@formae.FieldHint { createOnly = true }
|
||||
vmid: String
|
||||
|
||||
@formae.FieldHint {}
|
||||
name: String
|
||||
|
||||
@formae.FieldHint {}
|
||||
description: String = "No description"
|
||||
|
||||
}
|
||||
61
scripts/ci/clean-environment.sh
Executable file
61
scripts/ci/clean-environment.sh
Executable file
@@ -0,0 +1,61 @@
|
||||
#!/bin/bash
|
||||
# © 2025 Platform Engineering Labs Inc.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
# Clean Environment Hook
|
||||
# ======================
|
||||
# This script is called before AND after conformance tests to clean up
|
||||
# test resources in your cloud environment.
|
||||
#
|
||||
# Purpose:
|
||||
# - Before tests: Remove orphaned resources from previous failed runs
|
||||
# - After tests: Clean up resources created during the test run
|
||||
#
|
||||
# The script should be idempotent - safe to run multiple times.
|
||||
# It should delete all resources matching the test resource prefix.
|
||||
#
|
||||
# Test resources typically use a naming convention like:
|
||||
# formae-plugin-sdk-test-{run-id}-*
|
||||
#
|
||||
# Implementation varies by provider. Examples:
|
||||
#
|
||||
# AWS:
|
||||
# - List and delete resources with test prefix using AWS CLI
|
||||
# - Use resource tagging for easier identification
|
||||
#
|
||||
# OpenStack:
|
||||
# - Use openstack CLI to list and delete test resources
|
||||
# - Clean up in order: instances, volumes, networks, security groups, etc.
|
||||
#
|
||||
# Exit with non-zero status only for unexpected errors.
|
||||
# Missing resources (already cleaned) should not cause failures.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Prefix used for test resources - should match what conformance tests create
|
||||
TEST_PREFIX="${TEST_PREFIX:-formae-plugin-sdk-test-}"
|
||||
|
||||
echo "clean-environment.sh: Cleaning resources with prefix '${TEST_PREFIX}'"
|
||||
echo ""
|
||||
echo "To implement cleanup for your provider, edit this script."
|
||||
echo "See comments in this file for examples."
|
||||
echo ""
|
||||
|
||||
# Uncomment and modify for your provider:
|
||||
#
|
||||
# # AWS - clean up S3 buckets with test prefix
|
||||
# echo "Cleaning S3 buckets..."
|
||||
# aws s3api list-buckets --query "Buckets[?starts_with(Name, '${TEST_PREFIX}')].Name" --output text | \
|
||||
# xargs -r -n1 aws s3 rb --force s3://
|
||||
#
|
||||
# # OpenStack - clean up instances
|
||||
# echo "Cleaning instances..."
|
||||
# openstack server list --name "^${TEST_PREFIX}" -f value -c ID | \
|
||||
# xargs -r -n1 openstack server delete --wait
|
||||
#
|
||||
# # OpenStack - clean up volumes
|
||||
# echo "Cleaning volumes..."
|
||||
# openstack volume list --name "^${TEST_PREFIX}" -f value -c ID | \
|
||||
# xargs -r -n1 openstack volume delete
|
||||
|
||||
echo "clean-environment.sh: Cleanup complete (no-op - not configured)"
|
||||
174
scripts/run-conformance-tests.sh
Executable file
174
scripts/run-conformance-tests.sh
Executable file
@@ -0,0 +1,174 @@
|
||||
#!/bin/bash
|
||||
# © 2025 Platform Engineering Labs Inc.
|
||||
# SPDX-License-Identifier: FSL-1.1-ALv2
|
||||
#
|
||||
# Script to run conformance tests against a specific version of formae.
|
||||
#
|
||||
# Usage:
|
||||
# ./scripts/run-conformance-tests.sh [VERSION]
|
||||
#
|
||||
# Arguments:
|
||||
# VERSION - Optional formae version (e.g., 0.76.0). Defaults to "latest".
|
||||
#
|
||||
# Environment variables:
|
||||
# FORMAE_BINARY - Path to formae binary (skips download if set)
|
||||
# FORMAE_INSTALL_PREFIX - Installation directory (default: temp directory)
|
||||
# FORMAE_TEST_FILTER - Filter tests by name pattern (e.g., "s3-bucket")
|
||||
# FORMAE_TEST_TYPE - Select test type: "all" (default), "crud", or "discovery"
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Cross-platform sed in-place edit (macOS vs Linux)
|
||||
sed_inplace() {
|
||||
if [[ "$(uname)" == "Darwin" ]]; then
|
||||
sed -i '' "$@"
|
||||
else
|
||||
sed -i "$@"
|
||||
fi
|
||||
}
|
||||
|
||||
VERSION="${1:-latest}"
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
||||
|
||||
# =============================================================================
|
||||
# Setup Formae Binary
|
||||
# =============================================================================
|
||||
|
||||
# Check if FORMAE_BINARY is already set and valid
|
||||
if [[ -n "${FORMAE_BINARY:-}" ]] && [[ -x "${FORMAE_BINARY}" ]]; then
|
||||
echo "Using FORMAE_BINARY from environment: ${FORMAE_BINARY}"
|
||||
# Extract version from binary if not explicitly provided
|
||||
if [[ "${VERSION}" == "latest" ]]; then
|
||||
VERSION=$("${FORMAE_BINARY}" --version 2>/dev/null | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1)
|
||||
if [[ -z "${VERSION}" ]]; then
|
||||
echo "Warning: Could not extract version from FORMAE_BINARY, using 'latest'"
|
||||
VERSION="latest"
|
||||
else
|
||||
echo "Detected formae version: ${VERSION}"
|
||||
fi
|
||||
fi
|
||||
else
|
||||
# Always download formae to temp directory for conformance tests
|
||||
# Don't use system-installed formae to ensure version consistency
|
||||
|
||||
INSTALL_DIR=$(mktemp -d -t formae-conformance-XXXXXX)
|
||||
echo "Using temp directory: ${INSTALL_DIR}"
|
||||
trap "rm -rf ${INSTALL_DIR}" EXIT
|
||||
|
||||
# Determine OS and architecture
|
||||
DETECTED_OS=$(uname | tr '[:upper:]' '[:lower:]')
|
||||
DETECTED_ARCH=$(uname -m | tr -d '_')
|
||||
|
||||
# Resolve version if "latest"
|
||||
if [[ "${VERSION}" == "latest" ]]; then
|
||||
echo "Resolving latest version..."
|
||||
VERSION=$(curl -s https://hub.platform.engineering/binaries/repo.json | \
|
||||
jq -r "[.Packages[] | select(.Version | index(\"-\") | not) | select(.OsArch.OS == \"${DETECTED_OS}\" and .OsArch.Arch == \"${DETECTED_ARCH}\")][0].Version")
|
||||
if [[ -z "${VERSION}" || "${VERSION}" == "null" ]]; then
|
||||
echo "Error: Could not determine latest version for ${DETECTED_OS}-${DETECTED_ARCH}"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "Downloading formae version ${VERSION}..."
|
||||
PKGNAME="formae@${VERSION}_${DETECTED_OS}-${DETECTED_ARCH}.tgz"
|
||||
DOWNLOAD_URL="https://hub.platform.engineering/binaries/pkgs/${PKGNAME}"
|
||||
|
||||
if ! curl -fsSL "${DOWNLOAD_URL}" -o "${INSTALL_DIR}/${PKGNAME}"; then
|
||||
echo "Error: Failed to download ${DOWNLOAD_URL}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Extract to install directory
|
||||
echo "Extracting..."
|
||||
tar -xzf "${INSTALL_DIR}/${PKGNAME}" -C "${INSTALL_DIR}"
|
||||
|
||||
# Find the formae binary
|
||||
FORMAE_BINARY="${INSTALL_DIR}/formae/bin/formae"
|
||||
if [[ ! -x "${FORMAE_BINARY}" ]]; then
|
||||
# Try alternative locations
|
||||
if [[ -x "${INSTALL_DIR}/bin/formae" ]]; then
|
||||
FORMAE_BINARY="${INSTALL_DIR}/bin/formae"
|
||||
elif [[ -x "${INSTALL_DIR}/formae" ]]; then
|
||||
FORMAE_BINARY="${INSTALL_DIR}/formae"
|
||||
else
|
||||
echo "Error: formae binary not found in ${INSTALL_DIR}"
|
||||
find "${INSTALL_DIR}" -name "formae" -type f 2>/dev/null || ls -laR "${INSTALL_DIR}"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "Using formae binary: ${FORMAE_BINARY}"
|
||||
"${FORMAE_BINARY}" --version
|
||||
|
||||
# Export environment variables for the tests
|
||||
# FORMAE_VERSION is required by the plugin SDK to resolve PKL schema paths
|
||||
export FORMAE_BINARY
|
||||
export FORMAE_VERSION="${VERSION}"
|
||||
|
||||
# Pass through test filter and type if set
|
||||
if [[ -n "${FORMAE_TEST_FILTER:-}" ]]; then
|
||||
export FORMAE_TEST_FILTER
|
||||
echo "Test filter: ${FORMAE_TEST_FILTER}"
|
||||
fi
|
||||
if [[ -n "${FORMAE_TEST_TYPE:-}" ]]; then
|
||||
export FORMAE_TEST_TYPE
|
||||
echo "Test type: ${FORMAE_TEST_TYPE}"
|
||||
fi
|
||||
|
||||
# =============================================================================
|
||||
# Update and Resolve PKL Dependencies
|
||||
# =============================================================================
|
||||
# Update testdata/PklProject with the resolved formae version, then resolve
|
||||
# dependencies from the public package registry.
|
||||
# =============================================================================
|
||||
|
||||
echo ""
|
||||
echo "Updating PKL dependencies for formae version ${VERSION}..."
|
||||
|
||||
# Update PklProject files with the resolved formae version
|
||||
if [[ "${VERSION}" != "latest" ]]; then
|
||||
# Update schema/pkl/PklProject (plugin schema depends on formae)
|
||||
if [[ -f "${PROJECT_ROOT}/schema/pkl/PklProject" ]]; then
|
||||
echo "Updating schema/pkl/PklProject to use formae@${VERSION}..."
|
||||
sed_inplace "s|formae/formae@[0-9a-zA-Z.\-]*\"|formae/formae@${VERSION}\"|g" "${PROJECT_ROOT}/schema/pkl/PklProject"
|
||||
fi
|
||||
|
||||
# Update testdata/PklProject (test files depend on formae)
|
||||
if [[ -f "${PROJECT_ROOT}/testdata/PklProject" ]]; then
|
||||
echo "Updating testdata/PklProject to use formae@${VERSION}..."
|
||||
sed_inplace "s|formae/formae@[0-9a-zA-Z.\-]*\"|formae/formae@${VERSION}\"|g" "${PROJECT_ROOT}/testdata/PklProject"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Resolve schema dependencies (if any)
|
||||
if [[ -f "${PROJECT_ROOT}/schema/pkl/PklProject" ]]; then
|
||||
echo "Resolving schema/pkl dependencies..."
|
||||
if ! pkl project resolve "${PROJECT_ROOT}/schema/pkl" 2>&1; then
|
||||
echo "Error: Failed to resolve schema/pkl dependencies"
|
||||
echo "Make sure the formae PKL package is accessible at the configured URL"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Resolve testdata dependencies
|
||||
if [[ -f "${PROJECT_ROOT}/testdata/PklProject" ]]; then
|
||||
echo "Resolving testdata dependencies..."
|
||||
if ! pkl project resolve "${PROJECT_ROOT}/testdata" 2>&1; then
|
||||
echo "Error: Failed to resolve testdata dependencies"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "PKL dependencies resolved successfully"
|
||||
|
||||
# =============================================================================
|
||||
# Run Conformance Tests
|
||||
# =============================================================================
|
||||
echo ""
|
||||
echo "Running conformance tests..."
|
||||
cd "${PROJECT_ROOT}"
|
||||
go test -tags=conformance -v -timeout 30m ./...
|
||||
12
testdata/PklProject
vendored
Normal file
12
testdata/PklProject
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
amends "pkl:Project"
|
||||
|
||||
dependencies {
|
||||
// Reference local plugin schema during development
|
||||
// IMPORTANT: The alias must match the package name from the schema's PklProject
|
||||
["proxmox"] = import("../schema/pkl/PklProject")
|
||||
|
||||
// Formae schema - fetched from public registry
|
||||
["formae"] {
|
||||
uri = "package://hub.platform.engineering/plugins/pkl/schema/pkl/formae/formae@0.80.0"
|
||||
}
|
||||
}
|
||||
50
testdata/resource-replace.pkl
vendored
Normal file
50
testdata/resource-replace.pkl
vendored
Normal file
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Conformance Test: Replace Resource
|
||||
*
|
||||
* This file modifies a createOnly field to trigger resource replacement.
|
||||
* Changes from resource.pkl:
|
||||
* - region: changed from "us-east-1" to "us-west-2"
|
||||
*
|
||||
* The region field has createOnly=true, so formae should delete the
|
||||
* existing resource and create a new one with the new region.
|
||||
*/
|
||||
amends "@formae/forma.pkl"
|
||||
import "@formae/formae.pkl"
|
||||
|
||||
import "@proxmox/proxmox.pkl"
|
||||
|
||||
local stackName = "plugin-sdk-test-stack"
|
||||
local testRunID = read("env:FORMAE_TEST_RUN_ID")
|
||||
|
||||
forma {
|
||||
new formae.Stack {
|
||||
label = stackName
|
||||
description = "Plugin SDK conformance test stack"
|
||||
}
|
||||
|
||||
new formae.Target {
|
||||
label = "example-target"
|
||||
namespace = "PROXMOX"
|
||||
config = new Mapping {
|
||||
["region"] = "us-west-2" // CHANGED to match resource
|
||||
}
|
||||
}
|
||||
|
||||
new example.ExampleResource {
|
||||
label = "plugin-sdk-test-resource"
|
||||
name = "formae-plugin-sdk-test-\(testRunID)"
|
||||
description = "Test resource for plugin SDK conformance tests"
|
||||
region = "us-west-2" // CHANGED - triggers replacement
|
||||
|
||||
endpoint = new example.Endpoint {
|
||||
url = "https://api.example.com"
|
||||
port = 8080
|
||||
protocol = "https"
|
||||
}
|
||||
|
||||
tags = new Listing {
|
||||
new example.Tag { key = "Environment"; value = "test" }
|
||||
new example.Tag { key = "ManagedBy"; value = "formae" }
|
||||
}
|
||||
}
|
||||
}
|
||||
54
testdata/resource-update.pkl
vendored
Normal file
54
testdata/resource-update.pkl
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Conformance Test: Update Resource (in-place)
|
||||
*
|
||||
* This file modifies mutable fields to trigger an in-place update.
|
||||
* Changes from resource.pkl:
|
||||
* - name: added "-updated" suffix
|
||||
* - description: changed text
|
||||
* - endpoint.port: changed from 8080 to 9090
|
||||
* - tags: added a new tag
|
||||
*
|
||||
* These fields do NOT have createOnly=true, so formae should update
|
||||
* the resource in place without replacement.
|
||||
*/
|
||||
amends "@formae/forma.pkl"
|
||||
import "@formae/formae.pkl"
|
||||
|
||||
import "@proxmox/proxmox.pkl"
|
||||
|
||||
local stackName = "plugin-sdk-test-stack"
|
||||
local testRunID = read("env:FORMAE_TEST_RUN_ID")
|
||||
|
||||
forma {
|
||||
new formae.Stack {
|
||||
label = stackName
|
||||
description = "Plugin SDK conformance test stack"
|
||||
}
|
||||
|
||||
new formae.Target {
|
||||
label = "example-target"
|
||||
namespace = "PROXMOX"
|
||||
config = new Mapping {
|
||||
["region"] = "us-east-1"
|
||||
}
|
||||
}
|
||||
|
||||
new example.ExampleResource {
|
||||
label = "plugin-sdk-test-resource"
|
||||
name = "formae-plugin-sdk-test-\(testRunID)-updated" // CHANGED
|
||||
description = "Test resource - UPDATED" // CHANGED
|
||||
region = "us-east-1" // unchanged (createOnly)
|
||||
|
||||
endpoint = new example.Endpoint {
|
||||
url = "https://api.example.com"
|
||||
port = 9090 // CHANGED from 8080
|
||||
protocol = "https"
|
||||
}
|
||||
|
||||
tags = new Listing {
|
||||
new example.Tag { key = "Environment"; value = "test" }
|
||||
new example.Tag { key = "ManagedBy"; value = "formae" }
|
||||
new example.Tag { key = "UpdatedAt"; value = "conformance-test" } // ADDED
|
||||
}
|
||||
}
|
||||
}
|
||||
49
testdata/resource.pkl
vendored
Normal file
49
testdata/resource.pkl
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Conformance Test: Create Resource
|
||||
*
|
||||
* This file defines the initial resource state for conformance testing.
|
||||
* The conformance test harness will apply this file first.
|
||||
*/
|
||||
amends "@formae/forma.pkl"
|
||||
import "@formae/formae.pkl"
|
||||
|
||||
import "@proxmox/proxmox.pkl"
|
||||
|
||||
local stackName = "plugin-sdk-test-stack"
|
||||
// Read the test run ID from environment variable set by the test harness
|
||||
// This ensures consistent naming within a test run but unique names between runs
|
||||
local testRunID = read("env:FORMAE_TEST_RUN_ID")
|
||||
|
||||
forma {
|
||||
new formae.Stack {
|
||||
label = stackName
|
||||
description = "Plugin SDK conformance test stack"
|
||||
}
|
||||
|
||||
new formae.Target {
|
||||
label = "example-target"
|
||||
namespace = "PROXMOX"
|
||||
// TODO: Add your provider-specific configuration
|
||||
config = new Mapping {
|
||||
["region"] = "us-east-1"
|
||||
}
|
||||
}
|
||||
|
||||
new example.ExampleResource {
|
||||
label = "plugin-sdk-test-resource"
|
||||
name = "formae-plugin-sdk-test-\(testRunID)"
|
||||
description = "Test resource for plugin SDK conformance tests"
|
||||
region = "us-east-1"
|
||||
|
||||
endpoint = new example.Endpoint {
|
||||
url = "https://api.example.com"
|
||||
port = 8080
|
||||
protocol = "https"
|
||||
}
|
||||
|
||||
tags = new Listing {
|
||||
new example.Tag { key = "Environment"; value = "test" }
|
||||
new example.Tag { key = "ManagedBy"; value = "formae" }
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user