Skip to content

feat: support instance applications#322

Open
BoxBoxJason wants to merge 3 commits into
crossplane-contrib:masterfrom
BoxBoxJason:feat/instance-applications
Open

feat: support instance applications#322
BoxBoxJason wants to merge 3 commits into
crossplane-contrib:masterfrom
BoxBoxJason:feat/instance-applications

Conversation

@BoxBoxJason

Copy link
Copy Markdown
Contributor

Description of your changes

This PR adds the support of instance scoped (Oauth) Applications. This only works on self hosted instances.

This comes packaged with its full CRD api definition, controller lifecycle management logic, and associated clients methods.

Closes #321

I have:

  • Read and followed Crossplane's contribution process.
  • Followed the git conventional commit message format.
  • Made sure all changes are covered by proper tests, reaching a coverage of at least 80% when applicable.
  • Run make reviewable to ensure this PR is ready for review.
  • Added backport release-x.y labels to auto-backport this PR if necessary.

How has this code been tested

I have:

  • Successfully built and ran the provider locally against a kubernetes cluster.
  • Successfully created, updated, and deleted resources of the types I changed / created.
  • Ensured reconciliation loops for the changed / created resource complete without error.
    • Creation
    • Update
    • Deletion
  • Deleted the resource on the app side to ensure the provider correctly handles
    unexpected drift. (should result in recreation of the resource if applicable)
  • Updated the resource on the app side to ensure the provider correctly handles
    unexpected drift. (should result in an update of the resource if applicable)

@AyRickk

AyRickk commented Jun 11, 2026

Copy link
Copy Markdown

I'm interested in this change

@henrysachs @janwillies @derbauer97 Is it good for you ?

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new managed resource for GitLab instance-scoped OAuth Applications (self-hosted GitLab), including API types/CRDs, controller reconciliation logic, and supporting GitLab client helpers for both namespaced (instance.gitlab.m.crossplane.io) and cluster-scoped (instance.gitlab.crossplane.io) variants.

Changes:

  • Introduces Application API types and generated scaffolding (deepcopy/managed interfaces/registration) for cluster + namespaced scopes.
  • Adds controllers that reconcile Applications (observe via paginated list lookup; create returns secret via connection details; update implemented as delete-to-recreate).
  • Adds instance application client helpers, tests, and an example manifest.

Reviewed changes

