catalog

package
v1.0.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Dec 31, 2025 License: Apache-2.0 Imports: 18 Imported by: 0

README

Catalog

The catalog package defines the list of modules and add‑ons that can be purchased in Openlane. These modules are created and added to Stripe subscriptions if the feature is enabled. Each feature describes its billing information and any usage limits that are granted when enabled. Catalog data is stored in catalog.yaml and loaded at runtime to reconcile Stripe products and expose the available modules through the API.

Overview

A catalog entry contains the following pieces:

  • Modules – first‑class portions of the platform that customers subscribe to. At least one module is usually required. See the modules vs. addons section for explanation for a comparison with add‑ons.
  • Add‑ons – optional features that enhance a module. They often provide additional usage or automation in small increments.
  • Billing – one or more prices for a feature, defined by interval and amount. Prices are matched in Stripe by lookup key and metadata rather than hard coded IDs.
  • Usage – optional limits (e.g. evidence_storage_gb) granted when the feature is enabled.

The package exposes helper functions to load and save catalogs, validate prices against Stripe and ensure any missing products or prices are created. These packages are intended to be used for the Openlane Saas Product, so you would not enable this functionality running it as an open source project. This means that when you run the server WITHOUT the entitlements option enabled, there will be no checks around features, so access to any feature of the platform is not gated separately.

Flow
graph TD
    A[Catalog structs in catalog.go] --> B[generate catalog.schema.json]
    B --> C[catalog.yaml]
    C --> D[cmd/catalog syncs with Stripe]
    D --> E[Stripe products and prices]
    D --> C
    E --> F[Stripe webhook]
    F --> G[OpenFGA tuples]
    G --> H[Ent hooks & policies]

Modules vs. Add‑ons

Kind What it is Typical price Core to the product? UI placement Examples
module A first‑class, standalone slice of the platform. Customers normally subscribe to at least one module to get value. $20–$100 / mo Yes – at least one required Primary cards in signup & pricing page compliance, trust_center
addon An optional enhancement that augments a module. Often usage‑based or small flat fee. $1–$10 / mo No – opt‑in “Extras / Marketplace” or Billing settings vanity_domain, extra seats
Why we keep the lists separate
  • Positioning: Modules appear in marketing copy as base offerings; add‑ons are upsells.
  • Off‑boarding: Cancelling the last module should close the subscription; removing an add‑on should not.
  • Visibility controls: Add‑ons are frequently beta or private audience.
  • Pricing UI: Front‑end renders modules and add‑ons in distinct sections for clarity.

Implementation‑wise, the two kinds are identical Go structs; the separation only affects UX.

JSON Schema and Code Generation

Two small utilities live in the genjsonschema and genyaml directories.

go run genjsonschema/catalog_schema.go   # generates genjsonschema/catalog.schema.json
go run genyaml/yamlgen.go                # converts catalog.yaml into Go code

The generated JSON schema is used by LoadCatalog to validate the YAML format. Running go generate ./pkg/catalog (or task catalog:genjsonschema and task catalog:genyaml) will update both artifacts.

CLI Utilities

Under cmd/catalog are helper commands for working with the catalog and Stripe:

  • catalog – compares catalog.yaml with your Stripe account and optionally creates missing products or prices. Use --stripe-key to supply the API key.
  • pricemigrate – tags a replacement price and can migrate subscriptions from one price ID to another.

These tools are meant for internal maintenance but are useful when seeding a new Stripe environment or validating changes.

Catalog Versioning

SaveCatalog writes the catalog back to disk and manages version bumps. If the contents change, the patch version is incremented and a SHA256 of the version string is stored. This hash is checked by IsCurrent() to verify the catalog on disk matches its declared version.

Example

c, err := catalog.LoadCatalog("./pkg/catalog/catalog.yaml")
if err != nil {
    log.Fatal(err)
}

// Ensure products and prices exist in Stripe and update PriceID fields
if err := c.EnsurePrices(ctx, stripeClient, "usd"); err != nil {
    log.Fatal(err)
}

