Skip to content

Commit e164336

Browse files
authored
Merge pull request #1 from cruxstack/dev
feat: create initial github-ops-app
2 parents 2e373ee + 75b26e5 commit e164336

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+6021
-0
lines changed

.editorconfig

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
[*]
2+
indent_style = space
3+
indent_size = 2
4+
end_of_line = lf
5+
insert_final_newline = true
6+
trim_trailing_whitespace = true
7+
charset = utf-8
8+
9+
[{Dockerfile,Dockerfile.*}]
10+
indent_size = 4
11+
tab_width = 4
12+
13+
[{Makefile,makefile,GNUmakefile}]
14+
indent_style = tab
15+
indent_size = 4
16+
17+
[Makefile.*]
18+
indent_style = tab
19+
indent_size = 4
20+
21+
[**/*.{go,mod,sum}]
22+
indent_style = tab
23+
indent_size = unset
24+
25+
[**/*.py]
26+
indent_size = 4

.env.example

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# debug
2+
APP_DEBUG_ENABLED=false
3+
4+
# githup app (required)
5+
APP_GITHUB_APP_ID=123456
6+
APP_GITHUB_APP_PRIVATE_KEY_PATH=./.local/private-key.pem
7+
APP_GITHUB_INSTALLATION_ID=987654
8+
APP_GITHUB_ORG=cruxstack
9+
APP_GITHUB_WEBHOOK_SECRET=your-webhook-secret-here
10+
11+
# githu pr compliance (optional)
12+
APP_PR_COMPLIANCE_ENABLED=true
13+
APP_PR_MONITORED_BRANCHES=main,master
14+
15+
# okta (optional)
16+
APP_OKTA_DOMAIN=company.okta.com
17+
APP_OKTA_CLIENT_ID=0oaxxxxxxxxxxxxxxxxxxxxx
18+
APP_OKTA_PRIVATE_KEY_PATH=./.local/okta-private-key.pem
19+
# APP_OKTA_SCOPES=okta.groups.read,okta.users.read
20+
21+
# okta sync rules
22+
APP_OKTA_GITHUB_USER_FIELD=githubUsername
23+
APP_OKTA_SYNC_RULES=[{"name":"sync-eng","enabled":true,"okta_group_pattern":"^github-eng-.*","github_team_prefix":"eng-","strip_prefix":"github-eng-","sync_members":true,"create_team_if_missing":true}]
24+
# APP_OKTA_SYNC_SAFETY_THRESHOLD=0.5 # Prevent mass removal if more than 50% would be removed (default: 0.5)
25+
26+
# slack configuration (optional)
27+
APP_SLACK_TOKEN=xoxb-xxxxxxxxxxxxxxxxxxxxxxxxxxxxx
28+
APP_SLACK_CHANNEL=C01234ABCDE

.github/.dependabot.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
version: 2
2+
updates:
3+
- package-ecosystem: "github-actions"
4+
directory: "/"
5+
schedule:
6+
interval: "daily"

.github/workflows/ci.yaml

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
name: ci
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
pull_request:
8+
branches:
9+
- main
10+
11+
permissions:
12+
contents: read
13+
14+
jobs:
15+
lint:
16+
runs-on: ubuntu-latest
17+
steps:
18+
- name: Checkout Code
19+
uses: actions/checkout@v4
20+
21+
- name: Setup Go
22+
uses: actions/setup-go@v5
23+
with:
24+
go-version: '1.24'
25+
26+
- name: Run go vet
27+
run: go vet ./...
28+
29+
- name: Check formatting
30+
run: |
31+
unformatted=$(gofmt -l .)
32+
if [ -n "$unformatted" ]; then
33+
echo "The following files are not formatted:"
34+
echo "$unformatted"
35+
exit 1
36+
fi
37+
38+
test:
39+
runs-on: ubuntu-latest
40+
steps:
41+
- name: Checkout Code
42+
uses: actions/checkout@v4
43+
44+
- name: Setup Go
45+
uses: actions/setup-go@v5
46+
with:
47+
go-version: '1.24'
48+
49+
- name: Run tests
50+
run: make test
51+
52+
- name: Run tests
53+
run: make test-verify-verbose
54+
55+
build:
56+
runs-on: ubuntu-latest
57+
steps:
58+
- name: Checkout Code
59+
uses: actions/checkout@v4
60+
61+
- name: Setup Go
62+
uses: actions/setup-go@v5
63+
with:
64+
go-version: '1.24'
65+
66+
- name: Build Lambda
67+
run: GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -C cmd/lambda -o ../../dist/bootstrap