Copilot reviewed 10 out of 23 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
pkg/namespaced/controller/instance/setup.go Registers the new namespaced Application controller in instance setup.
pkg/namespaced/controller/instance/applications/controller.go Namespaced Application managed reconciler implementation (observe/create/update/delete).
pkg/namespaced/controller/instance/applications/controller_test.go Unit tests for the namespaced Application controller behavior.
pkg/namespaced/clients/instance/application.go Namespaced GitLab Applications client wrapper + helpers (options/observation/up-to-date checks).
pkg/namespaced/clients/instance/application_test.go Unit tests for namespaced application client helpers.
pkg/cluster/controller/instance/zz_setup.go Registers the new cluster-scoped Application controller in instance setup.
pkg/cluster/controller/instance/applications/zz_controller.go Cluster-scoped (generated) Application controller implementation.
pkg/cluster/controller/instance/applications/zz_controller_test.go Unit tests for the cluster-scoped Application controller behavior.
pkg/cluster/clients/instance/zz_application.go Cluster-scoped (generated) GitLab Applications client wrapper + helpers.
pkg/cluster/clients/instance/zz_application_test.go Unit tests for cluster-scoped application client helpers.
package/crds/instance.gitlab.m.crossplane.io_applications.yaml Namespaced CRD for Application.
package/crds/instance.gitlab.crossplane.io_applications.yaml Cluster-scoped CRD for Application.
examples/instance/application.yaml Example manifest for creating a namespaced Application.
apis/namespaced/instance/v1alpha1/zz_generated.managedlist.go Generated managed list support for namespaced ApplicationList.
apis/namespaced/instance/v1alpha1/zz_generated.managed.go Generated managed interface helpers for namespaced Application.
apis/namespaced/instance/v1alpha1/zz_generated.deepcopy.go Generated deepcopy implementations for namespaced Application types.
apis/namespaced/instance/v1alpha1/register.go Registers namespaced Application types with the scheme.
apis/namespaced/instance/v1alpha1/application_types.go Source API type definitions and kubebuilder markers for namespaced Application.
apis/cluster/instance/v1alpha1/zz_register.go Registers cluster-scoped Application types with the scheme.
apis/cluster/instance/v1alpha1/zz_generated.managedlist.go Generated managed list support for cluster-scoped ApplicationList.
apis/cluster/instance/v1alpha1/zz_generated.managed.go Generated managed interface helpers for cluster-scoped Application.
apis/cluster/instance/v1alpha1/zz_generated.deepcopy.go Generated deepcopy implementations for cluster-scoped Application types.
apis/cluster/instance/v1alpha1/zz_application_types.go Generated cluster-scoped Application API types and markers.
Files not reviewed (13)
  • apis/cluster/instance/v1alpha1/zz_application_types.go: Generated file
  • apis/cluster/instance/v1alpha1/zz_generated.deepcopy.go: Generated file
  • apis/cluster/instance/v1alpha1/zz_generated.managed.go: Generated file
  • apis/cluster/instance/v1alpha1/zz_generated.managedlist.go: Generated file
  • apis/cluster/instance/v1alpha1/zz_register.go: Generated file
  • apis/namespaced/instance/v1alpha1/zz_generated.deepcopy.go: Generated file
  • apis/namespaced/instance/v1alpha1/zz_generated.managed.go: Generated file
  • apis/namespaced/instance/v1alpha1/zz_generated.managedlist.go: Generated file
  • pkg/cluster/clients/instance/zz_application.go: Generated file
  • pkg/cluster/clients/instance/zz_application_test.go: Generated file
  • pkg/cluster/controller/instance/applications/zz_controller.go: Generated file
  • pkg/cluster/controller/instance/applications/zz_controller_test.go: Generated file
  • pkg/cluster/controller/instance/zz_setup.go: Generated file

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread pkg/namespaced/controller/instance/applications/controller.go Outdated
Comment thread pkg/cluster/controller/instance/applications/zz_controller.go Outdated
Comment thread apis/namespaced/instance/v1alpha1/application_types.go
Comment thread apis/cluster/instance/v1alpha1/zz_application_types.go
@markussiebert markussiebert force-pushed the feat/instance-applications branch from 3566e1a to 7829337 Compare June 16, 2026 13:05

@markussiebert markussiebert left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this @BoxBoxJason — nice addition. I dug into the immutability/update story and want to align with you on the model before this lands. (I pushed a small follow-up commit to the branch first: fixes the double-wrapped delete error in Update() and the connection-secret doc comment, with regenerated zz_*/CRDs.)

The core constraint: there is no update API for instance applications

I checked both APIs and the client library:

  • REST — the Applications API only exposes POST /applications (create), GET /applications (list), DELETE /applications/:id (delete) and POST /applications/:id/renew-secret. There is no PUT/PATCH. The update endpoint is only a stalled draft: MR gitlab-org/gitlab!151480 (still Draft since Apr 2024) against issue gitlab-org/gitlab#458617 (still open).
  • GraphQL — no OAuth-application mutation exists at all. The only *ApplicationCreate mutation is cdApplicationCreate (continuous-deployment apps), which is unrelated.
  • The WebUI can edit scopes in place (keeping the same id) because it uses the internal Admin::ApplicationsController#update Rails route, which is not exposed programmatically.

So in-place update is simply not reachable from a provider today, and a delete+recreate would rotate both the application_id and the secret, breaking every existing OAuth client. We should not do that silently.

