chore: update compatibility pages

This commit is contained in:
Julian Sommer
2026-09-14 11:49:46 +10:00
parent fb6494da01
commit 29faaa78fd
17 changed files with 121 additions and 201 deletions
+4 -1
View File
@@ -84,7 +84,10 @@ func writePages(outputDir string, coverages []awsmodel.OperationCoverage) error
}
for _, coverage := range coverages {
page := pages.Services[coverage.Service]
page, ok := pages.Services[coverage.Service]
if !ok {
continue
}
intro, err := readIntro(outputDir, page.Slug)
if err != nil {
return err
@@ -30,14 +30,6 @@ Terraform's canonical certificate, DNS record and validation flow works unmodifi
### Operations
| Status | Meaning |
|---|---|
| ✅ Implemented | A modelled operation bound to a real handler. |
| 🟡 Stub | A registered handler that answers with a fixed or empty result. |
| 🚫 Not supported | A registered handler that deliberately refuses, so a client sees "not offered" rather than an unknown action. |
| ❌ Not implemented | Modelled by AWS, not registered by Spinifex. |
| 🔒 Outside the pinned model | Registered by Spinifex but absent from the pinned model — an internal route, not a tenant-callable AWS action. |
| Operation | Status |
|---|---|
| `AddTagsToCertificate` | ✅ Implemented |
@@ -29,17 +29,4 @@ Spinifex serves the AWS APIs below. Every page counts the operations in the pinn
| [ELBv2](/docs/elbv2-api-coverage) | 2015-12-01 | 33 | 46 | 71.7% |
| [IAM](/docs/iam-api-coverage) | 2010-05-08 | 75 | 159 | 47.2% |
| [RDS](/docs/rds-api-coverage) | 2014-10-31 | 26 | 162 | 16.0% |
| [S3](/docs/s3-api-coverage) | 2006-03-01 | — | 99 | — |
| [STS](/docs/sts-api-coverage) | 2011-06-15 | 4 | 8 | 50.0% |
| Status | Meaning |
|---|---|
| ✅ Implemented | A modelled operation bound to a real handler. |
| 🟡 Stub | A registered handler that answers with a fixed or empty result. |
| 🚫 Not supported | A registered handler that deliberately refuses, so a client sees "not offered" rather than an unknown action. |
| ❌ Not implemented | Modelled by AWS, not registered by Spinifex. |
| 🔒 Outside the pinned model | Registered by Spinifex but absent from the pinned model — an internal route, not a tenant-callable AWS action. |
### What "implemented" means here
An operation is implemented when the gateway binds it to a real handler. These pages are generated from those dispatch tables on every build, so they cannot go stale against the code. That is a mechanical fact, and it is the only claim they make. It does not say the handler honours every parameter AWS models, and no page reports parameter or field-level detail: a handler receives a fully-populated typed SDK struct, so nothing distinguishes a field it honours from one it ignores. Behavioural conformance is measured separately by the integration conformance suite.
@@ -1,3 +0,0 @@
### What "implemented" means here
An operation is implemented when the gateway binds it to a real handler. These pages are generated from those dispatch tables on every build, so they cannot go stale against the code. That is a mechanical fact, and it is the only claim they make. It does not say the handler honours every parameter AWS models, and no page reports parameter or field-level detail: a handler receives a fully-populated typed SDK struct, so nothing distinguishes a field it honours from one it ignores. Behavioural conformance is measured separately by the integration conformance suite.
@@ -28,14 +28,6 @@ Spot Instance Requests are a mock over the on-demand `RunInstances` path. A requ
### Operations
| Status | Meaning |
|---|---|
| ✅ Implemented | A modelled operation bound to a real handler. |
| 🟡 Stub | A registered handler that answers with a fixed or empty result. |
| 🚫 Not supported | A registered handler that deliberately refuses, so a client sees "not offered" rather than an unknown action. |
| ❌ Not implemented | Modelled by AWS, not registered by Spinifex. |
| 🔒 Outside the pinned model | Registered by Spinifex but absent from the pinned model — an internal route, not a tenant-callable AWS action. |
| Operation | Status |
|---|---|
| `AcceptAddressTransfer` | ❌ Not implemented |
@@ -30,14 +30,6 @@ Vulnerability scanning is not offered, so the image-scanning operations refuse d
### Operations
| Status | Meaning |
|---|---|
| ✅ Implemented | A modelled operation bound to a real handler. |
| 🟡 Stub | A registered handler that answers with a fixed or empty result. |
| 🚫 Not supported | A registered handler that deliberately refuses, so a client sees "not offered" rather than an unknown action. |
| ❌ Not implemented | Modelled by AWS, not registered by Spinifex. |
| 🔒 Outside the pinned model | Registered by Spinifex but absent from the pinned model — an internal route, not a tenant-callable AWS action. |
| Operation | Status |
|---|---|
| `BatchCheckLayerAvailability` | 🟡 Stub |
@@ -26,14 +26,6 @@ Clusters, services and tasks run on EC2 container instances. There is no Fargate
### Operations
| Status | Meaning |
|---|---|
| ✅ Implemented | A modelled operation bound to a real handler. |
| 🟡 Stub | A registered handler that answers with a fixed or empty result. |
| 🚫 Not supported | A registered handler that deliberately refuses, so a client sees "not offered" rather than an unknown action. |
| ❌ Not implemented | Modelled by AWS, not registered by Spinifex. |
| 🔒 Outside the pinned model | Registered by Spinifex but absent from the pinned model — an internal route, not a tenant-callable AWS action. |
| Operation | Status |
|---|---|
| `CreateCapacityProvider` | ✅ Implemented |
@@ -37,14 +37,6 @@ None is callable with an ordinary tenant credential. Each names the target accou
### Operations
| Status | Meaning |
|---|---|
| ✅ Implemented | A modelled operation bound to a real handler. |
| 🟡 Stub | A registered handler that answers with a fixed or empty result. |
| 🚫 Not supported | A registered handler that deliberately refuses, so a client sees "not offered" rather than an unknown action. |
| ❌ Not implemented | Modelled by AWS, not registered by Spinifex. |
| 🔒 Outside the pinned model | Registered by Spinifex but absent from the pinned model — an internal route, not a tenant-callable AWS action. |
| Operation | Status |
|---|---|
| `AssociateAccessPolicy` | ✅ Implemented |
@@ -26,14 +26,6 @@ The data plane is a system-managed load balancer VM, launched automatically duri
### Operations
| Status | Meaning |
|---|---|
| ✅ Implemented | A modelled operation bound to a real handler. |
| 🟡 Stub | A registered handler that answers with a fixed or empty result. |
| 🚫 Not supported | A registered handler that deliberately refuses, so a client sees "not offered" rather than an unknown action. |
| ❌ Not implemented | Modelled by AWS, not registered by Spinifex. |
| 🔒 Outside the pinned model | Registered by Spinifex but absent from the pinned model — an internal route, not a tenant-callable AWS action. |
| Operation | Status |
|---|---|
| `AddListenerCertificates` | ✅ Implemented |
@@ -26,14 +26,6 @@ All IAM operations are account-scoped. The root user of an account bypasses poli
### Operations
| Status | Meaning |
|---|---|
| ✅ Implemented | A modelled operation bound to a real handler. |
| 🟡 Stub | A registered handler that answers with a fixed or empty result. |
| 🚫 Not supported | A registered handler that deliberately refuses, so a client sees "not offered" rather than an unknown action. |
| ❌ Not implemented | Modelled by AWS, not registered by Spinifex. |
| 🔒 Outside the pinned model | Registered by Spinifex but absent from the pinned model — an internal route, not a tenant-callable AWS action. |
| Operation | Status |
|---|---|
| `AddClientIDToOpenIDConnectProvider` | ❌ Not implemented |
+80 -18
View File
@@ -6,7 +6,13 @@
"title": "AWS API Coverage",
"seoTitle": "Which AWS API Operations Spinifex Implements — Spinifex Docs",
"description": "Operation-level coverage of every AWS API Spinifex serves, generated from its gateway dispatch tables and the pinned AWS SDK service models on each build.",
"tags": ["aws", "compatibility", "coverage", "api", "operations"]
"tags": [
"aws",
"compatibility",
"coverage",
"api",
"operations"
]
},
"services": {
"acm": {
@@ -15,7 +21,14 @@
"title": "ACM API Coverage",
"seoTitle": "AWS Certificate Manager API Coverage — Spinifex Docs",
"description": "Every operation in the AWS Certificate Manager API model and whether Spinifex implements it, generated from the gateway dispatch tables on each build.",
"tags": ["aws", "compatibility", "coverage", "acm", "certificates", "tls"]
"tags": [
"aws",
"compatibility",
"coverage",
"acm",
"certificates",
"tls"
]
},
"ec2": {
"slug": "ec2-api-coverage",
@@ -23,7 +36,14 @@
"title": "EC2 API Coverage",
"seoTitle": "Amazon EC2 API Coverage on Spinifex — Spinifex Docs",
"description": "Every operation in the Amazon EC2 API model and whether Spinifex implements it, covering instances, EBS volumes, VPC networking, tags and security groups.",
"tags": ["aws", "compatibility", "coverage", "ec2", "compute", "vpc"]
"tags": [
"aws",
"compatibility",
"coverage",
"ec2",
"compute",
"vpc"
]
},
"ecr": {
"slug": "ecr-api-coverage",
@@ -31,7 +51,14 @@
"title": "ECR API Coverage",
"seoTitle": "Elastic Container Registry API Coverage — Spinifex Docs",
"description": "Every operation in the Amazon ECR API model and whether Spinifex implements it, alongside the OCI distribution endpoint that carries the image layers.",
"tags": ["aws", "compatibility", "coverage", "ecr", "containers", "registry"]
"tags": [
"aws",
"compatibility",
"coverage",
"ecr",
"containers",
"registry"
]
},
"ecs": {
"slug": "ecs-api-coverage",
@@ -39,7 +66,14 @@
"title": "ECS API Coverage",
"seoTitle": "Elastic Container Service API Coverage — Spinifex Docs",
"description": "Every operation in the Amazon ECS API model and whether Spinifex implements it, covering clusters, services, tasks and container instances on each build.",
"tags": ["aws", "compatibility", "coverage", "ecs", "containers", "orchestration"]
"tags": [
"aws",
"compatibility",
"coverage",
"ecs",
"containers",
"orchestration"
]
},
"eks": {
"slug": "eks-api-coverage",
@@ -47,7 +81,14 @@
"title": "EKS API Coverage",
"seoTitle": "Elastic Kubernetes Service API Coverage — Spinifex Docs",
"description": "Every operation in the Amazon EKS API model and whether Spinifex implements it, covering clusters, nodegroups, add-ons and access entries on each build.",
"tags": ["aws", "compatibility", "coverage", "eks", "kubernetes", "containers"]
"tags": [
"aws",
"compatibility",
"coverage",
"eks",
"kubernetes",
"containers"
]
},
"elasticloadbalancingv2": {
"slug": "elbv2-api-coverage",
@@ -55,7 +96,14 @@
"title": "ELBv2 API Coverage",
"seoTitle": "Elastic Load Balancing v2 API Coverage — Spinifex Docs",
"description": "Every operation in the Elastic Load Balancing v2 API model and whether Spinifex implements it, for both the Application and Network Load Balancers it serves.",
"tags": ["aws", "compatibility", "coverage", "elbv2", "load balancing", "networking"]
"tags": [
"aws",
"compatibility",
"coverage",
"elbv2",
"load balancing",
"networking"
]
},
"iam": {
"slug": "iam-api-coverage",
@@ -63,7 +111,14 @@
"title": "IAM API Coverage",
"seoTitle": "AWS IAM API Operation Coverage on Spinifex — Spinifex Docs",
"description": "Every operation in the AWS IAM API model and whether Spinifex implements it, covering users, roles, policies, groups and instance profiles on each build.",
"tags": ["aws", "compatibility", "coverage", "iam", "identity", "policies"]
"tags": [
"aws",
"compatibility",
"coverage",
"iam",
"identity",
"policies"
]
},
"rds": {
"slug": "rds-api-coverage",
@@ -71,15 +126,15 @@
"title": "RDS API Coverage",
"seoTitle": "Relational Database Service API Coverage — Spinifex Docs",
"description": "Every operation in the Amazon RDS API model and whether Spinifex implements it, for the managed PostgreSQL and MariaDB engines it offers on each build.",
"tags": ["aws", "compatibility", "coverage", "rds", "databases", "postgresql", "mariadb"]
},
"s3": {
"slug": "s3-api-coverage",
"name": "S3",
"title": "S3 API Coverage",
"seoTitle": "Amazon S3 API Compatibility on Spinifex — Spinifex Docs",
"description": "How Spinifex serves the Amazon S3 API through Predastore, and why its coverage is measured there rather than derived from a gateway dispatch table here.",
"tags": ["aws", "compatibility", "coverage", "s3", "storage", "predastore"]
"tags": [
"aws",
"compatibility",
"coverage",
"rds",
"databases",
"postgresql",
"mariadb"
]
},
"sts": {
"slug": "sts-api-coverage",
@@ -87,7 +142,14 @@
"title": "STS API Coverage",
"seoTitle": "AWS Security Token Service API Coverage — Spinifex Docs",
"description": "Every operation in the AWS STS API model and whether Spinifex implements it, covering role assumption, session tokens and web identity federation (IRSA).",
"tags": ["aws", "compatibility", "coverage", "sts", "identity", "credentials"]
"tags": [
"aws",
"compatibility",
"coverage",
"sts",
"identity",
"credentials"
]
}
}
}
@@ -45,14 +45,6 @@ A parameter whose omission would create a false safety, security or availability
### Operations
| Status | Meaning |
|---|---|
| ✅ Implemented | A modelled operation bound to a real handler. |
| 🟡 Stub | A registered handler that answers with a fixed or empty result. |
| 🚫 Not supported | A registered handler that deliberately refuses, so a client sees "not offered" rather than an unknown action. |
| ❌ Not implemented | Modelled by AWS, not registered by Spinifex. |
| 🔒 Outside the pinned model | Registered by Spinifex but absent from the pinned model — an internal route, not a tenant-callable AWS action. |
| Operation | Status |
|---|---|
| `AddRoleToDBCluster` | ❌ Not implemented |
@@ -1,29 +0,0 @@
---
title: "S3 API Coverage"
seoTitle: "Amazon S3 API Compatibility on Spinifex — Spinifex Docs"
description: "How Spinifex serves the Amazon S3 API through Predastore, and why its coverage is measured there rather than derived from a gateway dispatch table here."
category: "Coverage"
sections:
- overview
tags:
- aws
- compatibility
- coverage
- s3
- storage
- predastore
---
# S3 API Coverage
## Overview
The S3 API model pins **99** operations at API version `2006-03-01`, but Spinifex's coverage of them is not mechanically enumerable: Spinifex delegates the S3 REST surface to Predastore, which has no operation-name dispatch table to compare mechanically.
### Where S3 is served
Spinifex does not dispatch S3 operations. The REST surface is delegated whole to **Predastore**, which serves it directly, so there is no operation-name dispatch table in the gateway to compare against the model — the comparison that produces every other page on this site has nothing to read here.
That is why this page reports no percentage. Claiming one from an empty dispatch table would read as 0% implemented, which is the opposite of the truth: S3 is one of the most complete surfaces in the platform.
Predastore publishes its own measured compatibility. Consult it for the operations, headers and behaviours it supports.
@@ -1,7 +0,0 @@
### Where S3 is served
Spinifex does not dispatch S3 operations. The REST surface is delegated whole to **Predastore**, which serves it directly, so there is no operation-name dispatch table in the gateway to compare against the model — the comparison that produces every other page on this site has nothing to read here.
That is why this page reports no percentage. Claiming one from an empty dispatch table would read as 0% implemented, which is the opposite of the truth: S3 is one of the most complete surfaces in the platform.
Predastore publishes its own measured compatibility. Consult it for the operations, headers and behaviours it supports.
@@ -32,14 +32,6 @@ Several inputs the model describes are deliberately rejected rather than accepte
### Operations
| Status | Meaning |
|---|---|
| ✅ Implemented | A modelled operation bound to a real handler. |
| 🟡 Stub | A registered handler that answers with a fixed or empty result. |
| 🚫 Not supported | A registered handler that deliberately refuses, so a client sees "not offered" rather than an unknown action. |
| ❌ Not implemented | Modelled by AWS, not registered by Spinifex. |
| 🔒 Outside the pinned model | Registered by Spinifex but absent from the pinned model — an internal route, not a tenant-callable AWS action. |
| Operation | Status |
|---|---|
| `AssumeRole` | ✅ Implemented |
+11 -38
View File
@@ -108,10 +108,12 @@ func ParsePageSet(contents []byte) (PageSet, error) {
if err := validatePage("index", pages.Index, slugs); err != nil {
return PageSet{}, err
}
// A known service may be left out. S3 is: Predastore serves that surface,
// so the gateway dispatch tables hold no honest number to publish for it.
for _, service := range Services() {
page, ok := pages.Services[service]
if !ok {
return PageSet{}, fmt.Errorf("awsmodel: coverage pages: no entry for service %q", service)
continue
}
if err := validatePage(string(service), page, slugs); err != nil {
return PageSet{}, err
@@ -165,23 +167,24 @@ func RenderServicePage(coverage OperationCoverage, pages PageSet, intro string)
if !ok {
return "", fmt.Errorf("awsmodel: coverage pages: no entry for service %q", coverage.Service)
}
// An opaque service has no dispatch table to count, so there is no honest
// number to put on a page. It is left out until a real one exists.
if coverage.Opaque {
return "", fmt.Errorf("awsmodel: %s coverage is not enumerable, so it has no publishable page", coverage.Service)
}
var body strings.Builder
writeFrontmatter(&body, page, pages.Category)
fmt.Fprintf(&body, "# %s\n\n## Overview\n\n", page.Title)
writeSummary(&body, coverage, page)
fmt.Fprintf(&body, "Spinifex implements **%d of the %d** operations in the %s `%s` API model, as pinned in `aws-sdk-go %s` — **%.1f%%**.\n\n",
len(coverage.Implemented), len(coverage.Modelled), page.Name, coverage.APIVersion, SourceSDKVersion, coverage.ImplementedPercent())
if intro != "" {
body.WriteString(strings.TrimSpace(intro) + "\n\n")
}
if coverage.Opaque {
return strings.TrimRight(body.String(), "\n") + "\n", nil
}
body.WriteString("### Operations\n\n")
writeStatusLegend(&body)
body.WriteString("\n| Operation | Status |\n|---|---|\n")
body.WriteString("### Operations\n\n| Operation | Status |\n|---|---|\n")
for _, status := range coverage.OperationStatuses() {
fmt.Fprintf(&body, "| `%s` | %s |\n", status.Operation, status.Status)
}
@@ -202,48 +205,18 @@ func RenderIndexPage(coverages []OperationCoverage, pages PageSet, intro string)
for _, coverage := range sortedCoverages(coverages) {
page, ok := pages.Services[coverage.Service]
if !ok {
return "", fmt.Errorf("awsmodel: coverage pages: no entry for service %q", coverage.Service)
}
if coverage.Opaque {
fmt.Fprintf(&body, "| [%s](/docs/%s) | %s | — | %d | — |\n", page.Name, page.Slug, coverage.APIVersion, len(coverage.Modelled))
continue
}
fmt.Fprintf(&body, "| [%s](/docs/%s) | %s | %d | %d | %.1f%% |\n",
page.Name, page.Slug, coverage.APIVersion, len(coverage.Implemented), len(coverage.Modelled), coverage.ImplementedPercent())
}
body.WriteString("\n")
writeStatusLegend(&body)
if intro != "" {
body.WriteString("\n" + strings.TrimSpace(intro) + "\n")
}
return body.String(), nil
}
// writeSummary states the implemented-of-modelled count, or why there is none.
func writeSummary(body *strings.Builder, coverage OperationCoverage, page PageMetadata) {
if coverage.Opaque {
fmt.Fprintf(body, "The %s API model pins **%d** operations at API version `%s`, but Spinifex's coverage of them is not mechanically enumerable: %s\n\n",
page.Name, len(coverage.Modelled), coverage.APIVersion, coverage.Note)
return
}
fmt.Fprintf(body, "Spinifex implements **%d of the %d** operations in the %s `%s` API model, as pinned in `aws-sdk-go %s` — **%.1f%%**.\n\n",
len(coverage.Implemented), len(coverage.Modelled), page.Name, coverage.APIVersion, SourceSDKVersion, coverage.ImplementedPercent())
}
func writeStatusLegend(body *strings.Builder) {
body.WriteString("| Status | Meaning |\n|---|---|\n")
for _, entry := range [][2]string{
{StatusImplemented, "A modelled operation bound to a real handler."},
{StatusStub, "A registered handler that answers with a fixed or empty result."},
{StatusNotSupported, "A registered handler that deliberately refuses, so a client sees \"not offered\" rather than an unknown action."},
{StatusNotImplemented, "Modelled by AWS, not registered by Spinifex."},
{StatusOutsideModel, "Registered by Spinifex but absent from the pinned model — an internal route, not a tenant-callable AWS action."},
} {
fmt.Fprintf(body, "| %s | %s |\n", entry[0], entry[1])
}
}
func writeFrontmatter(body *strings.Builder, page PageMetadata, category string) {
body.WriteString("---\n")
fmt.Fprintf(body, "title: %s\n", strconv.Quote(page.Title))
+26 -20
View File
@@ -1,10 +1,12 @@
package awsmodel
package awsmodel_test
import (
"os"
"path/filepath"
"strings"
"testing"
. "github.com/mulgadc/spinifex/internal/awsmodel"
)
func testPageSet(t *testing.T) PageSet {
@@ -23,10 +25,13 @@ func testPageSet(t *testing.T) PageSet {
func TestParsePageSetAcceptsTheCheckedInFile(t *testing.T) {
pages := testPageSet(t)
for _, service := range Services() {
if _, ok := pages.Services[service]; !ok {
t.Errorf("no page metadata for service %q", service)
}
if len(pages.Services) == 0 {
t.Fatal("no service pages are configured")
}
// S3 is deliberately unpublished: Predastore serves that surface, so the
// gateway dispatch tables hold no honest number for it.
if _, ok := pages.Services[S3]; ok {
t.Error("S3 has page metadata but no enumerable coverage to publish")
}
}
@@ -137,33 +142,28 @@ func TestRenderServicePageRejectsIntroOpeningASection(t *testing.T) {
}
}
func TestRenderServicePageStatesOpaqueCoverage(t *testing.T) {
// An opaque service has no dispatch table to count, so publishing a page for it
// would report 0% where the truth is that the surface is served elsewhere.
func TestRenderServicePageRefusesOpaqueCoverage(t *testing.T) {
pages := testPageSet(t)
coverage, err := CompareOperations(S3, DispatchInventory{
coverage, err := CompareOperations(STS, DispatchInventory{
Opaque: true,
Note: "Predastore serves this surface.",
Note: "Served elsewhere.",
})
if err != nil {
t.Fatal(err)
}
page, err := RenderServicePage(coverage, pages, "")
if err != nil {
t.Fatal(err)
}
if !strings.Contains(page, "not mechanically enumerable: Predastore serves this surface.") {
t.Errorf("page does not state why coverage is not enumerable:\n%s", page)
}
if strings.Contains(page, "### Operations") || strings.Contains(page, "%**") {
t.Errorf("opaque page claims a percentage or an operation table:\n%s", page)
if _, err := RenderServicePage(coverage, pages, ""); err == nil {
t.Fatal("RenderServicePage published a page for a service with no enumerable coverage")
}
}
func TestRenderIndexPageLinksEveryService(t *testing.T) {
func TestRenderIndexPageLinksEveryPublishedService(t *testing.T) {
pages := testPageSet(t)
coverages := make([]OperationCoverage, 0, len(Services()))
for _, service := range Services() {
coverage, err := CompareOperations(service, DispatchInventory{Opaque: true, Note: "not measured here."})
coverage, err := CompareOperations(service, DispatchInventory{})
if err != nil {
t.Fatal(err)
}
@@ -175,7 +175,13 @@ func TestRenderIndexPageLinksEveryService(t *testing.T) {
t.Fatal(err)
}
for _, service := range Services() {
page := pages.Services[service]
page, published := pages.Services[service]
if !published {
if strings.Contains(index, "/docs/"+string(service)+"-api-coverage") {
t.Errorf("index links unpublished service %q", service)
}
continue
}
if !strings.Contains(index, "["+page.Name+"](/docs/"+page.Slug+")") {
t.Errorf("index does not link %q at /docs/%s", page.Name, page.Slug)
}