diff, err := c.SaveCatalog("./pkg/catalog/catalog.yaml")
if err != nil {
    log.Fatal(err)
}
fmt.Println(diff)

Seeding a Stripe Sandbox

If you are just doing local development and not changing the catalog, you can use the existing Sandbox sandbox and skip the below. If you update your .config.yaml with:

entConfig:
  modules:
    enabled: true
    useSandbox: true

And grab the sandbox api keys, this is already seeded with the existing catalog_sandbox.yaml. Do not commit changes to this file unless they were synced to stripe to allow for multiple users to test in this development environment. If instead, you are doing development on the actual catalog, you should use a new sandbox until the changes are ready to be merged.

Best way to test this setup is to create a new Stripe sandbox, grab the API key, and seed it with your products and prices (creating a new catalog file and removing the existing product ID's and price ID's is best method), similar to below (NOTE: the 404's are intentional, this is a pre-check to ensure the configured catalog doesn't have products and prices in the stripe instance, and that results in 404 errors for "didn't find product and price"):


(⎈|gke_prod-apps-project_us-central1_prod-central1-core:openlane)➜  core git:(feat-modmodmod) ✗ go run cmd/catalog/main.go --write=true --stripe-key="keyforyoursandboxinstance"
[ERROR] Request error from Stripe (status 404): {"code":"resource_missing","doc_url":"https://stripe.com/docs/error-codes/resource-missing","status":404,"message":"No such product: 'base_module'","param":"id","request_id":"req_eyYmUh5YrY3nAj","request_log_url":"https://dashboard.stripe.com/test/logs/req_eyYmUh5YrY3nAj?t=1752104108","type":"invalid_request_error"}
[ERROR] Request error from Stripe (status 404): {"code":"resource_missing","doc_url":"https://stripe.com/docs/error-codes/resource-missing","status":404,"message":"No such product: 'compliance_module'","param":"id","request_id":"req_NE7xVpjXOw79Ft","request_log_url":"https://dashboard.stripe.com/test/logs/req_NE7xVpjXOw79Ft?t=1752104109","type":"invalid_request_error"}
[ERROR] Request error from Stripe (status 404): {"code":"resource_missing","doc_url":"https://stripe.com/docs/error-codes/resource-missing","status":404,"message":"No such product: 'entity_management_module'","param":"id","request_id":"req_d7iRolDbL2rDDH","request_log_url":"https://dashboard.stripe.com/test/logs/req_d7iRolDbL2rDDH?t=1752104109","type":"invalid_request_error"}
[ERROR] Request error from Stripe (status 404): {"code":"resource_missing","doc_url":"https://stripe.com/docs/error-codes/resource-missing","status":404,"message":"No such product: 'trust_center_module'","param":"id","request_id":"req_9KlgBqR9o6bZU1","request_log_url":"https://dashboard.stripe.com/test/logs/req_9KlgBqR9o6bZU1?t=1752104110","type":"invalid_request_error"}
[ERROR] Request error from Stripe (status 404): {"code":"resource_missing","doc_url":"https://stripe.com/docs/error-codes/resource-missing","status":404,"message":"No such product: 'vulnerability_management_module'","param":"id","request_id":"req_tgnXzB8Uiobzln","request_log_url":"https://dashboard.stripe.com/test/logs/req_tgnXzB8Uiobzln?t=1752104110","type":"invalid_request_error"}
[ERROR] Request error from Stripe (status 404): {"code":"resource_missing","doc_url":"https://stripe.com/docs/error-codes/resource-missing","status":404,"message":"No such product: 'risk_management_addon'","param":"id","request_id":"req_YmIq3oIQv4tlcm","request_log_url":"https://dashboard.stripe.com/test/logs/req_YmIq3oIQv4tlcm?t=1752104111","type":"invalid_request_error"}
[ERROR] Request error from Stripe (status 404): {"code":"resource_missing","doc_url":"https://stripe.com/docs/error-codes/resource-missing","status":404,"message":"No such product: 'domain_scanning_addon'","param":"id","request_id":"req_N27WiPYY0RVzvq","request_log_url":"https://dashboard.stripe.com/test/logs/req_N27WiPYY0RVzvq?t=1752104111","type":"invalid_request_error"}
[ERROR] Request error from Stripe (status 404): {"code":"resource_missing","doc_url":"https://stripe.com/docs/error-codes/resource-missing","status":404,"message":"No such product: 'extra_evidence_storage_addon'","param":"id","request_id":"req_fBB0qDTqfArkGu","request_log_url":"https://dashboard.stripe.com/test/logs/req_fBB0qDTqfArkGu?t=1752104112","type":"invalid_request_error"}
[ERROR] Request error from Stripe (status 404): {"code":"resource_missing","doc_url":"https://stripe.com/docs/error-codes/resource-missing","status":404,"message":"No such product: 'policy_management_addon'","param":"id","request_id":"req_QXl1cBH0M0GhEj","request_log_url":"https://dashboard.stripe.com/test/logs/req_QXl1cBH0M0GhEj?t=1752104112","type":"invalid_request_error"}
┌────────┬─────────────────────────────────┬─────────┬────────────────┐
│ TYPE   │ FEATURE                         │ PRODUCT │ MISSING PRICES │
├────────┼─────────────────────────────────┼─────────┼────────────────┤
│ module │ base_module                     │ false   │ 1              │
├────────┼─────────────────────────────────┼─────────┼────────────────┤
│ module │ compliance_module               │ false   │ 2              │
├────────┼─────────────────────────────────┼─────────┼────────────────┤
│ module │ entity_management_module        │ false   │ 2              │
├────────┼─────────────────────────────────┼─────────┼────────────────┤
│ module │ trust_center_module             │ false   │ 2              │
├────────┼─────────────────────────────────┼─────────┼────────────────┤
│ module │ vulnerability_management_module │ false   │ 2              │
├────────┼─────────────────────────────────┼─────────┼────────────────┤
│ addon  │ domain_scanning_addon           │ false   │ 2              │
├────────┼─────────────────────────────────┼─────────┼────────────────┤
│ addon  │ extra_evidence_storage_addon    │ false   │ 1              │
├────────┼─────────────────────────────────┼─────────┼────────────────┤
│ addon  │ policy_management_addon         │ false   │ 2              │
├────────┼─────────────────────────────────┼─────────┼────────────────┤
│ addon  │ risk_management_addon           │ false   │ 2              │
└────────┴─────────────────────────────────┴─────────┴────────────────┘