Proposed model

  1. Mark all spec fields immutable (name, redirectURI, scopes, confidential). I'd enforce it with a CEL rule (self == oldSelf) so the apiserver rejects edits directly, rather than relying only on a doc comment. When/if the PUT endpoint lands upstream, we can remove the immutability and implement real updates — relaxing a validation constraint is backward-compatible, so this is not a breaking change later.

  2. Implement a full diff in Observe and report drift even though we can't fix it. The whole point of the controller/GitOps model is to reflect whether actual == desired. If someone changes scopes out-of-band in the WebUI, the resource should go Synced=False, not silently pretend it's fine.

    This needs a client-go bump: the pinned v2.34.0 Application response struct has no Scopes field, so we currently can't read actual scopes. v2.39.0 added it — see applications.go#L56-L63:

    type Application struct {
        ID            int64    `json:"id"`
        ApplicationID string   `json:"application_id"`
        ...
        Confidential  bool     `json:"confidential"`
        Scopes        []string `json:"scopes"`
    }

    The GET /applications response already returns scopes (it's in the docs response example), so bumping client-go is all that's needed to observe it. application_name, callback_url and confidential are already observable on v2.34.0.

  3. Update returns a clear error instead of recreating, e.g. cannot update GitLab application in place: <field> is immutable in the GitLab API; delete and recreate to change it. Combined with (1), self-inflicted spec edits are rejected at the apiserver, and (2) surfaces any external drift.

Open question: the secret

The secret is only returned at creation. There is a rotation endpoint — POST /applications/:id/renew-secret (GitLab 16.11+) — but client-go does not wrap it (no method in v2.34.0 or v2.39.0; only Create/List/Delete). So rotation is possible at the REST level but would need a client-go addition or a raw call, and we'd have to decide how to trigger it (annotation? separate field?). I'd treat that as a follow-up rather than part of this PR.

What's your take on the immutable-now / observe-and-report / error-on-update model? And do you want secret rotation in scope here or split out?

@BoxBoxJason

BoxBoxJason commented Jun 16, 2026

Copy link
Copy Markdown
Contributor Author

Hey there !

Thanks for this nice and complete review.

I did not think it was a problem to rotate the application completely when the spec was mismatched. However, as you mentionned, it will indeed break other applications that do not support "hot" reloads. So I agree with you, this is something to avoid.

Secret Rotation

About the SDK modification, it seems like they proceed with an ultra fast release cycle, I created the issue about one week ago and they only took 3 days to complete and release it: https://gitlab.com/gitlab-org/api/client-go/-/work_items/2268. I will implement the drift detection for the Scopes too, now that this is available.

I just created an issue to request to add the RenewSecret endpoint for Oauth Applications: https://gitlab.com/gitlab-org/api/client-go/-/work_items/2270.

I think we could wait another 3 days to see if they implement it fast this time too.

Then I'd like to go with the RenewalPeriodDays field in the spec, we already implemented that in some resources. For this resource, it would be an integer indicating when to rotate the application secret after creation / rotate. The application secret creation / rotation date will be stored in an annotation (or it could also in the secret that contains app id / app secret but that's not my preference).

What do you think about that ?

BoxBoxJason and others added 3 commits June 19, 2026 02:39
Signed-off-by: BoxBoxJason <contact@boxboxjason.dev>
…connection-secret docs

Update() called Delete() and re-wrapped the already-wrapped error with
errDeleteFailed, producing duplicated context. Return the Delete() error
directly.

Drop the publishConnectionDetailsTo mention from the Application godoc:
Create() only accepts writeConnectionSecretToRef.

Cluster-scoped zz_* files and CRDs regenerated via make generate.

Signed-off-by: Markus Siebert <markus.siebert@deutschebahn.com>
Signed-off-by: BoxBoxJason <contact@boxboxjason.dev>
@BoxBoxJason BoxBoxJason force-pushed the feat/instance-applications branch from 5b57fc1 to 96eafde Compare June 19, 2026 00:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support (instance) (oauth) Applications

4 participants