How the JFrog Crossplane Providers Work
Learn the Crossplane building blocks, managed resources, the reconcile loop, and JFrog identifiers.
Learn how Crossplane and the JFrog Crossplane providers turn Kubernetes objects into JFrog objects. Read this page before you write your first manifests.
Building Blocks
A JFrog setup with Crossplane uses the following building blocks:
- Crossplane core runs in
crossplane-system. You install it with Helm. It installs packages and manages role-based access control (RBAC). The providers talk to JFrog. - Provider (
pkg.crossplane.io/v1) downloads and installs a provider package.- Each version becomes a ProviderRevision.
- The status columns
INSTALLEDandHEALTHYmust both beTrue. - Key fields:
package: The Open Container Initiative (OCI) image.packagePullSecrets: Required forreleases-authenticated-docker.jfrog.io.runtimeConfigRef: The DeploymentRuntimeConfig to use.revisionActivationPolicy:AutomaticorManual.packagePullPolicy.
- DeploymentRuntimeConfig (
pkg.crossplane.io/v1beta1) customizes the provider Deployment.- It sets
imagePullSecrets, container args (provider flags), resources, env, volumes, and replicas. - The container must be named
package-runtime. deploymentTemplate.spec.selector: {}is required.- One configuration can serve all providers.
- It sets
- Managed resource: One Kubernetes object per JFrog object. The JFrog providers define 260 Kinds.
- ClusterProviderConfig and ProviderConfig: The JFrog URL and credentials.
- Each provider has its own configuration API group (
artifactory.m.crossplane.io,xray.m.crossplane.io,project.m.crossplane.io,platform.m.crossplane.io, versionv1beta1). - You need one configuration per provider, even if all of them use the same Secret.
- The provider records a
ProviderConfigUsageper managed resource. - You can't delete a configuration while managed resources use it.
- Each provider has its own configuration API group (
Provider Architecture
Each provider is generated with Upjet from the official JFrog Terraform provider. It runs that provider's code in-process. There is no Terraform CLI, no workspace, and no state file on disk.
Validation, defaults, and API behavior of the upstream resources apply unchanged. The upstream JFrog Terraform documentation for a resource is therefore a valid source of field-level detail.
The following table lists each provider, its upstream Terraform provider, and its Kinds.
| Provider | Upstream | Kinds | API groups (<group>.<provider>.m.crossplane.io/v1alpha1) |
|---|---|---|---|
| provider-artifactory | jfrog/artifactory 12.11.14 | 214 | repository (150), webhook (24), configuration (16), security (11), replication (5), user (4), lifecycle (2), artifact (2) |
| provider-xray | jfrog/xray 3.1.13 | 21 | policy (7), configuration (7), report (5), watch (1), webhook (1) |
| provider-project | jfrog/project 1.9.9 | 8 | access (3), repository (3), project (2) |
| provider-platform | jfrog/platform 2.2.11 | 17 | access (6), authentication (6), configuration (3), lifecycle (2) |
Repository Kinds are named <Class><PackageType>Repository, for example LocalDockerV2Repository or RemoteNPMRepository. Webhook Kinds are named <Domain>Webhook and <Domain>CustomWebhook.
Anatomy of a Managed Resource
The following manifest shows the main parts of a managed resource:
apiVersion: repository.artifactory.m.crossplane.io/v1alpha1
kind: LocalDockerV2Repository
metadata:
name: team-docker-local # for most Kinds: the JFrog identifier (here the repository key)
namespace: team-a
annotations:
crossplane.io/external-name: team-docker-local # set automatically when omitted
spec:
providerConfigRef: {kind: ClusterProviderConfig, name: jfrog} # both fields required when written
managementPolicies: ["*"] # default
forProvider: # desired state, enforced continuously
description: Team images
xrayIndex: true
initProvider: {} # values applied only at creation
status:
atProvider: {} # observed state
conditions: [] # Synced, Ready, LastAsyncOperationIf you omit providerConfigRef entirely, it defaults to {kind: ClusterProviderConfig, name: default}.
Namespaced and Legacy Managed Resources
Each Kind exists in a namespaced and a legacy cluster-scoped variant. They differ as follows.
Namespaced (*.m.crossplane.io, recommended) | Legacy cluster-scoped (*.crossplane.io) | |
|---|---|---|
| Scope | Namespace (team isolation through RBAC) | Cluster |
providerConfigRef | {kind: ClusterProviderConfig or ProviderConfig, name} | {name} of a legacy ProviderConfig |
| Keep the JFrog object on delete | managementPolicies without Delete | deletionPolicy: Orphan |
| Secret references | {name, key} in the managed resource's namespace | {name, namespace, key} |
The Reconcile Loop
For every managed resource, the provider repeats these steps:
- Skips the object if it is paused (
crossplane.io/paused: "true"ormanagementPolicies: []). - Sets the
crossplane.io/external-nameannotation (defaults tometadata.name) and adds the finalizerfinalizer.managedresource.crossplane.io. - Resolves references (
...Ref,...Selector). - Connects using the ProviderConfig and credentials.
- Observes the JFrog object, then late-initializes it. Late initialization copies server defaults of optional fields you didn't set into
spec.forProvider. - Creates the object if it is missing, updates it if it differs, or deletes it if the managed resource is being deleted.
- Requeues the object. Healthy objects are checked again every poll interval (
--poll, default10min these providers). Failures retry with exponential backoff from 1 s to 60 s. You can't configure the backoff.
Create, update, and delete run asynchronously. Their result appears in the LastAsyncOperation condition.
Any change to the managed resource triggers an immediate reconcile. To force a reconcile, run the following command:
kubectl annotate <fq-kind>/<name> -n <ns> reconcile/kick="$(date +%s)" --overwriteIf the provider restarts in the middle of a create, Crossplane refuses to create the object again, to avoid duplicates. It reports cannot determine creation result - remove the crossplane.io/external-create-pending annotation if it is safe to proceed. For more information about the fix, see Troubleshoot the JFrog Crossplane Providers.
Conditions
Each managed resource reports the following conditions.
| Condition | Meaning |
|---|---|
Synced=True (ReconcileSuccess) | The last reconcile succeeded. |
Synced=False (ReconcileError) | The last reconcile failed. The message has the error (references, credentials, validation, API). |
Synced=False (ReconcilePaused) | The object is paused. |
Ready=True (Available) | The JFrog object exists. |
Ready=False (Creating, Deleting, Unavailable) | The object isn't yet created, is being deleted, or is unhealthy. |
LastAsyncOperation=False (AsyncCreateFailure, AsyncUpdateFailure, AsyncDeleteFailure, ApplyFailure, DestroyFailure) | The JFrog API request failed. The message contains the API response. |
SYNCED=False for a short time after you apply several objects together is normal. Objects wait for their dependencies and converge through retries.
kubectl describe shows the events of a managed resource. The events include the following:
CreatedExternalResource,UpdatedExternalResource, andDeletedExternalResource.- Failure events:
CannotResolveResourceReferences,CannotConnectToProvider,CannotCreateExternalResource,CannotUpdateExternalResource, andCannotDeleteExternalResource.
forProvider, initProvider, and Late Initialization
The provider treats these parts of a managed resource as follows:
- The provider enforces
spec.forProvideron every poll and reverts manual changes in JFrog. spec.initProviderhas the same shape, but the provider sends it only at creation. After that, the provider ignores it for drift.- Use it for values that someone else owns later. For example, set a user's initial groups here when membership is managed elsewhere.
- You can supply required fields in either
forProviderorinitProvider.
- Late initialization writes server defaults into
spec.forProvider. After that, the provider enforces them too. To enforce only what you wrote, omitLateInitializefrommanagementPolicies. status.atProvidershows the observed state, including read-only fields such asid. Secret values are never shown.
Some fields can take their value from another managed resource. For more information, see Reference Other Managed Resources.
Identifiers and External Names
The crossplane.io/external-name annotation stores the JFrog identifier. Each Kind uses one of the following strategies. The reference entry for each Kind states its strategy. For more information, see JFrog Crossplane Kinds Reference.
| Strategy | How it works | Kinds |
|---|---|---|
| Name as identifier | metadata.name (or the annotation) is the JFrog key or name. The identifying field isn't in spec. | Most Kinds: repositories, webhooks, users, groups, policies, watches, projects, roles, permissions |
| Spec field | The identifier is a normal spec field. metadata.name is free. | Platform GroupMembers, SCIMUser, AWSIAMRole, WorkersService. Xray RepositoryConfig. Project ShareRepositoryWithAll. |
| Composite | Built from several fields | project ShareRepository (<repoKey>:<targetProjectKey>), ReleaseBundleV2 (<name>:<version>), ReleaseBundleV2Promotion (<name>:<version>:<environment>), ItemProperties |
| Server-assigned ID | JFrog returns an ID that the provider writes into the annotation. Never edit or remove it, or a duplicate is created. | Xray CurationPolicy, CustomCurationCondition, IgnoreRule, reports. Artifactory replications, OauthSettings, SamlSettings, GeneralSecurity, AnonymousUser, APIKey, ScopedToken, Artifact |
| Singleton | Constant identifier, one per JFrog Platform | MailServer, TrashcanConfig, xray Settings, WorkersCount, CatalogLabels, platform CrowdSettings, HTTPSSOSettings, License, ReverseProxy, Lifecycle (per project or global) |
The following rules apply to identifiers:
- When the JFrog identifier isn't a valid Kubernetes name (uppercase, underscores), use any valid
metadata.name. Then set the annotation before the first apply. - Renaming a managed resource targets a different JFrog object. It doesn't rename the JFrog object.
- A managed resource whose identifier matches an existing object adopts that object instead of creating a duplicate. For the adoption procedure, see Adopt Existing JFrog Objects.
Next Steps
- To create, update, adopt, and delete JFrog objects, see Manage JFrog Objects with Crossplane.
- To plan names, ordering, and ownership, see Best Practices for the JFrog Crossplane Providers.
- To look up the fields of a Kind, see JFrog Crossplane Kinds Reference.
Updated about 1 hour ago