Create missing products and prices? (y/N): y
Catalog successfully written to ./pkg/catalog/catalog_sandbox.yaml
Catalog changes:
--- catalog(old)
+++ catalog(new)
@@ -1,5 +1,5 @@
-version: v0.0.5
-sha: d21b9ae3ff2758902ec42e6cc09da842318cc6d71d4d84ba58338a3333eb6685
+version: v0.0.6
+sha: d10bc42ff7f4d2dfbab7e9460d87e79aed1950f6734faf1636ad352f3ac5354b
 modules:
   base_module:
     display_name: Base Module
@@ -7,11 +7,13 @@
     description: Core System Access
     billing:
       prices:
-        - interval: year
-          unit_amount: 0
-          nickname: base_module_free
-          lookup_key: base_module_free
+      - interval: year
+        unit_amount: 0
+        nickname: base_module_free
+        lookup_key: base_module_free
+        price_id: price_1Rj7WKQrAxy77qa7zm8okFhE
     audience: public
+    product_id: prod_SeQNFK7TFhCH5K
     personal_org: true
     include_with_trial: true
   compliance_module:
@@ -20,17 +22,20 @@
     description: Core Compliance Automation and Standards Library
     billing:
       prices:
-        - interval: month
-          unit_amount: 45000
-          nickname: price_compliance_monthly
-          lookup_key: price_compliance_monthly
-        - interval: year
-          unit_amount: 500000
-          nickname: price_compliance_annually
-          lookup_key: price_compliance_annually
+      - interval: month
+        unit_amount: 45000
+        nickname: price_compliance_monthly
+        lookup_key: price_compliance_monthly
+        price_id: price_1Rj7WLQrAxy77qa7TpgKZRK6
+      - interval: year
+        unit_amount: 500000
+        nickname: price_compliance_annually
+        lookup_key: price_compliance_annually
+        price_id: price_1Rj7WLQrAxy77qa7x7I2fIkm
     audience: public
     usage:
       evidence_storage_gb: 25000
