Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions .github/actions/validate-repo-configs/action.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
name: "Validate repository configuration files"
description: "Validates repository config YAML files against the importer's JSON schema, failing fast with a clear error before Terraform runs"
inputs:
config-path:
description: "Path to the configuration repository containing repos/*.yaml files to validate"
required: true
fallback-schema-path:
description: "Workspace-relative path to the fallback schema, used when the config repository provides no override. Resolved against the runner workspace (e.g. 'gcss_config/.schemas/my.schema.json'). Defaults to the schema bundled with github-terraformer."
required: false
default: ".schemas/repository-config.schema.json"
importer-path:
description: "Path to the github-repo-importer Go module"
required: false
default: "feature/github-repo-importer"
runs:
using: "composite"
steps:
- name: Setup Go
uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5.6.0
with:
go-version-file: ${{ inputs.importer-path }}/go.mod

- name: Validate repos/*.yaml against schema
shell: bash
working-directory: ${{ inputs.importer-path }}
env:
FALLBACK_SCHEMA: ${{ format('{0}/{1}', github.workspace, inputs.fallback-schema-path != '' && inputs.fallback-schema-path || '.schemas/repository-config.schema.json') }}
run: |
go run . validate \
--config-dir "${{ github.workspace }}/${{ inputs.config-path }}" \
--fallback-schema "$FALLBACK_SCHEMA"
6 changes: 6 additions & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,9 @@ jobs:

- name: Test
run: go test ./...

- name: Check generated schema is up to date
run: |
go run . schema
git diff --exit-code ../../.schemas/repository-config.schema.json \
|| { echo "::error::.schemas/repository-config.schema.json is stale. Run 'go run . schema' in feature/github-repo-importer and commit the result."; exit 1; }
36 changes: 34 additions & 2 deletions .github/workflows/tf-plan.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: Terraform Plan
name: Validate and Plan

on:
workflow_call:
Expand All @@ -16,6 +16,11 @@ on:
type: string
description: 'The Terraform Cloud organization'
required: true
fallback_schema_path:
type: string
description: "Workspace-relative path to the fallback JSON schema for repos/*.yaml validation, used when the config repo provides no override (e.g. 'gcss_config/.schemas/my.schema.json'). When omitted, the schema bundled with github-terraformer is used."
required: false
default: ""
secrets:
app_private_key:
required: true
Expand All @@ -25,7 +30,34 @@ on:
required: true

jobs:
validate:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Checkout GCSS
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
with:
repository: G-Research/github-terraformer
ref: ${{ inputs.gcss_ref }}
persist-credentials: false

- name: Checkout config repo
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
with:
ref: ${{ inputs.commit_sha }}
token: ${{ secrets.gh_token }}
path: gcss_config
persist-credentials: false

- name: Validate repos/*.yaml
uses: ./.github/actions/validate-repo-configs
with:
config-path: gcss_config
fallback-schema-path: ${{ inputs.fallback_schema_path }}

terraform-plan:
needs: validate
runs-on: ubuntu-latest
environment: plan
permissions:
Expand Down Expand Up @@ -235,4 +267,4 @@ jobs:
summary: summary,
text: text
}
});
});
57 changes: 37 additions & 20 deletions feature/github-repo-importer/cmd/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,32 +11,58 @@ import (
orderedmap "github.com/wk8/go-ordered-map/v2"
)

const (
schemaOutDir = ".schemas"
schemaOutFile = "repository-config.schema.json"
)

var schemaCmd = &cobra.Command{
Use: "schema",
Short: "Generate JSON Schema for the repository config",
RunE: func(cmd *cobra.Command, args []string) error {
projectRoot := "../../"
outDir := ".schemas"
outFile := "repository-config.schema.json"
if err := os.MkdirAll(fmt.Sprintf("%s/%s", projectRoot, outDir), 0o755); err != nil {
return fmt.Errorf("create %s: %w", outDir, err)
if err := os.MkdirAll(fmt.Sprintf("%s/%s", projectRoot, schemaOutDir), 0o755); err != nil {
return fmt.Errorf("create %s: %w", schemaOutDir, err)
}

outPath := filepath.Join(projectRoot, outDir, outFile)
f, err := os.Create(outPath)
outPath := filepath.Join(projectRoot, schemaOutDir, schemaOutFile)

data, err := MarshalRepositoryConfigSchema()
if err != nil {
return fmt.Errorf("create schema file: %w", err)
return err
}
defer f.Close()
if err := os.WriteFile(outPath, data, 0o644); err != nil {
return fmt.Errorf("write schema file: %w", err)
}

fmt.Fprintf(cmd.OutOrStdout(), "Schema written to %s\n", outPath)
return nil
},
}

// MarshalRepositoryConfigSchema returns the JSON-encoded repository config schema.
func MarshalRepositoryConfigSchema() ([]byte, error) {
data, err := json.MarshalIndent(BuildRepositoryConfigSchema(), "", " ")
if err != nil {
return nil, fmt.Errorf("marshal schema: %w", err)
}
return data, nil
}

// BuildRepositoryConfigSchema reflects the JSON schema for the repository
// configuration from the Go structs and applies the manual constraints that
// cannot be expressed through struct tags. It is the single source of truth
// shared by the `schema` and `validate` commands.
func BuildRepositoryConfigSchema() *jsonschema.Schema {
{
reflector := &jsonschema.Reflector{
AllowAdditionalProperties: false,
FieldNameTag: "yaml",
}

schema := reflector.Reflect(&RepositoryWithExpansionConfig{})
schema.Title = "Repository Configuration"
schema.ID = jsonschema.ID(fmt.Sprintf("https://raw.githubusercontent.com/G-Research/github-terraformer/refs/heads/main/%s/%s", outDir, outFile))
schema.ID = jsonschema.ID(fmt.Sprintf("https://raw.githubusercontent.com/G-Research/github-terraformer/refs/heads/main/%s/%s", schemaOutDir, schemaOutFile))

squashIf := &jsonschema.Schema{
Properties: orderedmap.New[string, *jsonschema.Schema](),
Expand Down Expand Up @@ -108,17 +134,8 @@ var schemaCmd = &cobra.Command{
})
}

data, err := json.MarshalIndent(schema, "", " ")
if err != nil {
return fmt.Errorf("marshal schema: %w", err)
}
if _, err := f.Write(data); err != nil {
return fmt.Errorf("write schema: %w", err)
}

fmt.Fprintf(cmd.OutOrStdout(), "Schema written to %s\n", outPath)
return nil
},
return schema
}
}

func init() {
Expand Down
Loading
Loading