Skip to content

Commit 97b8272

Browse files
authored
adds staff to model (#35)
- adds new DB entities Foundation and StaffMember so staff are stored separately from maintainers and linked to a foundation (CNCF or LF): model/main.go:134. - Bootstrap now migrates and seeds staff data: - Auto-migrates Foundation + StaffMember: db/bootstrap.go:56 - Loads staff from Staff!A:F and associates each row with a Foundation: db/bootstrap.go:242 - Generalized readSheetRows to accept an explicit sheet range and configurable “carry-forward” columns: db/bootstrap.go:342 - Fixed a GORM migration bug by defining FoundationOfficer.Services as an explicit many-to-many relationship: model/main.go:203. - Authorization updates for onboarding issue commands: - Added SQLStore.IsStaffGitHubAccount to check if a GitHub login is a staff member: db/store_impl.go:195 - /label can be run by staff as well as maintainers: onboarding/ server.go:323 - /fossa-invite accepted can be run by staff as well as maintainers/ assignees (via a shared helper): onboarding/server.go:70, onboarding/server.go:137 - Tests updated/added to include staff models + verify staff authorization: onboarding/test_helpers.go:22, onboarding/ server_test.go:410
2 parents 1031ded + b2d7480 commit 97b8272

29 files changed

+894
-149
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,4 @@ demo.db
1111
.gocache/
1212
.modcache/
1313
coverage.out
14+
.kcp/

.golangci.yml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,6 @@ version: "2"
22
run:
33
modules-download-mode: readonly
44
tests: true
5-
skip-dirs:
6-
- .modcache
7-
- .gocache
85
linters:
96
enable:
107
- bodyclose
@@ -64,6 +61,8 @@ linters:
6461
- govet
6562
path: (cmd/bootstrap|plugins/fossa/client)\.go
6663
paths:
64+
- \.modcache/
65+
- \.gocache/
6766
- third_party$
6867
- builtin$
6968
- examples$
@@ -77,6 +76,8 @@ formatters:
7776
exclusions:
7877
generated: lax
7978
paths:
79+
- \.modcache/
80+
- \.gocache/
8081
- third_party$
8182
- builtin$
8283
- examples$

AGENTS.MD

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,4 +34,19 @@ This document gives agents instructions and tips for working in this repository.
3434
- Keep changes minimal and focused. Match existing Go style and module layout.
3535
- Avoid unrelated refactors in the same change. Update docs/Makefile if deployment surface changes.
3636
- Use the "The Friends method" of writing commit messages to describe patches where each commit message is notionally pre-fixed with the sentence, "This is the change that " and then the commit message continues this sentence, for example, (this is the change that) "adds the repo being monitored to log output at loglevel INFO"
37+
38+
## CRD Workflow (Generated)
39+
- CRDs and deepcopy files are generated; do not hand-edit `config/crd/bases/*.yaml`, `config/kcp/*.yaml`, or `apis/maintainers/v1alpha1/zz_generated.deepcopy.go`.
40+
- When changing existing CRDs: update Go types and kubebuilder markers in `apis/maintainers/v1alpha1/types.go`, then regenerate.
41+
- When adding new CRDs: add the new type + kubebuilder markers in `apis/maintainers/v1alpha1/types.go`, include it in `addKnownTypes`, then regenerate.
42+
- Regeneration command: `make kcp-generate` (requires `controller-gen` and `apigen`; run `make kcp-install` if needed).
43+
- CRDs are generated from `apis/maintainers/v1alpha1/types.go`, not from `model/main.go`. DB model changes do not automatically reflect in CRDs; update API types explicitly when you want schema changes exposed.
44+
45+
## Model → CRD Exposure Checklist
46+
- Decide whether the DB change should be exposed via CRD or remain internal.
47+
- If exposed, update `apis/maintainers/v1alpha1/types.go` (fields + kubebuilder markers). Add to `addKnownTypes` for new resources.
48+
- Update the sync/mapping layer to populate the new fields (e.g., `cmd/sync/main.go` or store accessors).
49+
- Regenerate artifacts: `make kcp-generate`.
50+
- Review generated diffs under `config/crd/bases/` and `config/kcp/`.
51+
- Run CI (at least `make ci-local` or lint + tests).
3752
f

Dockerfile

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,14 @@ COPY . .
1313
RUN --mount=type=cache,target=/go/pkg/mod \
1414
--mount=type=cache,target=/root/.cache/go-build \
1515
go build -o /bootstrap ./cmd/bootstrap && \
16-
go build -o /maintainerd ./main.go
16+
go build -o /maintainerd ./main.go && \
17+
go build -o /sync ./cmd/sync
1718

18-
FROM gcr.io/distroless/base-debian12
19+
FROM gcr.io/distroless/base-debian12 AS maintainerd
1920
COPY --from=build /bootstrap /usr/local/bin/bootstrap
2021
COPY --from=build /maintainerd /usr/local/bin/maintainerd
2122
ENTRYPOINT ["/usr/local/bin/maintainerd"]
23+
24+
FROM gcr.io/distroless/base-debian12 AS sync
25+
COPY --from=build /sync /usr/local/bin/sync
26+
ENTRYPOINT ["/usr/local/bin/sync"]

Makefile

Lines changed: 73 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
11
TOPDIR=$(PWD)
22
GH_ORG_LC=robertkielty
33
REGISTRY ?= ghcr.io
4-
IMAGE ?= $(REGISTRY)/$(GH_ORG_LC)/maintainerd:latest
5-
SYNC_IMAGE ?= $(REGISTRY)/$(GH_ORG_LC)/maintainerd-sync:latest
4+
GIT_SHA ?= $(shell git rev-parse --short HEAD 2>/dev/null || echo unknown)
5+
BRANCH ?= $(shell git rev-parse --abbrev-ref HEAD 2>/dev/null | tr '[:upper:]' '[:lower:]' | tr '/_' '--')
6+
BUILD_DATE ?= $(shell date -u '+%a-%b-%d-%Y' | tr '[:lower:]' '[:upper:]')
7+
TAG ?= $(BRANCH)-$(GIT_SHA)-$(BUILD_DATE)
8+
IMAGE ?= $(REGISTRY)/$(GH_ORG_LC)/maintainerd:$(TAG)
9+
IMAGE_LATEST ?= $(REGISTRY)/$(GH_ORG_LC)/maintainerd:latest
10+
SYNC_IMAGE ?= $(REGISTRY)/$(GH_ORG_LC)/maintainerd-sync:$(TAG)
11+
SYNC_IMAGE_LATEST ?= $(REGISTRY)/$(GH_ORG_LC)/maintainerd-sync:latest
612
WHOAMI=$(shell whoami)
713

814
# Helpful context string for logs
@@ -35,18 +41,18 @@ GHCR_TOKEN ?= $(GITHUB_GHCR_TOKEN)
3541

3642

3743
# ---- Image ----
38-
.PHONY: image-build
39-
image-build:
44+
.PHONY: mntrd-image-build
45+
mntrd-image-build:
4046
@echo "Building container image: $(IMAGE)"
41-
@docker buildx build -t $(IMAGE) -f Dockerfile .
47+
@docker buildx build -t $(IMAGE) -f Dockerfile --target maintainerd .
4248

4349
.PHONY: sync-image-build
4450
sync-image-build:
4551
@echo "Building sync image: $(SYNC_IMAGE)"
46-
@docker buildx build -t $(SYNC_IMAGE) -f deploy/sync/Dockerfile .
52+
@docker buildx build -t $(SYNC_IMAGE) -f Dockerfile --target sync .
4753

48-
.PHONY: image-push
49-
image-push: image-build
54+
.PHONY: mntrd-image-push
55+
mntrd-image-push: mntrd-image-build
5056
@echo "Ensuring docker is logged in to $(REGISTRY) (uses GHCR_TOKEN if set)"
5157
@if [ -n "$(GHCR_TOKEN)" ]; then \
5258
echo "Logging into $(REGISTRY) as $(GHCR_USER) using token from GHCR_TOKEN"; \
@@ -56,9 +62,12 @@ image-push: image-build
5662
fi
5763
@echo "Pushing image: $(IMAGE)"
5864
@docker push $(IMAGE)
65+
@echo "Tagging and pushing latest: $(IMAGE_LATEST)"
66+
@docker tag $(IMAGE) $(IMAGE_LATEST)
67+
@docker push $(IMAGE_LATEST)
5968

60-
.PHONY: image-deploy
61-
image-deploy: image-push
69+
.PHONY: mntrd-image-deploy
70+
mntrd-image-deploy: mntrd-image-push
6271
@echo "Image pushed. Attempting rollout on context $(CTX_STR)."
6372
@CTX_FLAG="$(if $(KUBECONTEXT),--context $(KUBECONTEXT))" ; \
6473
if kubectl $$CTX_FLAG config current-context >/dev/null 2>&1; then \
@@ -83,6 +92,9 @@ sync-image-push: sync-image-build
8392
fi
8493
@echo "Pushing image: $(SYNC_IMAGE)"
8594
@docker push $(SYNC_IMAGE)
95+
@echo "Tagging and pushing latest: $(SYNC_IMAGE_LATEST)"
96+
@docker tag $(SYNC_IMAGE) $(SYNC_IMAGE_LATEST)
97+
@docker push $(SYNC_IMAGE_LATEST)
8698

8799
.PHONY: sync-image-deploy
88100
sync-image-deploy: sync-image-push
@@ -93,7 +105,7 @@ sync-image-deploy: sync-image-push
93105
fi ; \
94106
if ! kubectl -n $(NAMESPACE) $$CTX_FLAG get cronjob/maintainer-sync >/dev/null 2>&1; then \
95107
echo "CronJob/maintainer-sync not found in namespace $(NAMESPACE)."; \
96-
echo "Hint: apply deploy/manifests/sync.yaml or run 'make sync-apply' (or 'make manifests-apply')."; \
108+
echo "Hint: apply deploy/manifests/cronjob.yaml + deploy/manifests/sync-rbac.yaml or run 'make sync-apply' (or 'make manifests-apply')."; \
97109
exit 1; \
98110
fi ; \
99111
kubectl -n $(NAMESPACE) $$CTX_FLAG set image cronjob/maintainer-sync '*=$(SYNC_IMAGE)'; \
@@ -103,14 +115,50 @@ sync-image-deploy: sync-image-push
103115
.PHONY: sync-apply
104116
sync-apply:
105117
@echo "Applying sync resources in namespace $(NAMESPACE) [ctx=$(CTX_STR)]"
106-
@kubectl -n $(NAMESPACE) $(if $(KUBECONTEXT),--context $(KUBECONTEXT)) apply -f deploy/manifests/sync.yaml
107-
108-
.PHONY: image
109-
image: image-build
118+
@kubectl -n $(NAMESPACE) $(if $(KUBECONTEXT),--context $(KUBECONTEXT)) apply -f deploy/manifests/cronjob.yaml -f deploy/manifests/sync-rbac.yaml
119+
120+
.PHONY: sync-run
121+
sync-run:
122+
@bash -c 'set -euo pipefail; \
123+
job="maintainer-sync-manual-$$(date +%s)"; \
124+
echo "Creating sync job $$job in namespace $(NAMESPACE) [ctx=$(CTX_STR)]"; \
125+
kubectl -n $(NAMESPACE) $(if $(KUBECONTEXT),--context $(KUBECONTEXT)) create job --from=cronjob/maintainer-sync $$job; \
126+
'
127+
128+
.PHONY: migrate-schema
129+
migrate-schema:
130+
@echo "Running schema migration job in namespace $(NAMESPACE) [ctx=$(CTX_STR)]"
131+
@kubectl -n $(NAMESPACE) $(if $(KUBECONTEXT),--context $(KUBECONTEXT)) apply -f deploy/manifests/maintainerd-migrate-schema-job.yaml
132+
133+
.PHONY: migrate-schema-safe
134+
migrate-schema-safe:
135+
@bash -c 'set -euo pipefail; \
136+
echo "Scaling Deployment/maintainerd to 0 for schema migration [ctx=$(CTX_STR)]"; \
137+
kubectl -n $(NAMESPACE) $(if $(KUBECONTEXT),--context $(KUBECONTEXT)) scale deploy/maintainerd --replicas=0; \
138+
echo "Resolving PVC attachment node for maintainerd-db [ctx=$(CTX_STR)]"; \
139+
pv="$$(kubectl -n $(NAMESPACE) $(if $(KUBECONTEXT),--context $(KUBECONTEXT)) get pvc maintainerd-db -o jsonpath="{.spec.volumeName}")"; \
140+
node="$$(kubectl get volumeattachment -o jsonpath="{range .items[?(@.spec.source.persistentVolumeName==\"$${pv}\")]}{.spec.nodeName}{end}")"; \
141+
kubectl -n $(NAMESPACE) $(if $(KUBECONTEXT),--context $(KUBECONTEXT)) delete job maintainerd-migrate --ignore-not-found; \
142+
if [ -n "$${node}" ]; then \
143+
echo "Running schema migration job pinned to node $${node} [ctx=$(CTX_STR)]"; \
144+
kubectl create -f deploy/manifests/maintainerd-migrate-schema-job.yaml --dry-run=client -o json | \
145+
kubectl patch --local -f - -p "{\"spec\":{\"template\":{\"spec\":{\"nodeName\":\"$${node}\"}}}}" -o json | \
146+
kubectl apply -f -; \
147+
else \
148+
echo "No attachment node found; running migration job without pinning [ctx=$(CTX_STR)]"; \
149+
kubectl -n $(NAMESPACE) $(if $(KUBECONTEXT),--context $(KUBECONTEXT)) apply -f deploy/manifests/maintainerd-migrate-schema-job.yaml; \
150+
fi; \
151+
kubectl -n $(NAMESPACE) $(if $(KUBECONTEXT),--context $(KUBECONTEXT)) wait --for=condition=complete job/maintainerd-migrate --timeout=300s; \
152+
echo "Scaling Deployment/maintainerd back to 1 [ctx=$(CTX_STR)]"; \
153+
kubectl -n $(NAMESPACE) $(if $(KUBECONTEXT),--context $(KUBECONTEXT)) scale deploy/maintainerd --replicas=1; \
154+
'
155+
156+
.PHONY: mntrd-image
157+
mntrd-image: mntrd-image-build
110158
@true
111159

112-
.PHONY: image-run
113-
image-run: image
160+
.PHONY: mntrd-image-run
161+
mntrd-image-run: mntrd-image
114162
@docker run -ti --rm $(IMAGE)
115163

116164
# ---- Config ----
@@ -146,15 +194,20 @@ help:
146194
@echo "make lint -> run linters (requires golangci-lint)"
147195
@echo ""
148196
@echo "== Deployment =="
197+
@echo "Image tags: <branch>-<shortsha>-<DAY-MON-DD-YYYY> (UTC), plus latest"
149198
@echo "make secrets -> build $(ENVOUT) from $(ENVSRC) and apply both Secrets"
150199
@echo "make env -> build $(ENVOUT) from $(ENVSRC)"
151200
@echo "make apply-env -> create/update $(ENV_SECRET_NAME) from $(ENVOUT)"
152201
@echo "make apply-creds -> create/update $(CREDS_SECRET_NAME) from $(CREDS_FILE)"
153202
@echo "make clean-env -> remove $(ENVOUT)"
154203
@echo "make print -> show which keys would be loaded (without values)"
155-
@echo "make image-build -> build container image $(IMAGE) locally"
156-
@echo "make image-push -> build and push $(IMAGE) (uses GHCR_TOKEN/GITHUB_GHCR_TOKEN + GHCR_USER/DOCKER_REGISTRY_USERNAME for ghcr login)"
157-
@echo "make image-deploy -> build, push, and restart Deployment in $(NAMESPACE)"
204+
@echo "make mntrd-image-build -> build maintainerd image $(IMAGE) locally"
205+
@echo "make mntrd-image-push -> build and push $(IMAGE) (uses GHCR_TOKEN/GITHUB_GHCR_TOKEN + GHCR_USER/DOCKER_REGISTRY_USERNAME for ghcr login)"
206+
@echo "make mntrd-image-deploy -> build, push, and restart Deployment in $(NAMESPACE)"
207+
@echo "make sync-apply -> apply CronJob + RBAC for the sync job"
208+
@echo "make sync-run -> trigger a manual sync job and tail logs"
209+
@echo "make migrate-schema -> run one-off schema migration job"
210+
@echo "make migrate-schema-safe -> scale down, run migration pinned to attached node, scale back up"
158211
@echo "make ensure-ns -> ensure namespace $(NAMESPACE) exists"
159212
@echo "make apply-ghcr-secret -> create/update docker-registry Secret 'ghcr-secret'"
160213
@echo "make manifests-apply -> kubectl apply -f deploy/manifests (prod-only)"

apis/maintainers/v1alpha1/types.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,44 @@ type MaintainerList struct {
8787
Items []Maintainer `json:"items"`
8888
}
8989

90+
// StaffMemberSpec captures desired staff member attributes.
91+
type StaffMemberSpec struct {
92+
DisplayName string `json:"displayName"`
93+
PrimaryEmail string `json:"primaryEmail,omitempty"`
94+
GitHubAccount string `json:"gitHubAccount,omitempty"`
95+
GitHubEmail string `json:"gitHubEmail,omitempty"`
96+
FoundationRef *ResourceReference `json:"foundationRef,omitempty"`
97+
RegisteredAt *metav1.Time `json:"registeredAt,omitempty"`
98+
ExternalIDs map[string]string `json:"externalIDs,omitempty"`
99+
}
100+
101+
// StaffMemberStatus surfaces derived information gathered by controllers.
102+
type StaffMemberStatus struct {
103+
LastSynced *metav1.Time `json:"lastSynced,omitempty"`
104+
}
105+
106+
// +kubebuilder:object:root=true
107+
// +kubebuilder:resource:path=staffmembers,scope=Namespaced,shortName=staff,categories=maintainerd
108+
// +kubebuilder:subresource:status
109+
110+
// StaffMember represents a foundation staff member.
111+
type StaffMember struct {
112+
metav1.TypeMeta `json:",inline"`
113+
metav1.ObjectMeta `json:"metadata,omitempty"`
114+
115+
Spec StaffMemberSpec `json:"spec,omitempty"`
116+
Status StaffMemberStatus `json:"status,omitempty"`
117+
}
118+
119+
// +kubebuilder:object:root=true
120+
121+
// StaffMemberList is a list of StaffMember resources.
122+
type StaffMemberList struct {
123+
metav1.TypeMeta `json:",inline"`
124+
metav1.ListMeta `json:"metadata,omitempty"`
125+
Items []StaffMember `json:"items"`
126+
}
127+
90128
// CollaboratorSpec captures collaborator specific attributes.
91129
type CollaboratorSpec struct {
92130
DisplayName string `json:"displayName"`
@@ -437,6 +475,7 @@ func addKnownTypes(scheme *runtime.Scheme) error {
437475
scheme.AddKnownTypes(
438476
SchemeGroupVersion,
439477
&Maintainer{}, &MaintainerList{},
478+
&StaffMember{}, &StaffMemberList{},
440479
&Collaborator{}, &CollaboratorList{},
441480
&Project{}, &ProjectList{},
442481
&Company{}, &CompanyList{},

0 commit comments

Comments
 (0)