03 - Target Configuration
This section explains how plugins receive target configuration.
What is a Target?
A target uniquely identifies a deployment location for your infrastructure. Think of it as "where" your resources live:
- For AWS: a region like
us-east-1 - For Kubernetes: a cluster endpoint
- For SFTP: a server URL like
sftp://files.example.com:22
Targets are stored in formae's database in plaintext, so they must never contain secrets. The target identifies the location; credentials are provided separately.
Target vs Credentials
| Target | Credentials | |
|---|---|---|
| Purpose | Identifies deployment location | Authenticates to the location |
| Storage | Plaintext in database | Environment variables / secrets |
| Example (SFTP) | sftp://files.example.com:22 |
Username, password |
| Example (AWS) | us-east-1 |
Access key, secret key |
Target Configuration in Requests
Every request to your plugin includes a TargetConfig field:
type CreateRequest struct {
ResourceType string
Properties json.RawMessage
TargetConfig json.RawMessage // <-- Target configuration (no secrets!)
}
Define the Target Config Type
For our SFTP plugin, the target is just the server URL. Add this to sftp.go:
// TargetConfig holds SFTP target settings.
// Contains only the deployment location, NOT credentials.
type TargetConfig struct {
URL string `json:"url"` // sftp://host:port
}
func parseTargetConfig(data json.RawMessage) (*TargetConfig, error) {
var cfg TargetConfig
if err := json.Unmarshal(data, &cfg); err != nil {
return nil, fmt.Errorf("invalid target config: %w", err)
}
if cfg.URL == "" {
return nil, fmt.Errorf("target config missing 'url'")
}
return &cfg, nil
}
func parseURL(sftpURL string) (host string, port string, err error) {
u, err := url.Parse(sftpURL)
if err != nil {
return "", "", fmt.Errorf("invalid URL: %w", err)
}
if u.Scheme != "sftp" {
return "", "", fmt.Errorf("expected sftp:// URL, got %s://", u.Scheme)
}
host = u.Hostname()
port = u.Port()
if port == "" {
port = "22"
}
return host, port, nil
}
Update sftp.go
Add the target configuration and credentials code. Update the imports and add the helper functions:
package main
import (
"context"
"encoding/json"
"errors"
"fmt"
"net/url"
"os"
"github.com/platform-engineering-labs/formae/pkg/plugin"
"github.com/platform-engineering-labs/formae/pkg/plugin/resource"
)
// TargetConfig holds SFTP target settings.
// Contains only the deployment location, NOT credentials.
type TargetConfig struct {
URL string `json:"url"` // sftp://host:port
}
func parseTargetConfig(data json.RawMessage) (*TargetConfig, error) {
var cfg TargetConfig
if err := json.Unmarshal(data, &cfg); err != nil {
return nil, fmt.Errorf("invalid target config: %w", err)
}
if cfg.URL == "" {
return nil, fmt.Errorf("target config missing 'url'")
}
return &cfg, nil
}
func parseURL(sftpURL string) (host string, port string, err error) {
u, err := url.Parse(sftpURL)
if err != nil {
return "", "", fmt.Errorf("invalid URL: %w", err)
}
if u.Scheme != "sftp" {
return "", "", fmt.Errorf("expected sftp:// URL, got %s://", u.Scheme)
}
host = u.Hostname()
port = u.Port()
if port == "" {
port = "22"
}
return host, port, nil
}
func getCredentials() (username, password string, err error) {
username = os.Getenv("SFTP_USERNAME")
password = os.Getenv("SFTP_PASSWORD")
if username == "" || password == "" {
return "", "", fmt.Errorf("SFTP_USERNAME and SFTP_PASSWORD must be set")
}
return username, password, nil
}
// ... rest of Plugin implementation
Config Field Mutability
You can annotate target config fields to tell formae which fields can be changed in place and which require a full target replace. Add @formae.ConfigFieldHint annotations to your PKL Config class:
import "@formae/formae.pkl"
open class Config {
hidden fixed type: String = "SFTP"
/// The server URL. Changing this requires recreating everything.
@formae.ConfigFieldHint { createOnly = true }
hidden url: String
fixed Type: String = type
fixed URL: String = url
}
createOnly = true— immutable, triggers target replace (default for unannotated fields)createOnly = false— mutable, allows in-place update
For our SFTP plugin, the URL is the only config field and it identifies the deployment location, so it should be immutable. For plugins with credential-like config fields (e.g. a named profile), marking those as createOnly = false lets users switch credentials without recreating resources.
See the schema reference for full details.
Verify
To verify that the target configuration code compiles correctly:
cd formae-plugin-sftp
make build