feat: VendorImport CRD and Ramp adapter#16
Conversation
Add a `VendorImport` CRD plus a reconciler that pulls accounting
vendors from Ramp and upserts them as Draft Vendor records, so the
compliance team doesn't have to hand-type every vendor before reviewing
its DPA.
Key features:
- New `VendorImport` CRD in api/v1alpha1/vendorimport_types.go. The
spec carries the source ("ramp" today, extensible to other adapters),
a Ramp credentials Secret reference (`client_id` / `client_secret`),
optional endpoint + token URL overrides for sandboxes, and a
resyncInterval (default 24h) that controls automatic refresh.
- Ramp adapter under internal/adapter/ramp implementing OAuth2
client_credentials with on-demand token caching, paginated listing
of /developer/v1/accounting/vendors via the cursor-style `start` +
`page_size` parameters, and a hand-rolled `MappedVendor` shape that
the controller turns into either a new Vendor or a patch.
- VendorImportReconciler watches VendorImport CRs. For each pass it:
- resolves the credentials Secret (missing -> Ready=False,
Reason=CredentialsNotConfigured, requeue after resyncInterval),
- lists active Ramp vendors,
- lists existing imported Vendors via the
`compliance.miloapis.com/imported-from=ramp` label so subsequent
syncs find them in O(1),
- creates a Vendor for each unseen ramp-vendor-id, refreshes the
identity fields on existing Draft Vendors, and records Active
Vendors in `status.skippedActiveRefs` rather than overwriting
operator-curated data.
- Resource names are derived deterministically via
`BuildResourceName(displayName, vendorID)` — slug + 8-char SHA-1
suffix of the vendor ID — so re-runs always produce the same K8s
name and the slug never breaks the DNS-1123 63-char limit.
- Country of incorporation defaults to "UN" on creation (Ramp doesn't
expose it); operators must update it during review before activating
the compliance profile. Re-syncs respect operator edits to the
country and never clobber a non-default value.
- IAM updated: new ProtectedResource for VendorImport plus
vendorimports.{create,update,delete,patch,list,get,watch} permissions
on the existing compliance-admin / compliance-viewer Roles.
- Controller RBAC ClusterRole gains vendorimports + secrets perms.
Tests cover the mapping helper: deterministic resource naming,
DNS-1123 cap, fallback slug for non-alphanumeric input, suffix-based
collision avoidance for different vendor IDs, and rejection of blank
input.
|
This functionality (vendor management) should exist in a ramp-provider repo since it's specific to ramp. We shouldn't need a VendorImport CRD since configuring the connection to Ramp is a deployment time concern, not something that needs to change at runtime. |
A standalone controller that polls Ramp's accounting-vendors endpoint on a schedule and upserts Draft Vendor resources in Milo so the compliance team has a starting point instead of hand-typing every record. Lifts the Ramp client + mapping logic from the abandoned milo-os/compliance#16 branch but drops the VendorImport CRD entirely: configuration is deployment-time (flags + env + a mounted credentials Secret), with nothing about a particular Ramp account stored in the control plane. Operators that don't pay vendors through Ramp simply don't deploy this controller. Key features: - internal/ramp ports the OAuth2 client_credentials token cache, the paginated /developer/v1/accounting/vendors lister, and the deterministic BuildResourceName helper (slug + 8-char SHA-1 suffix, RFC 1123-safe, fits the 63-char DNS-label limit). MapVendor no longer references the compliance API package; it emits a small provider-owned MappedVendor so the Vendor schema can evolve without forcing churn here. - internal/syncer is a controller-runtime Runnable that fires once on startup and then on a configurable interval (default 24h). It lists imported Vendors via the compliance.miloapis.com/imported-from=ramp label, creates new Drafts for unseen Ramp IDs, refreshes identity fields on existing Drafts, and records Active Vendors in the log without overwriting them. Vendors are written through unstructured.Unstructured so the provider doesn't import the compliance API types. - cmd/main.go exposes --milo-kubeconfig, --leader-election-namespace, --ramp-* credential flags (with file-mount / env equivalents so the client_secret never has to live in process args), and --resync-interval. Leader election routes through the same Milo control plane the syncer writes to. - config/base/manager + components/{controller_rbac,leader_election, namespace} mirror compliance-service's layout, with the manager deployment mounting a `ramp-credentials` Secret at /etc/ramp by default and ClusterRole permissions limited to compliance.miloapis.com/vendors.{get,list,watch,create,update,patch}. - Dockerfile, Makefile, and .github/workflows/{publish,test, validate-kustomize} match the milo-os/compliance conventions so the publish-kustomize-bundle workflow can package this provider the same way. Tests cover the mapping helper: deterministic naming, DNS-1123 cap, fallback slug for non-alphanumeric input, suffix divergence across different vendor IDs, and rejection of blank input.
|
Good call. Reworked as a standalone provider per the architectural feedback: the Ramp adapter, the periodic-sync loop, and the deployment manifests now live in milo-os/ramp-provider. There's no VendorImport CRD any more — the Ramp connection is purely a deployment-time concern, configured via flags / env vars and a mounted Imported Vendors still carry the Closing this in favour of the new repo. |
Summary
Adds a
VendorImportCRD and a reconciler that pulls accounting vendors from Ramp and upserts them as DraftVendorrecords, so the compliance team doesn't have to hand-type every vendor before reviewing its DPA. This is Phase 2 of the compliance ingestion plan; Phase 1 (contract OCR in the staff portal) is already on the staff-portal `feat/compliance-ui` branch.Test plan
Follow-ups