{
  "description": "ClusterCatalog makes File-Based Catalog (FBC) data available to your cluster.\nFor more information on FBC, see https://olm.operatorframework.io/docs/reference/file-based-catalogs/#docs",
  "properties": {
    "apiVersion": {
      "description": "APIVersion defines the versioned schema of this representation of an object.\nServers should convert recognized schemas to the latest internal value, and\nmay reject unrecognized values.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources",
      "type": "string"
    },
    "kind": {
      "description": "Kind is a string value representing the REST resource this object represents.\nServers may infer this from the endpoint the client submits requests to.\nCannot be updated.\nIn CamelCase.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds",
      "type": "string"
    },
    "metadata": {
      "type": "object"
    },
    "spec": {
      "description": "spec is a required field that defines the desired state of the ClusterCatalog.\nThe controller ensures that the catalog is unpacked and served over the catalog content HTTP server.",
      "properties": {
        "availabilityMode": {
          "default": "Available",
          "description": "availabilityMode is an optional field that defines how the ClusterCatalog is made available to clients on the cluster.\n\nAllowed values are \"Available\", \"Unavailable\", or omitted.\n\nWhen omitted, the default value is \"Available\".\n\nWhen set to \"Available\", the catalog contents are unpacked and served over the catalog content HTTP server.\nClients should consider this ClusterCatalog and its contents as usable.\n\nWhen set to \"Unavailable\", the catalog contents are no longer served over the catalog content HTTP server.\nTreat this the same as if the ClusterCatalog does not exist.\nUse \"Unavailable\" when you want to keep the ClusterCatalog but treat it as if it doesn't exist.",
          "enum": [
            "Unavailable",
            "Available"
          ],
          "type": "string"
        },
        "priority": {
          "default": 0,
          "description": "priority is an optional field that defines a priority for this ClusterCatalog.\n\nClients use the ClusterCatalog priority as a tie-breaker between ClusterCatalogs that meet their requirements.\nHigher numbers mean higher priority.\n\nClients decide how to handle scenarios where multiple ClusterCatalogs with the same priority meet their requirements.\nClients should prompt users for additional input to break the tie.\n\nWhen omitted, the default priority is 0.\n\nUse negative numbers to specify a priority lower than the default.\nUse positive numbers to specify a priority higher than the default.\n\nThe lowest possible value is -2147483648.\nThe highest possible value is 2147483647.",
          "format": "int32",
          "maximum": 2147483647,
          "minimum": -2147483648,
          "type": "integer"
        },
        "source": {
          "description": "source is a required field that defines the source of a catalog.\nA catalog contains information on content that can be installed on a cluster.\nThe catalog source makes catalog contents discoverable and usable by other on-cluster components.\nThese components can present the content in a GUI dashboard or install content from the catalog on the cluster.\nThe catalog source must contain catalog metadata in the File-Based Catalog (FBC) format.\nFor more information on FBC, see https://olm.operatorframework.io/docs/reference/file-based-catalogs/#docs.\n\nBelow is a minimal example of a ClusterCatalogSpec that sources a catalog from an image:\n\n source:\n   type: Image\n   image:\n     ref: quay.io/operatorhubio/catalog:latest",
          "properties": {
            "image": {
              "description": "image configures how catalog contents are sourced from an OCI image.\nIt is required when type is Image, and forbidden otherwise.",
              "properties": {
                "pollIntervalMinutes": {
                  "description": "pollIntervalMinutes is an optional field that sets the interval, in minutes, at which the image source is polled for new content.\nYou cannot specify pollIntervalMinutes when ref is a digest-based reference.\n\nWhen omitted, the image is not polled for new content.",
                  "minimum": 1,
                  "type": "integer"
                },
                "ref": {
                  "description": "ref is a required field that defines the reference to a container image containing catalog contents.\nIt cannot be more than 1000 characters.\n\nA reference has 3 parts: the domain, name, and identifier.\n\nThe domain is typically the registry where an image is located.\nIt must be alphanumeric characters (lowercase and uppercase) separated by the \".\" character.\nHyphenation is allowed, but the domain must start and end with alphanumeric characters.\nSpecifying a port to use is also allowed by adding the \":\" character followed by numeric values.\nThe port must be the last value in the domain.\nSome examples of valid domain values are \"registry.mydomain.io\", \"quay.io\", \"my-registry.io:8080\".\n\nThe name is typically the repository in the registry where an image is located.\nIt must contain lowercase alphanumeric characters separated only by the \".\", \"_\", \"__\", \"-\" characters.\nMultiple names can be concatenated with the \"/\" character.\nThe domain and name are combined using the \"/\" character.\nSome examples of valid name values are \"operatorhubio/catalog\", \"catalog\", \"my-catalog.prod\".\nAn example of the domain and name parts of a reference being combined is \"quay.io/operatorhubio/catalog\".\n\nThe identifier is typically the tag or digest for an image reference and is present at the end of the reference.\nIt starts with a separator character used to distinguish the end of the name and beginning of the identifier.\nFor a digest-based reference, the \"@\" character is the separator.\nFor a tag-based reference, the \":\" character is the separator.\nAn identifier is required in the reference.\n\nDigest-based references must contain an algorithm reference immediately after the \"@\" separator.\nThe algorithm reference must be followed by the \":\" character and an encoded string.\nThe algorithm must start with an uppercase or lowercase alpha character followed by alphanumeric characters and may contain the \"-\", \"_\", \"+\", and \".\" characters.\nSome examples of valid algorithm values are \"sha256\", \"sha256+b64u\", \"multihash+base58\".\nThe encoded string following the algorithm must be hex digits (a-f, A-F, 0-9) and must be a minimum of 32 characters.\n\nTag-based references must begin with a word character (alphanumeric + \"_\") followed by word characters or \".\", and \"-\" characters.\nThe tag must not be longer than 127 characters.\n\nAn example of a valid digest-based image reference is \"quay.io/operatorhubio/catalog@sha256:200d4ddb2a73594b91358fe6397424e975205bfbe44614f5846033cad64b3f05\"\nAn example of a valid tag-based image reference is \"quay.io/operatorhubio/catalog:latest\"",
                  "maxLength": 1000,
                  "type": "string",
                  "x-kubernetes-validations": [
                    {
                      "message": "must start with a valid domain. valid domains must be alphanumeric characters (lowercase and uppercase) separated by the \".\" character.",
                      "rule": "self.matches('^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])((\\\\.([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9]))+)?(:[0-9]+)?\\\\b')"
                    },
                    {
                      "message": "a valid name is required. valid names must contain lowercase alphanumeric characters separated only by the \".\", \"_\", \"__\", \"-\" characters.",
                      "rule": "self.find('(\\\\/[a-z0-9]+((([._]|__|[-]*)[a-z0-9]+)+)?((\\\\/[a-z0-9]+((([._]|__|[-]*)[a-z0-9]+)+)?)+)?)') != \"\""
                    },
                    {
                      "message": "must end with a digest or a tag",
                      "rule": "self.find('(@.*:)') != \"\" || self.find(':.*$') != \"\""
                    },
                    {
                      "message": "tag is invalid. the tag must not be more than 127 characters",
                      "rule": "self.find('(@.*:)') == \"\" ? (self.find(':.*$') != \"\" ? self.find(':.*$').substring(1).size() <= 127 : true) : true"
                    },
                    {
                      "message": "tag is invalid. valid tags must begin with a word character (alphanumeric + \"_\") followed by word characters or \".\", and \"-\" characters",
                      "rule": "self.find('(@.*:)') == \"\" ? (self.find(':.*$') != \"\" ? self.find(':.*$').matches(':[\\\\w][\\\\w.-]*$') : true) : true"
                    },
                    {
                      "message": "digest algorithm is not valid. valid algorithms must start with an uppercase or lowercase alpha character followed by alphanumeric characters and may contain the \"-\", \"_\", \"+\", and \".\" characters.",
                      "rule": "self.find('(@.*:)') != \"\" ? self.find('(@.*:)').matches('(@[A-Za-z][A-Za-z0-9]*([-_+.][A-Za-z][A-Za-z0-9]*)*[:])') : true"
                    },
                    {
                      "message": "digest is not valid. the encoded string must be at least 32 characters",
                      "rule": "self.find('(@.*:)') != \"\" ? self.find(':.*$').substring(1).size() >= 32 : true"
                    },
                    {
                      "message": "digest is not valid. the encoded string must only contain hex characters (A-F, a-f, 0-9)",
                      "rule": "self.find('(@.*:)') != \"\" ? self.find(':.*$').matches(':[0-9A-Fa-f]*$') : true"
                    }
                  ]
                }
              },
              "required": [
                "ref"
              ],
              "type": "object",
              "x-kubernetes-validations": [
                {
                  "message": "cannot specify pollIntervalMinutes while using digest-based image",
                  "rule": "self.ref.find('(@.*:)') != \"\" ? !has(self.pollIntervalMinutes) : true"
                }
              ],
              "additionalProperties": false
            },
            "type": {
              "description": "type is a required field that specifies the type of source for the catalog.\n\nThe only allowed value is \"Image\".\n\nWhen set to \"Image\", the ClusterCatalog content is sourced from an OCI image.\nWhen using an image source, the image field must be set and must be the only field defined for this type.",
              "enum": [
                "Image"
              ],
              "type": "string"
            }
          },
          "required": [
            "type"
          ],
          "type": "object",
          "x-kubernetes-validations": [
            {
              "message": "image is required when source type is Image, and forbidden otherwise",
              "rule": "has(self.type) && self.type == 'Image' ? has(self.image) : !has(self.image)"
            }
          ],
          "additionalProperties": false
        }
      },
      "required": [
        "source"
      ],
      "type": "object",
      "additionalProperties": false
    },
    "status": {
      "description": "status contains the following information about the state of the ClusterCatalog:\n  - Whether the catalog contents are being served via the catalog content HTTP server\n  - Whether the ClusterCatalog is progressing to a new state\n  - A reference to the source from which the catalog contents were retrieved",
      "properties": {
        "conditions": {
          "description": "conditions represents the current state of this ClusterCatalog.\n\nThe current condition types are Serving and Progressing.\n\nThe Serving condition represents whether the catalog contents are being served via the HTTP(S) web server:\n  - When status is True and reason is Available, the catalog contents are being served.\n  - When status is False and reason is Unavailable, the catalog contents are not being served because the contents are not yet available.\n  - When status is False and reason is UserSpecifiedUnavailable, the catalog contents are not being served because the catalog has been intentionally marked as unavailable.\n\nThe Progressing condition represents whether the ClusterCatalog is progressing or is ready to progress towards a new state:\n  - When status is True and reason is Retrying, an error occurred that may be resolved on subsequent reconciliation attempts.\n  - When status is True and reason is Succeeded, the ClusterCatalog has successfully progressed to a new state and is ready to continue progressing.\n  - When status is False and reason is Blocked, an error occurred that requires manual intervention for recovery.\n\nIf the system initially fetched contents and polling identifies updates, both conditions can be active simultaneously:\n  - The Serving condition remains True with reason Available because the previous contents are still served via the HTTP(S) web server.\n  - The Progressing condition is True with reason Retrying because the system is working to serve the new version.",
          "items": {
            "description": "Condition contains details for one aspect of the current state of this API Resource.",
            "properties": {
              "lastTransitionTime": {
                "description": "lastTransitionTime is the last time the condition transitioned from one status to another.\nThis should be when the underlying condition changed.  If that is not known, then using the time when the API field changed is acceptable.",
                "format": "date-time",
                "type": "string"
              },
              "message": {
                "description": "message is a human readable message indicating details about the transition.\nThis may be an empty string.",
                "maxLength": 32768,
                "type": "string"
              },
              "observedGeneration": {
                "description": "observedGeneration represents the .metadata.generation that the condition was set based upon.\nFor instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\nwith respect to the current state of the instance.",
                "format": "int64",
                "minimum": 0,
                "type": "integer"
              },
              "reason": {
                "description": "reason contains a programmatic identifier indicating the reason for the condition's last transition.\nProducers of specific condition types may define expected values and meanings for this field,\nand whether the values are considered a guaranteed API.\nThe value should be a CamelCase string.\nThis field may not be empty.",
                "maxLength": 1024,
                "minLength": 1,
                "pattern": "^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$",
                "type": "string"
              },
              "status": {
                "description": "status of the condition, one of True, False, Unknown.",
                "enum": [
                  "True",
                  "False",
                  "Unknown"
                ],
                "type": "string"
              },
              "type": {
                "description": "type of condition in CamelCase or in foo.example.com/CamelCase.",
                "maxLength": 316,
                "pattern": "^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$",
                "type": "string"
              }
            },
            "required": [
              "lastTransitionTime",
              "message",
              "reason",
              "status",
              "type"
            ],
            "type": "object",
            "additionalProperties": false
          },
          "type": "array",
          "x-kubernetes-list-map-keys": [
            "type"
          ],
          "x-kubernetes-list-type": "map"
        },
        "lastUnpacked": {
          "description": "lastUnpacked represents the last time the catalog contents were extracted from their source format.\nFor example, when using an Image source, the OCI image is pulled and image layers are written to a file-system backed cache.\nThis extraction from the source format is called \"unpacking\".",
          "format": "date-time",
          "type": "string"
        },
        "resolvedSource": {
          "description": "resolvedSource contains information about the resolved source based on the source type.",
          "properties": {
            "image": {
              "description": "image contains resolution information for a catalog sourced from an image.\nIt must be set when type is Image, and forbidden otherwise.",
              "properties": {
                "ref": {
                  "description": "ref contains the resolved image digest-based reference.\nThe digest format allows you to use other tooling to fetch the exact OCI manifests\nthat were used to extract the catalog contents.",
                  "maxLength": 1000,
                  "type": "string",
                  "x-kubernetes-validations": [
                    {
                      "message": "must start with a valid domain. valid domains must be alphanumeric characters (lowercase and uppercase) separated by the \".\" character.",
                      "rule": "self.matches('^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])((\\\\.([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9]))+)?(:[0-9]+)?\\\\b')"
                    },
                    {
                      "message": "a valid name is required. valid names must contain lowercase alphanumeric characters separated only by the \".\", \"_\", \"__\", \"-\" characters.",
                      "rule": "self.find('(\\\\/[a-z0-9]+((([._]|__|[-]*)[a-z0-9]+)+)?((\\\\/[a-z0-9]+((([._]|__|[-]*)[a-z0-9]+)+)?)+)?)') != \"\""
                    },
                    {
                      "message": "must end with a digest",
                      "rule": "self.find('(@.*:)') != \"\""
                    },
                    {
                      "message": "digest algorithm is not valid. valid algorithms must start with an uppercase or lowercase alpha character followed by alphanumeric characters and may contain the \"-\", \"_\", \"+\", and \".\" characters.",
                      "rule": "self.find('(@.*:)') != \"\" ? self.find('(@.*:)').matches('(@[A-Za-z][A-Za-z0-9]*([-_+.][A-Za-z][A-Za-z0-9]*)*[:])') : true"
                    },
                    {
                      "message": "digest is not valid. the encoded string must be at least 32 characters",
                      "rule": "self.find('(@.*:)') != \"\" ? self.find(':.*$').substring(1).size() >= 32 : true"
                    },
                    {
                      "message": "digest is not valid. the encoded string must only contain hex characters (A-F, a-f, 0-9)",
                      "rule": "self.find('(@.*:)') != \"\" ? self.find(':.*$').matches(':[0-9A-Fa-f]*$') : true"
                    }
                  ]
                }
              },
              "required": [
                "ref"
              ],
              "type": "object",
              "additionalProperties": false
            },
            "type": {
              "description": "type is a required field that specifies the type of source for the catalog.\n\nThe only allowed value is \"Image\".\n\nWhen set to \"Image\", information about the resolved image source is set in the image field.",
              "enum": [
                "Image"
              ],
              "type": "string"
            }
          },
          "required": [
            "image",
            "type"
          ],
          "type": "object",
          "x-kubernetes-validations": [
            {
              "message": "image is required when source type is Image, and forbidden otherwise",
              "rule": "has(self.type) && self.type == 'Image' ? has(self.image) : !has(self.image)"
            }
          ],
          "additionalProperties": false
        },
        "urls": {
          "description": "urls contains the URLs that can be used to access the catalog.",
          "properties": {
            "base": {
              "description": "base is a cluster-internal URL that provides endpoints for accessing the catalog content.\n\nClients should append the path for the endpoint they want to access.\n\nCurrently, only a single endpoint is served and is accessible at the path /api/v1.\n\nThe endpoints served for the v1 API are:\n  - /all - this endpoint returns the entire catalog contents in the FBC format\n\nNew endpoints may be added as needs evolve.",
              "maxLength": 525,
              "type": "string",
              "x-kubernetes-validations": [
                {
                  "message": "must be a valid URL",
                  "rule": "isURL(self)"
                },
                {
                  "message": "scheme must be either http or https",
                  "rule": "isURL(self) ? (url(self).getScheme() == \"http\" || url(self).getScheme() == \"https\") : true"
                }
              ]
            }
          },
          "required": [
            "base"
          ],
          "type": "object",
          "additionalProperties": false
        }
      },
      "type": "object",
      "additionalProperties": false
    }
  },
  "required": [
    "metadata",
    "spec"
  ],
  "type": "object"
}
