Skip to content

Commit 69bf842

Browse files
committed
CI: unify release workflow to publish Ruby gem and sync Terraform provider; add dry-run support; add Terraform docs page and sidebar; document provider repo layout and workflow in CLAUDE.md
1 parent 8dd069f commit 69bf842

File tree

4 files changed

+247
-8
lines changed

4 files changed

+247
-8
lines changed

.github/workflows/sync-provider.yml

Lines changed: 91 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,30 @@
1-
name: Sync Terraform Provider Catalog
1+
name: Release Gem + Sync Terraform Provider
22

33
on:
44
release:
55
types: [published]
6+
push:
7+
tags:
8+
- 'v*'
9+
workflow_dispatch:
10+
inputs:
11+
dry_run:
12+
description: 'Dry run (no pushes/tags/uploads)'
13+
required: false
14+
default: 'false'
15+
type: choice
16+
options: ['false', 'true']
617

718
permissions:
819
contents: read
920

1021
jobs:
1122
sync:
12-
name: Sync provider on release
23+
name: Release gem and sync provider
1324
runs-on: ubuntu-latest
25+
env:
26+
DRY_RUN: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.dry_run == 'true' && 'true' || 'false' }}
27+
TAG_NAME: ${{ github.ref_type == 'tag' && github.ref_name || github.event.release.tag_name }}
1428
steps:
1529
- name: Checkout logstruct
1630
uses: actions/checkout@v4
@@ -33,10 +47,67 @@ jobs:
3347
with:
3448
version: latest
3549

50+
- name: Determine version and validate tag
51+
id: ver
52+
shell: bash
53+
run: |
54+
TAG="${TAG_NAME:-}"
55+
if [[ -z "$TAG" ]]; then
56+
echo "No tag resolved; using release event tag." >&2
57+
TAG="${{ github.event.release.tag_name }}"
58+
fi
59+
if [[ -z "$TAG" ]]; then
60+
echo "Error: Could not determine tag name from event." >&2
61+
exit 1
62+
fi
63+
VERSION_FROM_TAG="${TAG#v}"
64+
VERSION_RB=$(ruby -e 'print File.read("lib/log_struct/version.rb")[/VERSION\s*=\s*"([^"]+)"/,1]')
65+
echo "tag=$TAG" >> $GITHUB_OUTPUT
66+
echo "version_tag=$VERSION_FROM_TAG" >> $GITHUB_OUTPUT
67+
echo "version_rb=$VERSION_RB" >> $GITHUB_OUTPUT
68+
echo "Resolved tag: $TAG (version $VERSION_FROM_TAG); code version: $VERSION_RB"
69+
if [[ "$DRY_RUN" != "true" && "$VERSION_FROM_TAG" != "$VERSION_RB" ]]; then
70+
echo "::error::Tag version ($VERSION_FROM_TAG) does not match lib/log_struct/version.rb ($VERSION_RB). Update version.rb before tagging, or run as dry-run."
71+
exit 1
72+
fi
73+
3674
- name: Generate site exports (enums/structs/keys)
3775
run: |
3876
ruby scripts/export_typescript_types.rb
3977
78+
- name: Build gem
79+
run: |
80+
gem build logstruct.gemspec
81+
ls -la *.gem
82+
83+
- name: Publish gem to RubyGems
84+
if: env.DRY_RUN != 'true'
85+
env:
86+
RUBYGEMS_API_KEY: ${{ secrets.RUBYGEMS_API_KEY }}
87+
run: |
88+
if [[ -z "$RUBYGEMS_API_KEY" ]]; then
89+
echo '::error::RUBYGEMS_API_KEY secret is missing.'
90+
exit 1
91+
fi
92+
mkdir -p ~/.gem
93+
umask 077
94+
cat > ~/.gem/credentials <<EOF
95+
---
96+
:rubygems_api_key: $RUBYGEMS_API_KEY
97+
EOF
98+
GEM_FILE="logstruct-${{ steps.ver.outputs.version_tag }}.gem"
99+
if [[ ! -f "$GEM_FILE" ]]; then
100+
echo "::error::Expected gem file $GEM_FILE not found."
101+
ls -la *.gem || true
102+
exit 1
103+
fi
104+
gem push "$GEM_FILE"
105+
106+
- name: Gem publish (dry-run)
107+
if: env.DRY_RUN == 'true'
108+
run: |
109+
echo "[DRY-RUN] Would push gem logstruct-${{ steps.ver.outputs.version_tag }}.gem to RubyGems"
110+
40111
- name: Clone terraform-provider-logstruct repository
41112
env:
42113
PUSH_TOKEN: ${{ secrets.PROVIDER_PUSH_TOKEN }}
@@ -65,9 +136,17 @@ jobs:
65136
PUSH_TOKEN: ${{ secrets.PROVIDER_PUSH_TOKEN }}
66137
run: |
67138
cd terraform-provider-logstruct
68-
if ! git diff --quiet; then
69-
git add pkg/data/catalog_gen.go pkg/data/catalog.json go.mod go.sum || true
70-
git commit -m "Sync catalog from logstruct ${{ github.event.release.tag_name }}"
139+
if git diff --quiet; then
140+
echo "No provider changes to commit."
141+
exit 0
142+
fi
143+
git add pkg/data/catalog_gen.go pkg/data/catalog.json go.mod go.sum || true
144+
if [[ "$DRY_RUN" == "true" ]]; then
145+
echo "[DRY-RUN] Would commit and push provider catalog changes with message:"
146+
echo "Sync catalog from logstruct ${{ steps.ver.outputs.tag }}"
147+
git --no-pager diff --staged || true
148+
else
149+
git commit -m "Sync catalog from logstruct ${{ steps.ver.outputs.tag }}"
71150
git push origin HEAD:main
72151
fi
73152
@@ -76,10 +155,14 @@ jobs:
76155
PUSH_TOKEN: ${{ secrets.PROVIDER_PUSH_TOKEN }}
77156
run: |
78157
cd terraform-provider-logstruct
79-
TAG="${{ github.event.release.tag_name }}"
158+
TAG="${{ steps.ver.outputs.tag }}"
80159
if git rev-parse -q --verify "refs/tags/$TAG" >/dev/null; then
81160
echo "Tag $TAG already exists; skipping."
82161
exit 0
83162
fi
84-
git tag -a "$TAG" -m "Release $TAG (logstruct sync)"
85-
git push origin "$TAG"
163+
if [[ "$DRY_RUN" == "true" ]]; then
164+
echo "[DRY-RUN] Would create and push tag $TAG"
165+
else
166+
git tag -a "$TAG" -m "Release $TAG (logstruct sync)"
167+
git push origin "$TAG"
168+
fi