+    product_id: prod_SeQNBPhSEM6pZw
     include_with_trial: true
   entity_management_module:
     display_name: Vendor Risk Management
@@ -38,45 +43,54 @@
     description: Vendor Risk Management
     billing:
       prices:
-        - interval: month
-          unit_amount: 20000
-          nickname: price_entity_monthly
-          lookup_key: price_entity_monthly
-        - interval: year
-          unit_amount: 200000
-          nickname: price_vendor_annually
-          lookup_key: price_vendor_annually
+      - interval: month
+        unit_amount: 20000
+        nickname: price_entity_monthly
+        lookup_key: price_entity_monthly
+        price_id: price_1Rj7WMQrAxy77qa7HVbosvRE
+      - interval: year
+        unit_amount: 200000
+        nickname: price_vendor_annually
+        lookup_key: price_vendor_annually
+        price_id: price_1Rj7WNQrAxy77qa7PO4XII8e
     audience: private
+    product_id: prod_SeQN9eOdITFyEL
   trust_center_module:
     display_name: Trust Center
     lookup_key: trust_center_module
     description: Public Security Portal
     billing:
       prices:
-        - interval: month
-          unit_amount: 30000
-          nickname: price_trustcenter_monthly
-          lookup_key: price_trustcenter_monthly
-        - interval: year
-          unit_amount: 300000
-          nickname: price_trustcenter_annually
-          lookup_key: price_trustcenter_annually
+      - interval: month
+        unit_amount: 30000
+        nickname: price_trustcenter_monthly
+        lookup_key: price_trustcenter_monthly
+        price_id: price_1Rj7WGQrAxy77qa7vP3pArda
+      - interval: year
+        unit_amount: 300000
+        nickname: price_trustcenter_annually
+        lookup_key: price_trustcenter_annually
+        price_id: price_1Rj7WGQrAxy77qa7e13ilZYl
     audience: beta
+    product_id: prod_SeQMrqIdGRyzOo
   vulnerability_management_module:
     display_name: Vulnerability Tracking and Management
     lookup_key: vulnerability_management_module
     description: Vulnerability Tracking and Management
     billing:
       prices:
-        - interval: month
-          unit_amount: 10000
-          nickname: price_vulnerability_mgmt_monthly
-          lookup_key: price_vulnerability_mgmt_monthly
-        - interval: year
-          unit_amount: 100000
-          nickname: price_vulnerability_mgmt_annually
-          lookup_key: price_vulnerability_mgmt_annually
+      - interval: month
+        unit_amount: 10000
+        nickname: price_vulnerability_mgmt_monthly
+        lookup_key: price_vulnerability_mgmt_monthly
+        price_id: price_1Rj7WIQrAxy77qa7iq26LyGr
+      - interval: year
+        unit_amount: 100000
+        nickname: price_vulnerability_mgmt_annually
+        lookup_key: price_vulnerability_mgmt_annually
+        price_id: price_1Rj7WIQrAxy77qa7HjEXbA99
     audience: private
+    product_id: prod_SeQN6ZyE2ssUjw
 addons:
   domain_scanning_addon:
     display_name: Domain and Vulnerability Scanning
@@ -84,56 +98,67 @@
     description: Domain and Vulnerability Scanning
     billing:
       prices:
-        - interval: month
-          unit_amount: 10000
-          nickname: domain_scan_monthly
-          lookup_key: domain_scan_monthly
-        - interval: year
-          unit_amount: 100000
-          nickname: domain_scan_annually
-          lookup_key: domain_scan_annually
+      - interval: month
+        unit_amount: 10000
+        nickname: domain_scan_monthly
+        lookup_key: domain_scan_monthly
+        price_id: price_1Rj7WOQrAxy77qa7bpyEd7JH
+      - interval: year
+        unit_amount: 100000
+        nickname: domain_scan_annually
+        lookup_key: domain_scan_annually
+        price_id: price_1Rj7WPQrAxy77qa7AQ4HKz4K
     audience: private