.github/workflows/release.yaml

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
name: release
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
8+
permissions:
9+
contents: write
10+
11+
jobs:
12+
release:
13+
runs-on: ubuntu-latest
14+
steps:
15+
- name: Checkout Code
16+
uses: actions/checkout@v3
17+
- name: Bump Version
18+
id: tag_version
19+
uses: mathieudutour/github-tag-action@v6.1
20+
with:
21+
github_token: ${{ secrets.GITHUB_TOKEN }}
22+
default_bump: minor
23+
custom_release_rules: bug:patch:Fixes,chore:patch:Chores,docs:patch:Documentation,feat:minor:Features,refactor:minor:Refactors,test:patch:Tests,ci:patch:Development,dev:patch:Development
24+
- name: Create Release
25+
uses: ncipollo/release-action@v1.12.0
26+
with:
27+
tag: ${{ steps.tag_version.outputs.new_tag }}
28+
name: ${{ steps.tag_version.outputs.new_tag }}
29+
body: ${{ steps.tag_version.outputs.changelog }}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
name: semantic-check
2+
on:
3+
pull_request_target:
4+
types:
5+
- opened
6+
- edited
7+
- synchronize
8+
9+
permissions:
10+
contents: read
11+
pull-requests: read
12+
13+
jobs:
14+
main:
15+
name: Semantic Commit Message Check
16+
runs-on: ubuntu-latest
17+
steps:
18+
- name: Checkout Code
19+
uses: actions/checkout@v3
20+
- uses: amannn/action-semantic-pull-request@v5.2.0
21+
name: Check PR for Semantic Commit Message
22+
env:
23+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
24+
with:
25+
requireScope: false
26+
validateSingleCommit: true

.gitignore

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
!**/.gitkeep
2+
3+
tmp/
4+
dist/
5+
.DS_Store
6+
7+
.local/
8+
.env
9+
10+
cognito-hooks-go
11+
main
12+

AGENTS.md

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
# Agent Guidelines
2+
3+
## Architecture Overview
4+
- **Deployment**: Standard HTTP server OR AWS Lambda (both supported)
5+
- **GitHub**: GitHub App required (JWT + installation token authentication)
6+
- **Features**: Okta group sync + PR compliance monitoring (both optional)
7+
- **Entry points**:
8+
- `cmd/server/main.go` - Standard HTTP server (VPS, container, K8s)
9+
- `cmd/lambda/main.go` - Lambda adapter (API Gateway + EventBridge)
10+
- `cmd/verify/main.go` - Integration tests with HTTP mock servers
11+
- `cmd/sample/main.go` - **DO NOT RUN** (requires live credentials)
12+
- **Packages**:
13+
- `internal/app/` - Core logic, configuration, and HTTP handlers (no AWS
14+
dependencies)
15+
- `internal/github/` - API client, webhooks, PR checks, team mgmt, auth
16+
- `internal/okta/` - API client, group sync
17+
- `internal/notifiers/` - Slack formatting for events and reports
18+
- `internal/errors/` - Sentinel errors
19+
20+
## Build & Test
21+
- **Build server**: `make build-server` (creates `dist/server`)
22+
- **Build Lambda**: `make build-lambda` (creates `dist/bootstrap`)
23+
- **Run server locally**: `make server`
24+
- **Test all**: `make test` (runs with `-race -count=1`)
25+
- **Test single package**: `go test -race -count=1 ./internal/github`
26+
- **Test single function**: `go test -race -count=1 ./internal/okta -run
27+
TestGroupSync`
28+
- **Integration tests**: `make test-verify` (offline tests using HTTP mock
29+
servers)
30+
- **Verbose integration tests**: `make test-verify-verbose` (shows all HTTP
31+
requests during testing)
32+
- **Lint**: `go vet ./...` and `gofmt -l .`
33+
34+
IMPORTANT: DO NOT run `go run cmd/sample/main.go` as it requires live
35+
credentials and makes real API calls to GitHub/Okta/Slack.
36+
37+
## Code Style
38+
- **Imports**: stdlib, blank line, third-party, local (e.g., `internal/`)
39+
- **Naming**: `PascalCase` exports, `camelCase` private, `ALL_CAPS` env vars (prefixed `APP_`)
40+
- **Structs**: define types in package; constructors as `New()` or `NewTypeName()`; methods public (PascalCase)
41+
- **Formatting**: `gofmt` (tabs for indentation)
42+
- **Comments**: rare, lowercase, short; prefer self-documenting code
43+
- **Error handling**: return errors up stack; wrap with `fmt.Errorf` (see Error Handling below)
44+
45+
## Error Handling
46+
47+
### Error Message Format
48+
- **Style**: lowercase, action-focused, concise
49+
- **Pattern**: `"failed to {action} {object}: {context}"`
50+
- **Always include**: specific identifiers (PR numbers, team names, IDs)
51+
- **Examples**:
52+
-`"failed to fetch pr #123 from owner/repo: %w"`
53+
-`"failed to create team 'engineers' in org 'myorg': %w"`
54+
-`"required check 'ci' did not pass"`
55+
-`"PR is nil"` (no context)
56+
-`"Failed to Get Team"` (capitalized, generic)
57+
58+
### Error Wrapping
59+
- Use `github.com/cockroachdb/errors` package for all error handling
60+
- **Wrap errors**: `errors.Wrap(err, "context")` or `errors.Wrapf(err,
61+
"failed to sync team '%s'", teamName)`
62+
- **Create new errors**: `errors.New("message")` or `errors.Newf("error:
63+
%s", context)`
64+
- Automatically captures stack traces for debugging Lambda issues
65+
- Preserve original error context while adding specific details
66+
67+
### Sentinel Errors
68+
- Define common errors in `internal/errors/errors.go`
69+
- Each sentinel error is marked with a domain type (ValidationError,
70+
AuthError, APIError, ConfigError)
71+
- Domain markers enable error classification and monitoring
72+
- Use `errors.Is()` to check for sentinel errors in tests
73+
- Use `errors.HasType()` to check for error domains
74+
- Examples: `ErrMissingPRData`, `ErrInvalidSignature`, `ErrClientNotInit`
75+
76+
### Stack Traces
77+
- Automatically captured when wrapping errors with cockroachdb/errors
78+
- No performance overhead unless error is formatted
79+
- Critical for debugging serverless Lambda executions
80+
- Use `errors.WithDetailf()` to add structured context to auth/API errors
81+
82+
### Validation
83+
- Validate at parse time, not during processing
84+
- Webhook events validated in `ParseXxxEvent()` functions
85+
- Return detailed validation errors immediately
86+
- Prevents nil pointer issues downstream
87+
88+
### Error Logging vs Returning
89+
- **Fatal errors** (config, init): return immediately
90+
- **Recoverable errors** (individual items in batch): collect and continue
91+
- **Optional features** (notifications): log only, don't fail parent operation
92+
- Lambda handlers: log detailed errors, return sanitized messages to client
93+
94+
### Batch Operation Errors
95+
- Collect errors in result structs (e.g., `SyncReport.Errors`)
96+
- Continue processing remaining items
97+
- Return aggregated results with partial success
98+
- Helper methods: `HasErrors()`, `HasChanges()`
99+
100+
## Authentication & Integration
101+
102+
### GitHub
103+
- **Required**: GitHub App (JWT + installation tokens, automatic rotation)
104+
- **Auth flow**: JWT signed with private key → exchange for installation token → cached with auto-refresh
105+
- **Webhooks**: HMAC-SHA256 signature verification
106+
107+
### Okta
108+
- OAuth 2.0 with private key authentication
109+
- **Required scopes**: `okta.groups.read` and `okta.users.read`
110+
- Sync uses slug-based GitHub Teams API
111+
112+
### Slack
113+
- Optional notifications for PR events and sync reports
114+
- Configuration in `internal/notifiers/`
115+
116+
## Markdown Style
117+
- **Line length**: Max 80 characters for prose text
118+
- **Exceptions**: Code blocks, links, ASCII art, tables
119+
- **Table alignment**: Align columns in plaintext for readability
120+
- **Wrapping**: Break at natural boundaries (commas, periods, conjunctions)
121+
- **Lists**: Indent continuation lines with 2 spaces
122+
123+
NOTE: AGENTS.md itself is exempt from the 80-character line limit.

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2025 CruxStack
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

