Problem
Harbor never auto-creates projects, and the chart has no way to declare them. Today every project is created by hand in the UI, so a fresh install (or a GitOps re-deploy onto a new cluster) comes up with only library and every downstream consumer breaks until someone clicks through the UI.
The chart already has the right pattern for this: templates/oidc-config-job.yaml is a post-install/post-upgrade hook that drives Harbor's config API with curl using the admin Secret. Projects are the same kind of DB-resident state that Helm values can't set directly.
Proposal
Add a bootstrap.projects[] values block and apply it from a post-install job that runs after the OIDC job (higher hook weight, same ServiceAccount, image, and admin-password Secret):
bootstrap:
enabled: true
projects:
- name: cogs
public: false
# optional: map Keycloak groups (OIDC group members) to project roles
members:
- group: cog-publishers
role: developer # projectAdmin | maintainer | developer | guest | limitedGuest
# optional: tag immutability rules for the project
immutableTags:
- tagPattern: "sha-*"
repoPattern: "**"
API surface (all under /api/v2.0):
POST /projects with project_name and metadata.public; HEAD /projects?project_name= to test existence
POST /projects/{name}/members with role_id and member_group: {group_name, group_type: 3} for OIDC groups
POST /projects/{name}/immutabletagrules
Requirements
- Idempotent: re-running
helm upgrade is a no-op when the state already matches (tolerate 409 on create; look up before member/rule creation).
- No new dependencies in the job image beyond what
oidc-config-job.yaml already assumes (curl, sh; no jq).
- Rendered only when
bootstrap.enabled; works with nebariapp.enabled: false too (standalone installs also need projects).
helm template CI check covers the new template.
Acceptance
Context
Motivated by the Collab Hub cog-registry integration (nebari-dev/collab-hub-pack#7), which indexes a private cogs project and needs the project to exist declaratively. Follow-ups that build on this: robot account provisioning (#3), webhook policy provisioning (#4), the project_creation_restriction default (#5), and in-cluster consumer docs (#6).
Problem
Harbor never auto-creates projects, and the chart has no way to declare them. Today every project is created by hand in the UI, so a fresh install (or a GitOps re-deploy onto a new cluster) comes up with only
libraryand every downstream consumer breaks until someone clicks through the UI.The chart already has the right pattern for this:
templates/oidc-config-job.yamlis a post-install/post-upgrade hook that drives Harbor's config API with curl using the admin Secret. Projects are the same kind of DB-resident state that Helm values can't set directly.Proposal
Add a
bootstrap.projects[]values block and apply it from a post-install job that runs after the OIDC job (higher hook weight, same ServiceAccount, image, and admin-password Secret):API surface (all under
/api/v2.0):POST /projectswithproject_nameandmetadata.public;HEAD /projects?project_name=to test existencePOST /projects/{name}/memberswithrole_idandmember_group: {group_name, group_type: 3}for OIDC groupsPOST /projects/{name}/immutabletagrulesRequirements
helm upgradeis a no-op when the state already matches (tolerate 409 on create; look up before member/rule creation).oidc-config-job.yamlalready assumes (curl, sh; no jq).bootstrap.enabled; works withnebariapp.enabled: falsetoo (standalone installs also need projects).helm templateCI check covers the new template.Acceptance
make up-ssowith abootstrap.projectsentry brings up Harbor with that project present, correct visibility, members, and immutability rules.helm upgradecompletes without errors or duplicate members/rules.Context
Motivated by the Collab Hub cog-registry integration (nebari-dev/collab-hub-pack#7), which indexes a private
cogsproject and needs the project to exist declaratively. Follow-ups that build on this: robot account provisioning (#3), webhook policy provisioning (#4), theproject_creation_restrictiondefault (#5), and in-cluster consumer docs (#6).