+    product_id: prod_SeQNr2n1jkKU6t
   extra_evidence_storage_addon:
     display_name: Additional Evidence Storage (100GB)
     lookup_key: extra_evidence_storage_addon
     description: Additional Evidence Storage for Compliance Evidence and Documents (100GB)
     billing:
       prices:
-        - interval: month
-          unit_amount: 1000
-          nickname: extra_evidence_storage_100gb
-          lookup_key: extra_evidence_storage_100gb
-          metadata:
-            increment_gb: "100"
+      - interval: month
+        unit_amount: 1000
+        nickname: extra_evidence_storage_100gb
+        lookup_key: extra_evidence_storage_100gb
+        metadata:
+          increment_gb: "100"
+        price_id: price_1Rj7WQQrAxy77qa7Da7Wiog7
     audience: public
+    product_id: prod_SeQNyCrFdvkB0N
   policy_management_addon:
     display_name: Policy and Procedure Management
     lookup_key: policy_management_addon
     description: Policy and Procedure Management
     billing:
       prices:
-        - interval: month
-          unit_amount: 1000
-          nickname: price_policy_mgmt_monthly
-          lookup_key: price_policy_mgmt_monthly
-        - interval: month
-          unit_amount: 10000
-          nickname: price_policy_mgmt_annually
-          lookup_key: price_policy_mgmt_annually
+      - interval: month
+        unit_amount: 1000
+        nickname: price_policy_mgmt_monthly
+        lookup_key: price_policy_mgmt_monthly
+        price_id: price_1Rj7WRQrAxy77qa7XAoCYdml
+      - interval: month
+        unit_amount: 10000
+        nickname: price_policy_mgmt_annually
+        lookup_key: price_policy_mgmt_annually
+        price_id: price_1Rj7WSQrAxy77qa73XxQP7b1
     audience: beta
+    product_id: prod_SeQNZsEGuTWqd6
   risk_management_addon:
     display_name: Risk Management
     lookup_key: risk_management_addon
     description: Risk Management
     billing:
       prices:
-        - interval: month
-          unit_amount: 1000
-          nickname: price_risk_mgmt_monthly
-          lookup_key: price_risk_mgmt_monthly
-        - interval: year
-          unit_amount: 10000
-          nickname: price_risk_mgmt_annually
-          lookup_key: price_risk_mgmt_annually
+      - interval: month
+        unit_amount: 1000
+        nickname: price_risk_mgmt_monthly
+        lookup_key: price_risk_mgmt_monthly
+        price_id: price_1Rj7WTQrAxy77qa7qCaZQUYK
+      - interval: year
+        unit_amount: 10000
+        nickname: price_risk_mgmt_annually
+        lookup_key: price_risk_mgmt_annually
+        price_id: price_1Rj7WTQrAxy77qa7QwqyTYwu
     audience: beta
+    product_id: prod_SeQNAVomfOKW6D

Documentation

Overview

Package catalog provides the core functionality for managing and validating the catalog of products, features, and prices in the Openlane project

Index

Constants

View Source
const ManagedByKey = "managed_by"

ManagedByKey is the metadata key applied to Stripe resources created via the catalog

View Source
const ManagedByValue = "module-manager"

ManagedByValue identifies objects managed by the catalog automation

Variables