Makefile

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# set mac-only linker flags only for go test (not global)
2+
UNAME_S := $(shell uname -s)
3+
TEST_ENV :=
4+
ifeq ($(UNAME_S),Darwin)
5+
TEST_ENV = CGO_LDFLAGS=-w
6+
endif
7+
8+
TEST_FLAGS := -race -count=1
9+
10+
.PHONY: build-lambda
11+
build-lambda:
12+
GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -trimpath -ldflags "-s -w" -o dist/bootstrap ./cmd/lambda
13+
14+
.PHONY: build-server
15+
build-server:
16+
CGO_ENABLED=0 go build -trimpath -ldflags "-s -w" -o dist/server ./cmd/server
17+
18+
.PHONY: build-debug
19+
build-debug:
20+
GOOS=$(GOOS) GOARCH=$(GOARCH) go build -trimpath -ldflags "-s -w" -o dist/sample ./cmd/sample
21+
22+
.PHONY: debug
23+
debug:
24+
go run ./cmd/sample
25+
26+
.PHONY: server
27+
server:
28+
go run ./cmd/server
29+
30+
.PHONY: test
31+
test:
32+
$(TEST_ENV) go test $(TEST_FLAGS) ./...
33+
34+
.PHONY: test-unit
35+
test-unit:
36+
$(TEST_ENV) go test $(TEST_FLAGS) ./internal/...
37+
38+
.PHONY: test-verify
39+
test-verify:
40+
go run ./cmd/verify
41+
42+
.PHONY: test-verify-verbose
43+
test-verify-verbose:
44+
go run ./cmd/verify -verbose
45+

0 commit comments

Comments
 (0)