CLAUDE.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,43 @@
4444
- Generate spellcheck dictionary: `scripts/generate_lockfile_words.sh`
4545
- Generate TypeScript types from Ruby log structs: `scripts/export_typescript_types.rb`
4646

47+
## Terraform Provider repo in this workspace
48+
49+
- The Terraform provider lives in a separate GitHub repo: `DocSpring/terraform-provider-logstruct`.
50+
- For convenience, the provider repo is checked out as a plain directory at `./terraform-provider-logstruct/` in this repo. It is NOT a Git submodule and is ignored by this repo’s `.gitignore`.
51+
- You can inspect/build it locally:
52+
- `cd terraform-provider-logstruct`
53+
- `go build ./...`
54+
- Changes you make here are not committed by this repo. To contribute to the provider, commit from inside its directory and push to its own remote.
55+
56+
## Automated releases (gem + provider)
57+
58+
- Workflow: `.github/workflows/sync-provider.yml` ("Release Gem + Sync Terraform Provider").
59+
- Triggers:
60+
- Push tag matching `v*` (e.g., `v0.0.1-rc1`, `v0.2.0`).
61+
- GitHub Release published.
62+
- Manual run (workflow_dispatch) with `dry_run` input.
63+
- Behavior:
64+
- Builds and publishes the Ruby gem to RubyGems (requires `RUBYGEMS_API_KEY`).
65+
- Regenerates the provider’s embedded catalog (`scripts/export_provider_catalog.rb`), builds the provider, commits catalog changes, and tags the provider repo with the same version.
66+
- Enforces version alignment: the tag `vX.Y.Z` (or RC) must match `lib/log_struct/version.rb` unless run in dry-run.
67+
68+
## Dry-run mode
69+
70+
- CI dry-run lets you smoke-test the workflow without publishing anything:
71+
- Actions → "Release Gem + Sync Terraform Provider" → Run workflow → `dry_run=true`.
72+
- The workflow builds the gem and provider, shows diffs, and skips pushes/tags/uploads.
73+
- Local dry-run for the GitHub Actions workflow isn’t practical without a runner like `act`. You can still sanity-check pieces locally:
74+
- `gem build logstruct.gemspec`
75+
- `ruby scripts/export_typescript_types.rb`
76+
- `ruby scripts/export_provider_catalog.rb`
77+
- `cd terraform-provider-logstruct && go build ./...`
78+
79+
## Required secrets
80+
81+
- `RUBYGEMS_API_KEY`: API key with permission to publish `logstruct`.
82+
- `PROVIDER_PUSH_TOKEN`: PAT with write access to `DocSpring/terraform-provider-logstruct` for syncing/tagging.
83+
4784
# Core Dependencies
4885

4986
This gem requires Rails 7.0+ and will always have access to these core Rails modules:

site/app/docs/layout.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,11 @@ export default function DocsLayout({
198198
title="Code Coverage"
199199
active={false}
200200
/>
201+
<DocNavItem
202+
href="/docs/terraform"
203+
title="Terraform Provider"
204+
active={pathname.startsWith('/docs/terraform')}
205+
/>
201206
</nav>
202207
</div>
203208
</div>

site/app/docs/terraform/page.tsx

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
import { CodeBlock } from '@/components/code-block';
2+
import EditPageLink from '@/components/edit-page-link';
3+
4+
export const metadata = {
5+
title: 'Terraform Provider',
6+
description:
7+
'Use the DocSpring/logstruct Terraform provider to validate struct/event combos and compile CloudWatch filter patterns.',
8+
};
9+
10+
export default function TerraformDocsPage() {
11+
const requiredProviders = `terraform {
12+
required_providers {
13+
logstruct = {
14+
source = "DocSpring/logstruct"
15+
version = "~> 0.2" # matches LogStruct tag
16+
}
17+
}
18+
}`;
19+
20+
const cwFilterDataSource = `data "logstruct_cloudwatch_filter" "email_delivered" {
21+
struct = "ActionMailer"
22+
event = "delivered"
23+
}`;
24+
25+
const cwMetricFilter = `resource "aws_cloudwatch_log_metric_filter" "email_delivered_count" {
26+
name = "Email Delivered Count"
27+
log_group_name = var.log_group.docspring
28+
pattern = data.logstruct_cloudwatch_filter.email_delivered.pattern
29+
30+
metric_transformation {
31+
name = "docspring_email_delivered_count"
32+
namespace = var.namespace.logs
33+
value = "1"
34+
default_value = "0"
35+
unit = "Count"
36+
}
37+
}`;
38+
39+
return (
40+
<div className="space-y-10">
41+
<h1 className="text-3xl font-bold mb-2">Terraform Provider</h1>
42+
<p className="text-neutral-600 dark:text-neutral-400">
43+
The LogStruct Terraform provider offers type-safe helpers for building
44+
CloudWatch filter patterns from your structured logs, and validates
45+
struct/event combinations at plan time.
46+
</p>
47+
48+
<section id="installation" className="space-y-4">
49+
<h2 className="text-2xl font-semibold">Installation</h2>
50+
<p>
51+
Add the provider to your Terraform configuration. The provider version
52+
tracks LogStruct releases and uses the same tag.
53+
</p>
54+
<CodeBlock language="hcl" title="Required Providers">
55+
{requiredProviders}
56+
</CodeBlock>
57+
</section>
58+
59+
<section id="example" className="space-y-4">
60+
<h2 className="text-2xl font-semibold">
61+
CloudWatch Metric Filter Example
62+
</h2>
63+
<p>
64+
Compile a CloudWatch JSON filter for a known struct/event and wire it
65+
into an AWS metric filter. Invalid combinations produce Terraform
66+
diagnostics during validate/plan.
67+
</p>
68+
<div className="grid gap-6 md:grid-cols-2">
69+
<CodeBlock language="hcl" title="Data Source">
70+
{cwFilterDataSource}
71+
</CodeBlock>
72+
<CodeBlock language="hcl" title="AWS Metric Filter">
73+
{cwMetricFilter}
74+
</CodeBlock>
75+
</div>
76+
<p className="text-neutral-600 dark:text-neutral-400">
77+
The compiled pattern looks like:{' '}
78+
<code>{`{ $.src = "mailer" && $.evt = "delivered" ... }`}</code>
79+
and includes canonical key names with any fixed source constraints.
80+
</p>
81+
</section>
82+
83+
<section id="validation" className="space-y-4">
84+
<h2 className="text-2xl font-semibold">Validation and Safety</h2>
85+
<ul className="list-disc pl-6 space-y-1">
86+
<li>
87+
Structs and events are validated at plan time using the embedded
88+
LogStruct catalog exported from this repository.
89+
</li>
90+
<li>
91+
If allowed events or keys change in a newer LogStruct release, your
92+
Terraform plan will fail fast with clear diagnostics.
93+
</li>
94+
</ul>
95+
</section>
96+
97+
<section id="versioning" className="space-y-4">
98+
<h2 className="text-2xl font-semibold">Versioning</h2>
99+
<ul className="list-disc pl-6 space-y-1">
100+
<li>
101+
Provider versions mirror LogStruct tags (for example,{' '}
102+
<code>v0.2.0</code>).
103+
</li>
104+
<li>
105+
Use a compatible constraint like <code>~&gt; 0.2</code> to receive
106+
non-breaking updates.
107+
</li>
108+
</ul>
109+
</section>
110+
111+
<EditPageLink path="app/docs/terraform/page.tsx" />
112+
</div>
113+
);
114+
}

0 commit comments

Comments
 (0)