View Source
var (
	// ErrCatalogValidationFailed is returned when the catalog fails validation
	ErrCatalogValidationFailed = errors.New("catalog validation failed")
	// ErrProductMissingFeature is returned when a product is missing a required feature
	ErrProductMissingFeature = errors.New("product missing required feature")
	// ErrYamlToJSONConversion = errors.New("failed to convert YAML to JSON for catalog validation")
	ErrYamlToJSONConversion = errors.New("failed to convert YAML to JSON for catalog validation")
	// ErrMatchingPriceNotFound = errors.New("matching price not found for feature")
	ErrMatchingPriceNotFound = errors.New("matching price not found for feature")
	// ErrFailedToCreateProduct = errors.New("failed to create product in Stripe")
	ErrFailedToCreateProduct = errors.New("failed to create product in Stripe")
	// ErrFailedToCreatePrice = errors.New("failed to create price in Stripe")
	ErrFailedToCreatePrice = errors.New("failed to create price in Stripe")
	// ErrContextandClientRequired = errors.New("context and client are required for catalog operations"
	ErrContextandClientRequired = errors.New("context and client are required for catalog operations")
	// ErrLookupKeyConflict indicates a price lookup key already exists in Stripe
	ErrLookupKeyConflict = errors.New("lookup key conflict")
)

Functions

This section is empty.

Types

type Billing

type Billing = models.Billing

type Catalog

type Catalog struct {
	// embed the base catalog model
	models.Catalog
}

Catalog wraps models.Catalog

func LoadCatalog

func LoadCatalog(path string) (*Catalog, error)

LoadCatalog reads and parses a Catalog definition from disk.

func New added in v0.52.0

func New() *Catalog

New creates a new empty Catalog instance

func (*Catalog) EnsurePrices

func (c *Catalog) EnsurePrices(ctx context.Context, sc *entitlements.StripeClient, currency string) error

EnsurePrices verifies prices exist in Stripe and creates them when missing. New products are created using the feature display name and description. Matching is performed by unit amount, interval, nickname, lookup key and metadata instead of a fixed price ID. The discovered Stripe price ID is stored back in the catalog struct but not persisted to disk.

func (*Catalog) IsCurrent

func (c *Catalog) IsCurrent() bool

IsCurrent reports whether the catalog SHA matches its version.

func (*Catalog) LookupKeyConflicts added in v0.23.0

func (c *Catalog) LookupKeyConflicts(ctx context.Context, sc lookupClient, opts ...LookupKeyCheckOption) ([]LookupKeyConflict, error)

LookupKeyConflicts checks the catalog for any lookup key conflicts with existing Stripe products, features, or prices. It returns a slice of LookupKeyConflict structs describing any conflicts found

func (*Catalog) SaveCatalog

func (c *Catalog) SaveCatalog(path string) (string, error)

SaveCatalog writes the catalog to disk in YAML format, as well as computing and updating the SHA

func (*Catalog) ValidatePrices

func (c *Catalog) ValidatePrices(ctx context.Context, sc *entitlements.StripeClient) error

ValidatePrices ensures every feature's price attributes match a Stripe price. Matching considers unit amount, interval, nickname, lookup key and metadata.

func (*Catalog) Visible

func (c *Catalog) Visible(audience string) *Catalog

Visible returns modules and addons filtered by audience

type Feature

type Feature = models.Feature

type FeatureSet

type FeatureSet = models.FeatureSet

type LookupKeyCheckOption added in v0.23.0

type LookupKeyCheckOption func(*lookupKeyCheckConfig)

LookupKeyCheckOption configures lookup key conflict checks

func WithFailFast added in v0.23.0

func WithFailFast(f bool) LookupKeyCheckOption

WithFailFast stops checking after the first conflict is found

type LookupKeyConflict added in v0.23.0

type LookupKeyConflict struct {
	Feature   string
	Resource  string
	LookupKey string
	ID        string
}

LookupKeyConflict describes an existing Stripe price using a lookup key

type Price

type Price = models.ItemPrice

type Usage

type Usage = models.Usage

Directories

Path Synopsis
Package gencatalog is the output of parsing the catalog file
Package gencatalog is the output of parsing the catalog file
Package genjsonschema generates JSON schema files from Go structs for use in validating catalogs
Package genjsonschema generates JSON schema files from Go structs for use in validating catalogs
Package main inside of genyaml generates Go source files from a catalog YAML file
Package main inside of genyaml generates Go source files from a catalog YAML file

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL