README
¶
Shoehorn Terraform Provider
Terraform provider for managing Shoehorn Intelligent Developer Platform resources as code.
Features
- Catalog Entities - Define services, libraries, APIs, and infrastructure as code with full metadata (links, relations, licenses, interfaces)
- Teams - Manage teams with members and role assignments
- Feature Flags - Toggle feature flags across environments
- Tenant Settings - Configure portal branding, appearance, hidden pages, and forge settings
- Platform Policies - Enforce organizational standards and governance
- API Keys - Provision API keys for service-to-service authentication
- User Roles - Assign RBAC roles to users
- Group Role Mappings - Map IdP groups to Cerbos roles so group members inherit permissions
- Integrations - Configure third-party integrations (GitHub, PagerDuty, etc.)
- Kubernetes Agents - Register K8s cluster agents for workload discovery
- Governance Actions - Track and manage governance action items with priority, SLA, and assignment
- Forge Molds - Define workflow templates as code with input schemas and actions
- Forge Approval Policies - Configure multi-step approval workflows for forge operations
- Marketplace Installations - Install and configure marketplace addons
Requirements
- Terraform >= 1.0
- Go >= 1.21 (to build the provider plugin)
- A running Shoehorn instance with an API key
Installation
From the Terraform Registry
terraform {
required_providers {
shoehorn = {
source = "shoehorn-dev/shoehorn"
version = "~> 0.1"
}
}
}
Local Development
# Clone the repository
git clone https://github.com/shoehorn-dev/terraform-provider-shoehorn.git
cd terraform-provider-shoehorn
# Build the provider
go build -o terraform-provider-shoehorn
# Install to local plugin directory
# Linux/macOS:
mkdir -p ~/.terraform.d/plugins/registry.terraform.io/shoehorn-dev/shoehorn/0.1.0/$(go env GOOS)_$(go env GOARCH)
cp terraform-provider-shoehorn ~/.terraform.d/plugins/registry.terraform.io/shoehorn-dev/shoehorn/0.1.0/$(go env GOOS)_$(go env GOARCH)/
# Windows: Use a dev_overrides block in your .terraformrc (see Development section below)
Authentication
The provider requires a Shoehorn API host and API key. These can be configured in three ways:
1. Provider Block
provider "shoehorn" {
host = "https://shoehorn.example.com"
api_key = var.shoehorn_api_key
}
2. Environment Variables (Recommended for CI/CD)
export SHOEHORN_HOST="https://shoehorn.example.com"
export SHOEHORN_API_KEY="shp_your_api_key_here"
provider "shoehorn" {}
3. Mixed
provider "shoehorn" {
host = "https://shoehorn.example.com"
# api_key read from SHOEHORN_API_KEY env var
}
Configuring the provider when no resources are managed
The provider validates host and api_key at init time, before any resource
operations. If you consume terraform-shoehorn-modules/modules/kubernetes
with deploy_agent = false, no shoehorn_* resources will be created.
Terraform still requires the provider block to be valid because the module
declares an agent resource (gated count = 0).
Pass stub values:
provider "shoehorn" {
host = "https://${var.domain}"
api_key = var.deploy_agent ? var.shoehorn_api_key : "stub-not-used"
}
Quick Start
terraform {
required_providers {
shoehorn = {
source = "shoehorn-dev/shoehorn"
version = "~> 0.1"
}
}
}
provider "shoehorn" {
host = "https://shoehorn.example.com"
api_key = var.shoehorn_api_key
}
variable "shoehorn_api_key" {
type = string
sensitive = true
}
# Create a team
resource "shoehorn_team" "platform" {
name = "Platform Engineering"
slug = "platform-engineering"
display_name = "Platform Engineering"
description = "Core platform team"
members = jsonencode([
{ user_id = "alice@example.com", role = "manager" },
{ user_id = "bob@example.com", role = "member" }
])
}
# Create a service entity
resource "shoehorn_entity" "api_gateway" {
name = "api-gateway"
type = "service"
description = "Central API gateway for all backend services"
entity_lifecycle = "production"
tier = "critical"
owner = shoehorn_team.platform.slug
tags = ["gateway", "go", "infrastructure"]
links = jsonencode([
{ name = "Repository", url = "https://github.com/org/api-gateway", icon = "github" },
{ name = "Dashboard", url = "https://grafana.internal/d/api-gw", icon = "grafana" },
{ name = "Runbook", url = "https://wiki.internal/api-gateway", icon = "docs" }
])
relations = jsonencode([
{ type = "depends_on", target = "service:auth-service" },
{ type = "calls", target = "service:user-service" }
])
}
Resources
shoehorn_entity
Manages a catalog entity (service, library, API, resource, etc.).
resource "shoehorn_entity" "payments" {
name = "payments-service"
type = "service"
description = "Payment processing microservice"
entity_lifecycle = "production"
tier = "critical"
owner = "platform-engineering"
tags = ["payments", "go", "grpc"]
links = jsonencode([
{ name = "Repository", url = "https://github.com/org/payments", icon = "github" },
{ name = "API Docs", url = "https://docs.internal/payments", icon = "api" }
])
relations = jsonencode([
{ type = "depends_on", target = "resource:postgres-primary" },
{ type = "calls", target = "service:notification-service" },
{ type = "reads_from", target = "resource:kafka-cluster" }
])
licenses = jsonencode([
{
title = "Stripe Enterprise"
vendor = "Stripe"
expires = "2026-12-31"
seats = 50
cost = "$25000/year"
}
])
interfaces = jsonencode({
http = {
openapi = "https://api.example.com/openapi.json"
baseUrl = "https://api.example.com/v1"
}
grpc = {
package = "payments.v1"
proto = "https://github.com/org/payments/proto/payments.proto"
}
})
changelog_path = "CHANGELOG.md"
}
Attributes
| Name | Type | Required | Description |
|---|---|---|---|
name |
String | Yes | Entity name (used as service ID). Forces replacement if changed. |
type |
String | Yes | Entity type: service, library, api, website, resource, system, component |
description |
String | No | Description of the entity |
entity_lifecycle |
String | No | Lifecycle stage: experimental, production, deprecated |
tier |
String | No | Service tier: critical, gold, silver, bronze, tier1-tier4 |
owner |
String | No | Owner team slug |
tags |
Set of String | No | Tags for categorization |
links |
JSON String | No | Array of {name, url, icon} objects. Icons: github, grafana, docs, api, slack, jira, etc. |
relations |
JSON String | No | Array of {type, target} objects. Types: depends_on, calls, reads_from, writes_to, owned_by |
licenses |
JSON String | No | Array of {title, vendor, expires, seats, cost} objects |
interfaces |
JSON String | No | Object with http and/or grpc interface definitions |
changelog_path |
String | No | Path to changelog file in repository |
Computed: id, repository_path, created_at, updated_at
shoehorn_team
Manages a team with optional member assignments.
resource "shoehorn_team" "backend" {
name = "Backend Engineering"
slug = "backend-engineering"
display_name = "Backend Engineering"
description = "Backend services team"
members = jsonencode([
{ user_id = "alice@example.com", role = "manager" },
{ user_id = "bob@example.com", role = "admin" },
{ user_id = "carol@example.com", role = "member" }
])
}
| Name | Type | Required | Description |
|---|---|---|---|
name |
String | Yes | Team name |
slug |
String | Yes | Unique slug (forces replacement if changed) |
display_name |
String | No | Display name |
description |
String | No | Team description |
members |
JSON String | No | Array of {user_id, role} objects. Roles: manager, admin, member |
metadata |
JSON String | No | Arbitrary JSON metadata |
Computed: id, is_active, member_count, created_at, updated_at
shoehorn_tenant_settings
Manages portal branding and appearance (singleton per tenant).
resource "shoehorn_tenant_settings" "main" {
platform_name = "Acme Developer Portal"
platform_description = "Internal developer portal for Acme Corp"
company_name = "Acme Corp"
primary_color = "#3b82f6"
default_theme = "system"
hidden_pages = ["forge", "insights"]
forge {
allowed_orgs = ["my-org", "my-other-org"]
default_org = "my-org"
}
}
shoehorn_platform_policy
Configures platform governance policies. Policies are pre-seeded and cannot be created or destroyed - Terraform only manages their enabled and enforcement state.
resource "shoehorn_platform_policy" "require_docs" {
key = "required-entity-docs"
enabled = true
enforcement = "warning"
}
shoehorn_feature_flag
Manages feature flags.
resource "shoehorn_feature_flag" "dark_mode" {
key = "enable-dark-mode"
name = "Dark Mode"
description = "Enable dark mode for the platform UI"
default_enabled = true
}
shoehorn_api_key
Provisions API keys for service-to-service authentication.
resource "shoehorn_api_key" "ci" {
name = "CI Pipeline Key"
expires_in = "90d"
scopes = ["entities:read", "catalog:read"]
}
output "api_key" {
value = shoehorn_api_key.ci.raw_key
sensitive = true
}
shoehorn_integration
Configures third-party integrations.
resource "shoehorn_integration" "github" {
name = "GitHub Production"
type = "github"
config_json = jsonencode({
token = var.github_token
organization = "acme-corp"
})
}
shoehorn_k8s_agent
Registers Kubernetes cluster agents.
resource "shoehorn_k8s_agent" "production" {
name = "production-cluster"
cluster_id = "prod-us-east-1"
}
output "agent_token" {
value = shoehorn_k8s_agent.production.token
sensitive = true
}
shoehorn_user_role
Assigns RBAC roles to users.
resource "shoehorn_user_role" "admin" {
user_id = "user-abc-123"
role = "admin"
}
shoehorn_group_role_mapping
Maps an IdP group to a Cerbos role so all members of the group inherit that role.
resource "shoehorn_group_role_mapping" "platform_editors" {
group_name = "team-developer-platform"
role_name = "entity:editor"
description = "Grant entity editor access to the developer platform group"
}
| Name | Type | Required | Description |
|---|---|---|---|
group_name |
String | Yes | The IdP group name. Changing this forces replacement. |
role_name |
String | Yes | The Cerbos role name (e.g., entity:editor, workflow:executor, tenant:admin). Changing this forces replacement. |
auth_provider |
String | No | The auth provider identifier. Defaults to default. |
description |
String | No | Optional description for the mapping. |
Computed: id (format: group_name:role_name)
shoehorn_governance_action
Manages a governance action item for tracking remediation and compliance work.
resource "shoehorn_governance_action" "update_docs" {
entity_id = "service:my-service"
title = "Update API documentation"
priority = "high"
source_type = "policy"
description = "API docs are outdated and need to reflect the v2 endpoints"
assigned_to = "user-123"
sla_days = 14
}
| Name | Type | Required | Description |
|---|---|---|---|
entity_id |
String | Yes | The entity this action applies to (e.g., service:my-service) |
title |
String | Yes | Action item title |
priority |
String | Yes | Priority level: critical, high, medium, low |
source_type |
String | Yes | Origin type: scorecard, security, policy |
description |
String | No | Detailed description of the action |
source_id |
String | No | Identifier of the originating source |
assigned_to |
String | No | User ID of the assignee |
sla_days |
Number | No | Number of days allowed to resolve the action |
Computed: id, status, due_date, created_at, updated_at
Import: terraform import shoehorn_governance_action.example <id>
shoehorn_forge_mold
Defines a forge workflow template with input schemas and actions.
resource "shoehorn_forge_mold" "new_service" {
slug = "create-microservice"
name = "Create Microservice"
description = "Scaffold a new Go microservice with CI/CD pipeline"
version = "1.0.0"
visibility = "tenant"
category = "scaffolding"
tags = ["go", "microservice", "cicd"]
schema_json = jsonencode({
properties = {
service_name = { type = "string", description = "Service name" }
team = { type = "string", description = "Owning team" }
}
required = ["service_name", "team"]
})
actions {
action = "create-repo"
label = "Create Repository"
primary = true
}
}
| Name | Type | Required | Description |
|---|---|---|---|
slug |
String | Yes | Unique slug identifier. Forces replacement if changed. |
name |
String | No | Display name for the mold |
description |
String | No | Description of what the mold does |
version |
String | No | Semantic version string |
visibility |
String | No | Visibility scope (e.g., tenant) |
category |
String | No | Category for organization (e.g., scaffolding) |
schema_json |
JSON String | No | JSON Schema defining the mold's input parameters |
defaults_json |
JSON String | No | Default values for schema properties |
actions |
Block List | No | Action blocks with action, label, and primary attributes |
tags |
Set of String | No | Tags for categorization |
icon |
String | No | Icon identifier |
Computed: id, published, created_at, updated_at
Import by slug: terraform import shoehorn_forge_mold.example <slug>
shoehorn_forge_approval_policy
Configures a multi-step approval workflow for forge operations.
resource "shoehorn_forge_approval_policy" "production_deploy" {
name = "Production Deployment Approval"
description = "Requires team lead and SRE approval for production deployments"
enabled = true
steps {
step = 1
name = "Team Lead Review"
approvers = ["lead@example.com"]
require_all = true
}
steps {
step = 2
name = "SRE Approval"
approvers = ["sre-oncall@example.com", "sre-lead@example.com"]
require_all = false
}
}
| Name | Type | Required | Description |
|---|---|---|---|
name |
String | Yes | Policy name |
description |
String | No | Description of the approval policy |
enabled |
Boolean | No | Whether the policy is active |
steps |
Block List | No | Approval steps, each with step (order), name, approvers (list of emails), and require_all (boolean) |
Computed: id, created_at, updated_at
Import: terraform import shoehorn_forge_approval_policy.example <id>
shoehorn_marketplace_installation
Installs and configures a marketplace addon.
resource "shoehorn_marketplace_installation" "cost_tracker" {
slug = "cost-tracker"
enabled = true
config_json = jsonencode({
refresh_interval = "1h"
currency = "USD"
})
}
| Name | Type | Required | Description |
|---|---|---|---|
slug |
String | Yes | Marketplace addon slug. Forces replacement if changed. |
enabled |
Boolean | No | Whether the installation is active |
config_json |
JSON String | No | Addon-specific configuration as JSON |
Computed: id, created_at, updated_at
Import by slug: terraform import shoehorn_marketplace_installation.example <slug>
Data Sources
All resources have corresponding data sources for reading existing state:
# List all entities
data "shoehorn_entities" "all" {}
# List all teams
data "shoehorn_teams" "all" {}
# List all feature flags
data "shoehorn_feature_flags" "all" {}
# List all integrations
data "shoehorn_integrations" "all" {}
# List all API keys
data "shoehorn_api_keys" "all" {}
# List all K8s agents
data "shoehorn_k8s_agents" "all" {}
# List all platform policies
data "shoehorn_platform_policies" "all" {}
# List all IdP directory users
data "shoehorn_users" "all" {}
# List all IdP groups with role mappings
data "shoehorn_groups" "all" {}
# List governance actions (filterable by priority and status)
data "shoehorn_governance_actions" "critical" {
priority = "critical"
status = "open"
}
# List all forge molds
data "shoehorn_forge_molds" "all" {}
# List marketplace items (filterable by kind)
data "shoehorn_marketplace_items" "addons" {
kind = "addon"
}
# List gitops resources (filterable by cluster and tool)
data "shoehorn_gitops_resources" "production" {
cluster_id = "prod-us-east-1"
tool = "argocd"
}
Importing Existing Resources
Resources can be imported into Terraform state:
# Import an entity by name
terraform import shoehorn_entity.api_gateway api-gateway
# Import a team by slug
terraform import shoehorn_team.platform platform-engineering
# Import tenant settings (singleton, use any ID)
terraform import shoehorn_tenant_settings.main singleton
# Import a platform policy by key
terraform import shoehorn_platform_policy.require_docs required-entity-docs
# Import a group role mapping (format: group_name:role_name)
terraform import shoehorn_group_role_mapping.example "team-developer-platform:entity:editor"
# Import a governance action by ID
terraform import shoehorn_governance_action.example action-abc-123
# Import a forge mold by slug
terraform import shoehorn_forge_mold.example create-microservice
# Import a forge approval policy by ID
terraform import shoehorn_forge_approval_policy.example policy-abc-123
# Import a marketplace installation by slug
terraform import shoehorn_marketplace_installation.example cost-tracker
Development
Building
go build -o terraform-provider-shoehorn
Testing
# Run all unit tests
go test ./...
# Run with verbose output
go test -v ./...
# Run with race detector
go test -race ./...
Local Testing with dev_overrides
Create or update your Terraform CLI configuration file:
Linux/macOS (~/.terraformrc):
provider_installation {
dev_overrides {
"shoehorn-dev/shoehorn" = "/path/to/terraform-provider-shoehorn"
}
direct {}
}
Windows (%APPDATA%\terraform.rc):
provider_installation {
dev_overrides {
"shoehorn-dev/shoehorn" = "C:\\path\\to\\terraform-provider-shoehorn"
}
direct {}
}
Then build and test:
go build -o terraform-provider-shoehorn
cd test-local
terraform plan
terraform apply
Generating Documentation
Documentation is generated with tfplugindocs:
go generate ./...
Support
- Bug reports and feature requests: GitHub Issues
- Security vulnerabilities: See SECURITY.md
- Documentation: Terraform Registry
License
This project is licensed under the MIT License - see the LICENSE file for details.
Documentation
¶
There is no documentation for this package.