diff --git a/staging/operator-lifecycle-manager/.github/workflows/e2e-tests.yml b/staging/operator-lifecycle-manager/.github/workflows/e2e-tests.yml index 27174e425e..18b857ae66 100644 --- a/staging/operator-lifecycle-manager/.github/workflows/e2e-tests.yml +++ b/staging/operator-lifecycle-manager/.github/workflows/e2e-tests.yml @@ -4,6 +4,9 @@ on: branches: - master pull_request: + paths: + - '**' + - '!doc/**' jobs: run-e2e-tests: runs-on: ubuntu-latest diff --git a/staging/operator-lifecycle-manager/.github/workflows/quickstart.yml b/staging/operator-lifecycle-manager/.github/workflows/quickstart.yml index eeee62a248..9e6f6bf167 100644 --- a/staging/operator-lifecycle-manager/.github/workflows/quickstart.yml +++ b/staging/operator-lifecycle-manager/.github/workflows/quickstart.yml @@ -20,4 +20,24 @@ jobs: kubectl apply -f deploy/upstream/quickstart/crds.yaml kubectl wait --timeout=5m --for=condition=Established crd $(kubectl get crd --output=jsonpath='{.items[*].metadata.name}') kubectl apply -f deploy/upstream/quickstart/olm.yaml - kubectl wait --timeout=5m --for=condition=Available -n olm deploy olm-operator catalog-operator packageserver + + # Note(tflannag): `kubectl wait` does not support waiting for resource creation: https://github.com/kubernetes/kubernetes/pull/87399. + wait_for_deployment() { + local deployment_name=$1 + timeout=60 + i=1 + echo "Checking if the ${deployment_name} deployment is ready" + until kubectl -n olm get deployment ${deployment_name} -o jsonpath='{.status.conditions[?(@.status=="True")].type}' | grep "Available" 2>/dev/null; do + ((i++)) + if [[ ${i} -gt ${timeout} ]]; then + echo "the ${deployment_name} deployment has not become ready before the timeout period" + exit 1 + fi + echo "waiting for ${deployment_name} deployment to report a ready status" + sleep 5 + done + echo "The ${deployment_name} deployment is ready" + } + wait_for_deployment catalog-operator + wait_for_deployment olm-operator + wait_for_deployment packageserver diff --git a/staging/operator-lifecycle-manager/deploy/upstream/manifests/0.18.3/0000_50_olm_00-catalogsources.crd.yaml b/staging/operator-lifecycle-manager/deploy/upstream/manifests/0.18.3/0000_50_olm_00-catalogsources.crd.yaml new file mode 100644 index 0000000000..2c1efc61dd --- /dev/null +++ b/staging/operator-lifecycle-manager/deploy/upstream/manifests/0.18.3/0000_50_olm_00-catalogsources.crd.yaml @@ -0,0 +1,169 @@ +--- +# Source: olm/crds/0000_50_olm_00-catalogsources.crd.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.4.1 + creationTimestamp: null + name: catalogsources.operators.coreos.com +spec: + group: operators.coreos.com + names: + categories: + - olm + kind: CatalogSource + listKind: CatalogSourceList + plural: catalogsources + shortNames: + - catsrc + singular: catalogsource + scope: Namespaced + versions: + - additionalPrinterColumns: + - description: The pretty name of the catalog + jsonPath: .spec.displayName + name: Display + type: string + - description: The type of the catalog + jsonPath: .spec.sourceType + name: Type + type: string + - description: The publisher of the catalog + jsonPath: .spec.publisher + name: Publisher + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: CatalogSource is a repository of CSVs, CRDs, and operator packages. + type: object + required: + - metadata + - spec + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More 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. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + type: object + required: + - sourceType + properties: + address: + description: 'Address is a host that OLM can use to connect to a pre-existing registry. Format: : Only used when SourceType = SourceTypeGrpc. Ignored when the Image field is set.' + type: string + configMap: + description: ConfigMap is the name of the ConfigMap to be used to back a configmap-server registry. Only used when SourceType = SourceTypeConfigmap or SourceTypeInternal. + type: string + description: + type: string + displayName: + description: Metadata + type: string + icon: + type: object + required: + - base64data + - mediatype + properties: + base64data: + type: string + mediatype: + type: string + image: + description: Image is an operator-registry container image to instantiate a registry-server with. Only used when SourceType = SourceTypeGrpc. If present, the address field is ignored. + type: string + priority: + description: 'Priority field assigns a weight to the catalog source to prioritize them so that it can be consumed by the dependency resolver. Usage: Higher weight indicates that this catalog source is preferred over lower weighted catalog sources during dependency resolution. The range of the priority value can go from positive to negative in the range of int32. The default value to a catalog source with unassigned priority would be 0. The catalog source with the same priority values will be ranked lexicographically based on its name.' + type: integer + publisher: + type: string + secrets: + description: Secrets represent set of secrets that can be used to access the contents of the catalog. It is best to keep this list small, since each will need to be tried for every catalog entry. + type: array + items: + type: string + sourceType: + description: SourceType is the type of source + type: string + updateStrategy: + description: UpdateStrategy defines how updated catalog source images can be discovered Consists of an interval that defines polling duration and an embedded strategy type + type: object + properties: + registryPoll: + type: object + properties: + interval: + description: Interval is used to determine the time interval between checks of the latest catalog source version. The catalog operator polls to see if a new version of the catalog source is available. If available, the latest image is pulled and gRPC traffic is directed to the latest catalog source. + type: string + status: + type: object + properties: + configMapReference: + type: object + required: + - name + - namespace + properties: + lastUpdateTime: + type: string + format: date-time + name: + type: string + namespace: + type: string + resourceVersion: + type: string + uid: + description: UID is a type that holds unique ID values, including UUIDs. Because we don't ONLY use UUIDs, this is an alias to string. Being a type captures intent and helps make sure that UIDs and names do not get conflated. + type: string + connectionState: + type: object + required: + - lastObservedState + properties: + address: + type: string + lastConnect: + type: string + format: date-time + lastObservedState: + type: string + latestImageRegistryPoll: + description: The last time the CatalogSource image registry has been polled to ensure the image is up-to-date + type: string + format: date-time + message: + description: A human readable message indicating details about why the CatalogSource is in this condition. + type: string + reason: + description: Reason is the reason the CatalogSource was transitioned to its current state. + type: string + registryService: + type: object + properties: + createdAt: + type: string + format: date-time + port: + type: string + protocol: + type: string + serviceName: + type: string + serviceNamespace: + type: string + served: true + storage: true + subresources: + status: {} + diff --git a/staging/operator-lifecycle-manager/deploy/upstream/manifests/0.18.3/0000_50_olm_00-clusterserviceversions.crd.yaml b/staging/operator-lifecycle-manager/deploy/upstream/manifests/0.18.3/0000_50_olm_00-clusterserviceversions.crd.yaml new file mode 100644 index 0000000000..394924902a --- /dev/null +++ b/staging/operator-lifecycle-manager/deploy/upstream/manifests/0.18.3/0000_50_olm_00-clusterserviceversions.crd.yaml @@ -0,0 +1,4841 @@ +--- +# Source: olm/crds/0000_50_olm_00-clusterserviceversions.crd.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.4.1 + creationTimestamp: null + name: clusterserviceversions.operators.coreos.com +spec: + group: operators.coreos.com + names: + categories: + - olm + kind: ClusterServiceVersion + listKind: ClusterServiceVersionList + plural: clusterserviceversions + shortNames: + - csv + - csvs + singular: clusterserviceversion + scope: Namespaced + versions: + - additionalPrinterColumns: + - description: The name of the CSV + jsonPath: .spec.displayName + name: Display + type: string + - description: The version of the CSV + jsonPath: .spec.version + name: Version + type: string + - description: The name of a CSV that this one replaces + jsonPath: .spec.replaces + name: Replaces + type: string + - jsonPath: .status.phase + name: Phase + type: string + name: v1alpha1 + schema: + openAPIV3Schema: + description: ClusterServiceVersion is a Custom Resource of type `ClusterServiceVersionSpec`. + type: object + required: + - metadata + - spec + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More 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. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: ClusterServiceVersionSpec declarations tell OLM how to install an operator that can manage apps for a given version. + type: object + required: + - displayName + - install + properties: + annotations: + description: Annotations is an unstructured key value map stored with a resource that may be set by external tools to store and retrieve arbitrary metadata. + type: object + additionalProperties: + type: string + apiservicedefinitions: + description: APIServiceDefinitions declares all of the extension apis managed or required by an operator being ran by ClusterServiceVersion. + type: object + properties: + owned: + type: array + items: + description: APIServiceDescription provides details to OLM about apis provided via aggregation + type: object + required: + - group + - kind + - name + - version + properties: + actionDescriptors: + type: array + items: + description: ActionDescriptor describes a declarative action that can be performed on a custom resource instance + type: object + required: + - path + properties: + description: + type: string + displayName: + type: string + path: + type: string + value: + description: RawMessage is a raw encoded JSON value. It implements Marshaler and Unmarshaler and can be used to delay JSON decoding or precompute a JSON encoding. + type: string + format: byte + x-descriptors: + type: array + items: + type: string + containerPort: + type: integer + format: int32 + deploymentName: + type: string + description: + type: string + displayName: + type: string + group: + type: string + kind: + type: string + name: + type: string + resources: + type: array + items: + description: APIResourceReference is a Kubernetes resource type used by a custom resource + type: object + required: + - kind + - name + - version + properties: + kind: + type: string + name: + type: string + version: + type: string + specDescriptors: + type: array + items: + description: SpecDescriptor describes a field in a spec block of a CRD so that OLM can consume it + type: object + required: + - path + properties: + description: + type: string + displayName: + type: string + path: + type: string + value: + description: RawMessage is a raw encoded JSON value. It implements Marshaler and Unmarshaler and can be used to delay JSON decoding or precompute a JSON encoding. + type: string + format: byte + x-descriptors: + type: array + items: + type: string + statusDescriptors: + type: array + items: + description: StatusDescriptor describes a field in a status block of a CRD so that OLM can consume it + type: object + required: + - path + properties: + description: + type: string + displayName: + type: string + path: + type: string + value: + description: RawMessage is a raw encoded JSON value. It implements Marshaler and Unmarshaler and can be used to delay JSON decoding or precompute a JSON encoding. + type: string + format: byte + x-descriptors: + type: array + items: + type: string + version: + type: string + required: + type: array + items: + description: APIServiceDescription provides details to OLM about apis provided via aggregation + type: object + required: + - group + - kind + - name + - version + properties: + actionDescriptors: + type: array + items: + description: ActionDescriptor describes a declarative action that can be performed on a custom resource instance + type: object + required: + - path + properties: + description: + type: string + displayName: + type: string + path: + type: string + value: + description: RawMessage is a raw encoded JSON value. It implements Marshaler and Unmarshaler and can be used to delay JSON decoding or precompute a JSON encoding. + type: string + format: byte + x-descriptors: + type: array + items: + type: string + containerPort: + type: integer + format: int32 + deploymentName: + type: string + description: + type: string + displayName: + type: string + group: + type: string + kind: + type: string + name: + type: string + resources: + type: array + items: + description: APIResourceReference is a Kubernetes resource type used by a custom resource + type: object + required: + - kind + - name + - version + properties: + kind: + type: string + name: + type: string + version: + type: string + specDescriptors: + type: array + items: + description: SpecDescriptor describes a field in a spec block of a CRD so that OLM can consume it + type: object + required: + - path + properties: + description: + type: string + displayName: + type: string + path: + type: string + value: + description: RawMessage is a raw encoded JSON value. It implements Marshaler and Unmarshaler and can be used to delay JSON decoding or precompute a JSON encoding. + type: string + format: byte + x-descriptors: + type: array + items: + type: string + statusDescriptors: + type: array + items: + description: StatusDescriptor describes a field in a status block of a CRD so that OLM can consume it + type: object + required: + - path + properties: + description: + type: string + displayName: + type: string + path: + type: string + value: + description: RawMessage is a raw encoded JSON value. It implements Marshaler and Unmarshaler and can be used to delay JSON decoding or precompute a JSON encoding. + type: string + format: byte + x-descriptors: + type: array + items: + type: string + version: + type: string + cleanup: + description: Cleanup specifies the cleanup behaviour when the CSV gets deleted + type: object + required: + - enabled + properties: + enabled: + type: boolean + customresourcedefinitions: + description: "CustomResourceDefinitions declares all of the CRDs managed or required by an operator being ran by ClusterServiceVersion. \n If the CRD is present in the Owned list, it is implicitly required." + type: object + properties: + owned: + type: array + items: + description: CRDDescription provides details to OLM about the CRDs + type: object + required: + - kind + - name + - version + properties: + actionDescriptors: + type: array + items: + description: ActionDescriptor describes a declarative action that can be performed on a custom resource instance + type: object + required: + - path + properties: + description: + type: string + displayName: + type: string + path: + type: string + value: + description: RawMessage is a raw encoded JSON value. It implements Marshaler and Unmarshaler and can be used to delay JSON decoding or precompute a JSON encoding. + type: string + format: byte + x-descriptors: + type: array + items: + type: string + description: + type: string + displayName: + type: string + kind: + type: string + name: + type: string + resources: + type: array + items: + description: APIResourceReference is a Kubernetes resource type used by a custom resource + type: object + required: + - kind + - name + - version + properties: + kind: + type: string + name: + type: string + version: + type: string + specDescriptors: + type: array + items: + description: SpecDescriptor describes a field in a spec block of a CRD so that OLM can consume it + type: object + required: + - path + properties: + description: + type: string + displayName: + type: string + path: + type: string + value: + description: RawMessage is a raw encoded JSON value. It implements Marshaler and Unmarshaler and can be used to delay JSON decoding or precompute a JSON encoding. + type: string + format: byte + x-descriptors: + type: array + items: + type: string + statusDescriptors: + type: array + items: + description: StatusDescriptor describes a field in a status block of a CRD so that OLM can consume it + type: object + required: + - path + properties: + description: + type: string + displayName: + type: string + path: + type: string + value: + description: RawMessage is a raw encoded JSON value. It implements Marshaler and Unmarshaler and can be used to delay JSON decoding or precompute a JSON encoding. + type: string + format: byte + x-descriptors: + type: array + items: + type: string + version: + type: string + required: + type: array + items: + description: CRDDescription provides details to OLM about the CRDs + type: object + required: + - kind + - name + - version + properties: + actionDescriptors: + type: array + items: + description: ActionDescriptor describes a declarative action that can be performed on a custom resource instance + type: object + required: + - path + properties: + description: + type: string + displayName: + type: string + path: + type: string + value: + description: RawMessage is a raw encoded JSON value. It implements Marshaler and Unmarshaler and can be used to delay JSON decoding or precompute a JSON encoding. + type: string + format: byte + x-descriptors: + type: array + items: + type: string + description: + type: string + displayName: + type: string + kind: + type: string + name: + type: string + resources: + type: array + items: + description: APIResourceReference is a Kubernetes resource type used by a custom resource + type: object + required: + - kind + - name + - version + properties: + kind: + type: string + name: + type: string + version: + type: string + specDescriptors: + type: array + items: + description: SpecDescriptor describes a field in a spec block of a CRD so that OLM can consume it + type: object + required: + - path + properties: + description: + type: string + displayName: + type: string + path: + type: string + value: + description: RawMessage is a raw encoded JSON value. It implements Marshaler and Unmarshaler and can be used to delay JSON decoding or precompute a JSON encoding. + type: string + format: byte + x-descriptors: + type: array + items: + type: string + statusDescriptors: + type: array + items: + description: StatusDescriptor describes a field in a status block of a CRD so that OLM can consume it + type: object + required: + - path + properties: + description: + type: string + displayName: + type: string + path: + type: string + value: + description: RawMessage is a raw encoded JSON value. It implements Marshaler and Unmarshaler and can be used to delay JSON decoding or precompute a JSON encoding. + type: string + format: byte + x-descriptors: + type: array + items: + type: string + version: + type: string + description: + type: string + displayName: + type: string + icon: + type: array + items: + type: object + required: + - base64data + - mediatype + properties: + base64data: + type: string + mediatype: + type: string + install: + description: NamedInstallStrategy represents the block of an ClusterServiceVersion resource where the install strategy is specified. + type: object + required: + - strategy + properties: + spec: + description: StrategyDetailsDeployment represents the parsed details of a Deployment InstallStrategy. + type: object + required: + - deployments + properties: + clusterPermissions: + type: array + items: + description: StrategyDeploymentPermissions describe the rbac rules and service account needed by the install strategy + type: object + required: + - rules + - serviceAccountName + properties: + rules: + type: array + items: + description: PolicyRule holds information that describes a policy rule, but does not contain information about who the rule applies to or which namespace the rule applies to. + type: object + required: + - verbs + properties: + apiGroups: + description: APIGroups is the name of the APIGroup that contains the resources. If multiple API groups are specified, any action requested against one of the enumerated resources in any API group will be allowed. + type: array + items: + type: string + nonResourceURLs: + description: NonResourceURLs is a set of partial urls that a user should have access to. *s are allowed, but only as the full, final step in the path Since non-resource URLs are not namespaced, this field is only applicable for ClusterRoles referenced from a ClusterRoleBinding. Rules can either apply to API resources (such as "pods" or "secrets") or non-resource URL paths (such as "/api"), but not both. + type: array + items: + type: string + resourceNames: + description: ResourceNames is an optional white list of names that the rule applies to. An empty set means that everything is allowed. + type: array + items: + type: string + resources: + description: Resources is a list of resources this rule applies to. ResourceAll represents all resources. + type: array + items: + type: string + verbs: + description: Verbs is a list of Verbs that apply to ALL the ResourceKinds and AttributeRestrictions contained in this rule. VerbAll represents all kinds. + type: array + items: + type: string + serviceAccountName: + type: string + deployments: + type: array + items: + description: StrategyDeploymentSpec contains the name, spec and labels for the deployment ALM should create + type: object + required: + - name + - spec + properties: + label: + description: Set is a map of label:value. It implements Labels. + type: object + additionalProperties: + type: string + name: + type: string + spec: + description: DeploymentSpec is the specification of the desired behavior of the Deployment. + type: object + required: + - selector + - template + properties: + minReadySeconds: + description: Minimum number of seconds for which a newly created pod should be ready without any of its container crashing, for it to be considered available. Defaults to 0 (pod will be considered available as soon as it is ready) + type: integer + format: int32 + paused: + description: Indicates that the deployment is paused. + type: boolean + progressDeadlineSeconds: + description: The maximum time in seconds for a deployment to make progress before it is considered to be failed. The deployment controller will continue to process failed deployments and a condition with a ProgressDeadlineExceeded reason will be surfaced in the deployment status. Note that progress will not be estimated during the time a deployment is paused. Defaults to 600s. + type: integer + format: int32 + replicas: + description: Number of desired pods. This is a pointer to distinguish between explicit zero and not specified. Defaults to 1. + type: integer + format: int32 + revisionHistoryLimit: + description: The number of old ReplicaSets to retain to allow rollback. This is a pointer to distinguish between explicit zero and not specified. Defaults to 10. + type: integer + format: int32 + selector: + description: Label selector for pods. Existing ReplicaSets whose pods are selected by this will be the ones affected by this deployment. It must match the pod template's labels. + type: object + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + type: array + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + type: array + items: + type: string + matchLabels: + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + additionalProperties: + type: string + strategy: + description: The deployment strategy to use to replace existing pods with new ones. + type: object + properties: + rollingUpdate: + description: 'Rolling update config params. Present only if DeploymentStrategyType = RollingUpdate. --- TODO: Update this to follow our convention for oneOf, whatever we decide it to be.' + type: object + properties: + maxSurge: + description: 'The maximum number of pods that can be scheduled above the desired number of pods. Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). This can not be 0 if MaxUnavailable is 0. Absolute number is calculated from percentage by rounding up. Defaults to 25%. Example: when this is set to 30%, the new ReplicaSet can be scaled up immediately when the rolling update starts, such that the total number of old and new pods do not exceed 130% of desired pods. Once old pods have been killed, new ReplicaSet can be scaled up further, ensuring that total number of pods running at any time during the update is at most 130% of desired pods.' + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + maxUnavailable: + description: 'The maximum number of pods that can be unavailable during the update. Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). Absolute number is calculated from percentage by rounding down. This can not be 0 if MaxSurge is 0. Defaults to 25%. Example: when this is set to 30%, the old ReplicaSet can be scaled down to 70% of desired pods immediately when the rolling update starts. Once new pods are ready, old ReplicaSet can be scaled down further, followed by scaling up the new ReplicaSet, ensuring that the total number of pods available at all times during the update is at least 70% of desired pods.' + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: + description: Type of deployment. Can be "Recreate" or "RollingUpdate". Default is RollingUpdate. + type: string + template: + description: Template describes the pods that will be created. + type: object + properties: + metadata: + description: 'Standard object''s metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata' + type: object + x-kubernetes-preserve-unknown-fields: true + spec: + description: 'Specification of the desired behavior of the pod. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status' + type: object + required: + - containers + properties: + activeDeadlineSeconds: + description: Optional duration in seconds the pod may be active on the node relative to StartTime before the system will actively try to mark it failed and kill associated containers. Value must be a positive integer. + type: integer + format: int64 + affinity: + description: If specified, the pod's scheduling constraints + type: object + properties: + nodeAffinity: + description: Describes node affinity scheduling rules for the pod. + type: object + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: The scheduler will prefer to schedule pods to nodes that satisfy the affinity expressions specified by this field, but it may choose a node that violates one or more of the expressions. The node that is most preferred is the one with the greatest sum of weights, i.e. for each node that meets all of the scheduling requirements (resource request, requiredDuringScheduling affinity expressions, etc.), compute a sum by iterating through the elements of this field and adding "weight" to the sum if the node matches the corresponding matchExpressions; the node(s) with the highest sum are the most preferred. + type: array + items: + description: An empty preferred scheduling term matches all objects with implicit weight 0 (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + type: object + required: + - preference + - weight + properties: + preference: + description: A node selector term, associated with the corresponding weight. + type: object + properties: + matchExpressions: + description: A list of node selector requirements by node's labels. + type: array + items: + description: A node selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: The label key that the selector applies to. + type: string + operator: + description: Represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: An array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. If the operator is Gt or Lt, the values array must have a single element, which will be interpreted as an integer. This array is replaced during a strategic merge patch. + type: array + items: + type: string + matchFields: + description: A list of node selector requirements by node's fields. + type: array + items: + description: A node selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: The label key that the selector applies to. + type: string + operator: + description: Represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: An array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. If the operator is Gt or Lt, the values array must have a single element, which will be interpreted as an integer. This array is replaced during a strategic merge patch. + type: array + items: + type: string + weight: + description: Weight associated with matching the corresponding nodeSelectorTerm, in the range 1-100. + type: integer + format: int32 + requiredDuringSchedulingIgnoredDuringExecution: + description: If the affinity requirements specified by this field are not met at scheduling time, the pod will not be scheduled onto the node. If the affinity requirements specified by this field cease to be met at some point during pod execution (e.g. due to an update), the system may or may not try to eventually evict the pod from its node. + type: object + required: + - nodeSelectorTerms + properties: + nodeSelectorTerms: + description: Required. A list of node selector terms. The terms are ORed. + type: array + items: + description: A null or empty node selector term matches no objects. The requirements of them are ANDed. The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + type: object + properties: + matchExpressions: + description: A list of node selector requirements by node's labels. + type: array + items: + description: A node selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: The label key that the selector applies to. + type: string + operator: + description: Represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: An array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. If the operator is Gt or Lt, the values array must have a single element, which will be interpreted as an integer. This array is replaced during a strategic merge patch. + type: array + items: + type: string + matchFields: + description: A list of node selector requirements by node's fields. + type: array + items: + description: A node selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: The label key that the selector applies to. + type: string + operator: + description: Represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: An array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. If the operator is Gt or Lt, the values array must have a single element, which will be interpreted as an integer. This array is replaced during a strategic merge patch. + type: array + items: + type: string + podAffinity: + description: Describes pod affinity scheduling rules (e.g. co-locate this pod in the same node, zone, etc. as some other pod(s)). + type: object + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: The scheduler will prefer to schedule pods to nodes that satisfy the affinity expressions specified by this field, but it may choose a node that violates one or more of the expressions. The node that is most preferred is the one with the greatest sum of weights, i.e. for each node that meets all of the scheduling requirements (resource request, requiredDuringScheduling affinity expressions, etc.), compute a sum by iterating through the elements of this field and adding "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the node(s) with the highest sum are the most preferred. + type: array + items: + description: The weights of all of the matched WeightedPodAffinityTerm fields are added per-node to find the most preferred node(s) + type: object + required: + - podAffinityTerm + - weight + properties: + podAffinityTerm: + description: Required. A pod affinity term, associated with the corresponding weight. + type: object + required: + - topologyKey + properties: + labelSelector: + description: A label query over a set of resources, in this case pods. + type: object + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + type: array + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + type: array + items: + type: string + matchLabels: + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + additionalProperties: + type: string + namespaces: + description: namespaces specifies which namespaces the labelSelector applies to (matches against); null or empty list means "this pod's namespace" + type: array + items: + type: string + topologyKey: + description: This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching the labelSelector in the specified namespaces, where co-located is defined as running on a node whose value of the label with key topologyKey matches that of any node on which any of the selected pods is running. Empty topologyKey is not allowed. + type: string + weight: + description: weight associated with matching the corresponding podAffinityTerm, in the range 1-100. + type: integer + format: int32 + requiredDuringSchedulingIgnoredDuringExecution: + description: If the affinity requirements specified by this field are not met at scheduling time, the pod will not be scheduled onto the node. If the affinity requirements specified by this field cease to be met at some point during pod execution (e.g. due to a pod label update), the system may or may not try to eventually evict the pod from its node. When there are multiple elements, the lists of nodes corresponding to each podAffinityTerm are intersected, i.e. all terms must be satisfied. + type: array + items: + description: Defines a set of pods (namely those matching the labelSelector relative to the given namespace(s)) that this pod should be co-located (affinity) or not co-located (anti-affinity) with, where co-located is defined as running on a node whose value of the label with key matches that of any node on which a pod of the set of pods is running + type: object + required: + - topologyKey + properties: + labelSelector: + description: A label query over a set of resources, in this case pods. + type: object + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + type: array + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + type: array + items: + type: string + matchLabels: + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + additionalProperties: + type: string + namespaces: + description: namespaces specifies which namespaces the labelSelector applies to (matches against); null or empty list means "this pod's namespace" + type: array + items: + type: string + topologyKey: + description: This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching the labelSelector in the specified namespaces, where co-located is defined as running on a node whose value of the label with key topologyKey matches that of any node on which any of the selected pods is running. Empty topologyKey is not allowed. + type: string + podAntiAffinity: + description: Describes pod anti-affinity scheduling rules (e.g. avoid putting this pod in the same node, zone, etc. as some other pod(s)). + type: object + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: The scheduler will prefer to schedule pods to nodes that satisfy the anti-affinity expressions specified by this field, but it may choose a node that violates one or more of the expressions. The node that is most preferred is the one with the greatest sum of weights, i.e. for each node that meets all of the scheduling requirements (resource request, requiredDuringScheduling anti-affinity expressions, etc.), compute a sum by iterating through the elements of this field and adding "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the node(s) with the highest sum are the most preferred. + type: array + items: + description: The weights of all of the matched WeightedPodAffinityTerm fields are added per-node to find the most preferred node(s) + type: object + required: + - podAffinityTerm + - weight + properties: + podAffinityTerm: + description: Required. A pod affinity term, associated with the corresponding weight. + type: object + required: + - topologyKey + properties: + labelSelector: + description: A label query over a set of resources, in this case pods. + type: object + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + type: array + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + type: array + items: + type: string + matchLabels: + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + additionalProperties: + type: string + namespaces: + description: namespaces specifies which namespaces the labelSelector applies to (matches against); null or empty list means "this pod's namespace" + type: array + items: + type: string + topologyKey: + description: This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching the labelSelector in the specified namespaces, where co-located is defined as running on a node whose value of the label with key topologyKey matches that of any node on which any of the selected pods is running. Empty topologyKey is not allowed. + type: string + weight: + description: weight associated with matching the corresponding podAffinityTerm, in the range 1-100. + type: integer + format: int32 + requiredDuringSchedulingIgnoredDuringExecution: + description: If the anti-affinity requirements specified by this field are not met at scheduling time, the pod will not be scheduled onto the node. If the anti-affinity requirements specified by this field cease to be met at some point during pod execution (e.g. due to a pod label update), the system may or may not try to eventually evict the pod from its node. When there are multiple elements, the lists of nodes corresponding to each podAffinityTerm are intersected, i.e. all terms must be satisfied. + type: array + items: + description: Defines a set of pods (namely those matching the labelSelector relative to the given namespace(s)) that this pod should be co-located (affinity) or not co-located (anti-affinity) with, where co-located is defined as running on a node whose value of the label with key matches that of any node on which a pod of the set of pods is running + type: object + required: + - topologyKey + properties: + labelSelector: + description: A label query over a set of resources, in this case pods. + type: object + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + type: array + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + type: array + items: + type: string + matchLabels: + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + additionalProperties: + type: string + namespaces: + description: namespaces specifies which namespaces the labelSelector applies to (matches against); null or empty list means "this pod's namespace" + type: array + items: + type: string + topologyKey: + description: This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching the labelSelector in the specified namespaces, where co-located is defined as running on a node whose value of the label with key topologyKey matches that of any node on which any of the selected pods is running. Empty topologyKey is not allowed. + type: string + automountServiceAccountToken: + description: AutomountServiceAccountToken indicates whether a service account token should be automatically mounted. + type: boolean + containers: + description: List of containers belonging to the pod. Containers cannot currently be added or removed. There must be at least one container in a Pod. Cannot be updated. + type: array + items: + description: A single application container that you want to run within a pod. + type: object + required: + - name + properties: + args: + description: 'Arguments to the entrypoint. The docker image''s CMD is used if this is not provided. Variable references $(VAR_NAME) are expanded using the container''s environment. If a variable cannot be resolved, the reference in the input string will be unchanged. The $(VAR_NAME) syntax can be escaped with a double $$, ie: $$(VAR_NAME). Escaped references will never be expanded, regardless of whether the variable exists or not. Cannot be updated. More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell' + type: array + items: + type: string + command: + description: 'Entrypoint array. Not executed within a shell. The docker image''s ENTRYPOINT is used if this is not provided. Variable references $(VAR_NAME) are expanded using the container''s environment. If a variable cannot be resolved, the reference in the input string will be unchanged. The $(VAR_NAME) syntax can be escaped with a double $$, ie: $$(VAR_NAME). Escaped references will never be expanded, regardless of whether the variable exists or not. Cannot be updated. More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell' + type: array + items: + type: string + env: + description: List of environment variables to set in the container. Cannot be updated. + type: array + items: + description: EnvVar represents an environment variable present in a Container. + type: object + required: + - name + properties: + name: + description: Name of the environment variable. Must be a C_IDENTIFIER. + type: string + value: + description: 'Variable references $(VAR_NAME) are expanded using the previous defined environment variables in the container and any service environment variables. If a variable cannot be resolved, the reference in the input string will be unchanged. The $(VAR_NAME) syntax can be escaped with a double $$, ie: $$(VAR_NAME). Escaped references will never be expanded, regardless of whether the variable exists or not. Defaults to "".' + type: string + valueFrom: + description: Source for the environment variable's value. Cannot be used if value is not empty. + type: object + properties: + configMapKeyRef: + description: Selects a key of a ConfigMap. + type: object + required: + - key + properties: + key: + description: The key to select. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + optional: + description: Specify whether the ConfigMap or its key must be defined + type: boolean + fieldRef: + description: 'Selects a field of the pod: supports metadata.name, metadata.namespace, `metadata.labels['''']`, `metadata.annotations['''']`, spec.nodeName, spec.serviceAccountName, status.hostIP, status.podIP, status.podIPs.' + type: object + required: + - fieldPath + properties: + apiVersion: + description: Version of the schema the FieldPath is written in terms of, defaults to "v1". + type: string + fieldPath: + description: Path of the field to select in the specified API version. + type: string + resourceFieldRef: + description: 'Selects a resource of the container: only resources limits and requests (limits.cpu, limits.memory, limits.ephemeral-storage, requests.cpu, requests.memory and requests.ephemeral-storage) are currently supported.' + type: object + required: + - resource + properties: + containerName: + description: 'Container name: required for volumes, optional for env vars' + type: string + divisor: + description: Specifies the output format of the exposed resources, defaults to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource to select' + type: string + secretKeyRef: + description: Selects a key of a secret in the pod's namespace + type: object + required: + - key + properties: + key: + description: The key of the secret to select from. Must be a valid secret key. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + optional: + description: Specify whether the Secret or its key must be defined + type: boolean + envFrom: + description: List of sources to populate environment variables in the container. The keys defined within a source must be a C_IDENTIFIER. All invalid keys will be reported as an event when the container is starting. When a key exists in multiple sources, the value associated with the last source will take precedence. Values defined by an Env with a duplicate key will take precedence. Cannot be updated. + type: array + items: + description: EnvFromSource represents the source of a set of ConfigMaps + type: object + properties: + configMapRef: + description: The ConfigMap to select from + type: object + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + optional: + description: Specify whether the ConfigMap must be defined + type: boolean + prefix: + description: An optional identifier to prepend to each key in the ConfigMap. Must be a C_IDENTIFIER. + type: string + secretRef: + description: The Secret to select from + type: object + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + optional: + description: Specify whether the Secret must be defined + type: boolean + image: + description: 'Docker image name. More info: https://kubernetes.io/docs/concepts/containers/images This field is optional to allow higher level config management to default or override container images in workload controllers like Deployments and StatefulSets.' + type: string + imagePullPolicy: + description: 'Image pull policy. One of Always, Never, IfNotPresent. Defaults to Always if :latest tag is specified, or IfNotPresent otherwise. Cannot be updated. More info: https://kubernetes.io/docs/concepts/containers/images#updating-images' + type: string + lifecycle: + description: Actions that the management system should take in response to container lifecycle events. Cannot be updated. + type: object + properties: + postStart: + description: 'PostStart is called immediately after a container is created. If the handler fails, the container is terminated and restarted according to its restart policy. Other management of the container blocks until the hook completes. More info: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks' + type: object + properties: + exec: + description: One and only one of the following should be specified. Exec specifies the action to take. + type: object + properties: + command: + description: Command is the command line to execute inside the container, the working directory for the command is root ('/') in the container's filesystem. The command is simply exec'd, it is not run inside a shell, so traditional shell instructions ('|', etc) won't work. To use a shell, you need to explicitly call out to that shell. Exit status of 0 is treated as live/healthy and non-zero is unhealthy. + type: array + items: + type: string + httpGet: + description: HTTPGet specifies the http request to perform. + type: object + required: + - port + properties: + host: + description: Host name to connect to, defaults to the pod IP. You probably want to set "Host" in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the request. HTTP allows repeated headers. + type: array + items: + description: HTTPHeader describes a custom header to be used in HTTP probes + type: object + required: + - name + - value + properties: + name: + description: The header field name + type: string + value: + description: The header field value + type: string + path: + description: Path to access on the HTTP server. + type: string + port: + description: Name or number of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + description: Scheme to use for connecting to the host. Defaults to HTTP. + type: string + tcpSocket: + description: 'TCPSocket specifies an action involving a TCP port. TCP hooks not yet supported TODO: implement a realistic TCP lifecycle hook' + type: object + required: + - port + properties: + host: + description: 'Optional: Host name to connect to, defaults to the pod IP.' + type: string + port: + description: Number or name of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + preStop: + description: 'PreStop is called immediately before a container is terminated due to an API request or management event such as liveness/startup probe failure, preemption, resource contention, etc. The handler is not called if the container crashes or exits. The reason for termination is passed to the handler. The Pod''s termination grace period countdown begins before the PreStop hooked is executed. Regardless of the outcome of the handler, the container will eventually terminate within the Pod''s termination grace period. Other management of the container blocks until the hook completes or until the termination grace period is reached. More info: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks' + type: object + properties: + exec: + description: One and only one of the following should be specified. Exec specifies the action to take. + type: object + properties: + command: + description: Command is the command line to execute inside the container, the working directory for the command is root ('/') in the container's filesystem. The command is simply exec'd, it is not run inside a shell, so traditional shell instructions ('|', etc) won't work. To use a shell, you need to explicitly call out to that shell. Exit status of 0 is treated as live/healthy and non-zero is unhealthy. + type: array + items: + type: string + httpGet: + description: HTTPGet specifies the http request to perform. + type: object + required: + - port + properties: + host: + description: Host name to connect to, defaults to the pod IP. You probably want to set "Host" in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the request. HTTP allows repeated headers. + type: array + items: + description: HTTPHeader describes a custom header to be used in HTTP probes + type: object + required: + - name + - value + properties: + name: + description: The header field name + type: string + value: + description: The header field value + type: string + path: + description: Path to access on the HTTP server. + type: string + port: + description: Name or number of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + description: Scheme to use for connecting to the host. Defaults to HTTP. + type: string + tcpSocket: + description: 'TCPSocket specifies an action involving a TCP port. TCP hooks not yet supported TODO: implement a realistic TCP lifecycle hook' + type: object + required: + - port + properties: + host: + description: 'Optional: Host name to connect to, defaults to the pod IP.' + type: string + port: + description: Number or name of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + livenessProbe: + description: 'Periodic probe of container liveness. Container will be restarted if the probe fails. Cannot be updated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + type: object + properties: + exec: + description: One and only one of the following should be specified. Exec specifies the action to take. + type: object + properties: + command: + description: Command is the command line to execute inside the container, the working directory for the command is root ('/') in the container's filesystem. The command is simply exec'd, it is not run inside a shell, so traditional shell instructions ('|', etc) won't work. To use a shell, you need to explicitly call out to that shell. Exit status of 0 is treated as live/healthy and non-zero is unhealthy. + type: array + items: + type: string + failureThreshold: + description: Minimum consecutive failures for the probe to be considered failed after having succeeded. Defaults to 3. Minimum value is 1. + type: integer + format: int32 + httpGet: + description: HTTPGet specifies the http request to perform. + type: object + required: + - port + properties: + host: + description: Host name to connect to, defaults to the pod IP. You probably want to set "Host" in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the request. HTTP allows repeated headers. + type: array + items: + description: HTTPHeader describes a custom header to be used in HTTP probes + type: object + required: + - name + - value + properties: + name: + description: The header field name + type: string + value: + description: The header field value + type: string + path: + description: Path to access on the HTTP server. + type: string + port: + description: Name or number of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + description: Scheme to use for connecting to the host. Defaults to HTTP. + type: string + initialDelaySeconds: + description: 'Number of seconds after the container has started before liveness probes are initiated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + type: integer + format: int32 + periodSeconds: + description: How often (in seconds) to perform the probe. Default to 10 seconds. Minimum value is 1. + type: integer + format: int32 + successThreshold: + description: Minimum consecutive successes for the probe to be considered successful after having failed. Defaults to 1. Must be 1 for liveness and startup. Minimum value is 1. + type: integer + format: int32 + tcpSocket: + description: 'TCPSocket specifies an action involving a TCP port. TCP hooks not yet supported TODO: implement a realistic TCP lifecycle hook' + type: object + required: + - port + properties: + host: + description: 'Optional: Host name to connect to, defaults to the pod IP.' + type: string + port: + description: Number or name of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + timeoutSeconds: + description: 'Number of seconds after which the probe times out. Defaults to 1 second. Minimum value is 1. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + type: integer + format: int32 + name: + description: Name of the container specified as a DNS_LABEL. Each container in a pod must have a unique name (DNS_LABEL). Cannot be updated. + type: string + ports: + description: List of ports to expose from the container. Exposing a port here gives the system additional information about the network connections a container uses, but is primarily informational. Not specifying a port here DOES NOT prevent that port from being exposed. Any port which is listening on the default "0.0.0.0" address inside a container will be accessible from the network. Cannot be updated. + type: array + items: + description: ContainerPort represents a network port in a single container. + type: object + required: + - containerPort + properties: + containerPort: + description: Number of port to expose on the pod's IP address. This must be a valid port number, 0 < x < 65536. + type: integer + format: int32 + hostIP: + description: What host IP to bind the external port to. + type: string + hostPort: + description: Number of port to expose on the host. If specified, this must be a valid port number, 0 < x < 65536. If HostNetwork is specified, this must match ContainerPort. Most containers do not need this. + type: integer + format: int32 + name: + description: If specified, this must be an IANA_SVC_NAME and unique within the pod. Each named port in a pod must have a unique name. Name for the port that can be referred to by services. + type: string + protocol: + description: Protocol for port. Must be UDP, TCP, or SCTP. Defaults to "TCP". + type: string + default: TCP + x-kubernetes-list-map-keys: + - containerPort + - protocol + x-kubernetes-list-type: map + readinessProbe: + description: 'Periodic probe of container service readiness. Container will be removed from service endpoints if the probe fails. Cannot be updated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + type: object + properties: + exec: + description: One and only one of the following should be specified. Exec specifies the action to take. + type: object + properties: + command: + description: Command is the command line to execute inside the container, the working directory for the command is root ('/') in the container's filesystem. The command is simply exec'd, it is not run inside a shell, so traditional shell instructions ('|', etc) won't work. To use a shell, you need to explicitly call out to that shell. Exit status of 0 is treated as live/healthy and non-zero is unhealthy. + type: array + items: + type: string + failureThreshold: + description: Minimum consecutive failures for the probe to be considered failed after having succeeded. Defaults to 3. Minimum value is 1. + type: integer + format: int32 + httpGet: + description: HTTPGet specifies the http request to perform. + type: object + required: + - port + properties: + host: + description: Host name to connect to, defaults to the pod IP. You probably want to set "Host" in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the request. HTTP allows repeated headers. + type: array + items: + description: HTTPHeader describes a custom header to be used in HTTP probes + type: object + required: + - name + - value + properties: + name: + description: The header field name + type: string + value: + description: The header field value + type: string + path: + description: Path to access on the HTTP server. + type: string + port: + description: Name or number of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + description: Scheme to use for connecting to the host. Defaults to HTTP. + type: string + initialDelaySeconds: + description: 'Number of seconds after the container has started before liveness probes are initiated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + type: integer + format: int32 + periodSeconds: + description: How often (in seconds) to perform the probe. Default to 10 seconds. Minimum value is 1. + type: integer + format: int32 + successThreshold: + description: Minimum consecutive successes for the probe to be considered successful after having failed. Defaults to 1. Must be 1 for liveness and startup. Minimum value is 1. + type: integer + format: int32 + tcpSocket: + description: 'TCPSocket specifies an action involving a TCP port. TCP hooks not yet supported TODO: implement a realistic TCP lifecycle hook' + type: object + required: + - port + properties: + host: + description: 'Optional: Host name to connect to, defaults to the pod IP.' + type: string + port: + description: Number or name of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + timeoutSeconds: + description: 'Number of seconds after which the probe times out. Defaults to 1 second. Minimum value is 1. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + type: integer + format: int32 + resources: + description: 'Compute Resources required by this container. Cannot be updated. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' + type: object + properties: + limits: + description: 'Limits describes the maximum amount of compute resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' + type: object + additionalProperties: + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + requests: + description: 'Requests describes the minimum amount of compute resources required. If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, otherwise to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' + type: object + additionalProperties: + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + securityContext: + description: 'Security options the pod should run with. More info: https://kubernetes.io/docs/concepts/policy/security-context/ More info: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/' + type: object + properties: + allowPrivilegeEscalation: + description: 'AllowPrivilegeEscalation controls whether a process can gain more privileges than its parent process. This bool directly controls if the no_new_privs flag will be set on the container process. AllowPrivilegeEscalation is true always when the container is: 1) run as Privileged 2) has CAP_SYS_ADMIN' + type: boolean + capabilities: + description: The capabilities to add/drop when running containers. Defaults to the default set of capabilities granted by the container runtime. + type: object + properties: + add: + description: Added capabilities + type: array + items: + description: Capability represent POSIX capabilities type + type: string + drop: + description: Removed capabilities + type: array + items: + description: Capability represent POSIX capabilities type + type: string + privileged: + description: Run container in privileged mode. Processes in privileged containers are essentially equivalent to root on the host. Defaults to false. + type: boolean + procMount: + description: procMount denotes the type of proc mount to use for the containers. The default is DefaultProcMount which uses the container runtime defaults for readonly paths and masked paths. This requires the ProcMountType feature flag to be enabled. + type: string + readOnlyRootFilesystem: + description: Whether this container has a read-only root filesystem. Default is false. + type: boolean + runAsGroup: + description: The GID to run the entrypoint of the container process. Uses runtime default if unset. May also be set in PodSecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence. + type: integer + format: int64 + runAsNonRoot: + description: Indicates that the container must run as a non-root user. If true, the Kubelet will validate the image at runtime to ensure that it does not run as UID 0 (root) and fail to start the container if it does. If unset or false, no such validation will be performed. May also be set in PodSecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence. + type: boolean + runAsUser: + description: The UID to run the entrypoint of the container process. Defaults to user specified in image metadata if unspecified. May also be set in PodSecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence. + type: integer + format: int64 + seLinuxOptions: + description: The SELinux context to be applied to the container. If unspecified, the container runtime will allocate a random SELinux context for each container. May also be set in PodSecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence. + type: object + properties: + level: + description: Level is SELinux level label that applies to the container. + type: string + role: + description: Role is a SELinux role label that applies to the container. + type: string + type: + description: Type is a SELinux type label that applies to the container. + type: string + user: + description: User is a SELinux user label that applies to the container. + type: string + seccompProfile: + description: The seccomp options to use by this container. If seccomp options are provided at both the pod & container level, the container options override the pod options. + type: object + required: + - type + properties: + localhostProfile: + description: localhostProfile indicates a profile defined in a file on the node should be used. The profile must be preconfigured on the node to work. Must be a descending path, relative to the kubelet's configured seccomp profile location. Must only be set if type is "Localhost". + type: string + type: + description: "type indicates which kind of seccomp profile will be applied. Valid options are: \n Localhost - a profile defined in a file on the node should be used. RuntimeDefault - the container runtime default profile should be used. Unconfined - no profile should be applied." + type: string + windowsOptions: + description: The Windows specific settings applied to all containers. If unspecified, the options from the PodSecurityContext will be used. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence. + type: object + properties: + gmsaCredentialSpec: + description: GMSACredentialSpec is where the GMSA admission webhook (https://github.com/kubernetes-sigs/windows-gmsa) inlines the contents of the GMSA credential spec named by the GMSACredentialSpecName field. + type: string + gmsaCredentialSpecName: + description: GMSACredentialSpecName is the name of the GMSA credential spec to use. + type: string + runAsUserName: + description: The UserName in Windows to run the entrypoint of the container process. Defaults to the user specified in image metadata if unspecified. May also be set in PodSecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence. + type: string + startupProbe: + description: 'StartupProbe indicates that the Pod has successfully initialized. If specified, no other probes are executed until this completes successfully. If this probe fails, the Pod will be restarted, just as if the livenessProbe failed. This can be used to provide different probe parameters at the beginning of a Pod''s lifecycle, when it might take a long time to load data or warm a cache, than during steady-state operation. This cannot be updated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + type: object + properties: + exec: + description: One and only one of the following should be specified. Exec specifies the action to take. + type: object + properties: + command: + description: Command is the command line to execute inside the container, the working directory for the command is root ('/') in the container's filesystem. The command is simply exec'd, it is not run inside a shell, so traditional shell instructions ('|', etc) won't work. To use a shell, you need to explicitly call out to that shell. Exit status of 0 is treated as live/healthy and non-zero is unhealthy. + type: array + items: + type: string + failureThreshold: + description: Minimum consecutive failures for the probe to be considered failed after having succeeded. Defaults to 3. Minimum value is 1. + type: integer + format: int32 + httpGet: + description: HTTPGet specifies the http request to perform. + type: object + required: + - port + properties: + host: + description: Host name to connect to, defaults to the pod IP. You probably want to set "Host" in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the request. HTTP allows repeated headers. + type: array + items: + description: HTTPHeader describes a custom header to be used in HTTP probes + type: object + required: + - name + - value + properties: + name: + description: The header field name + type: string + value: + description: The header field value + type: string + path: + description: Path to access on the HTTP server. + type: string + port: + description: Name or number of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + description: Scheme to use for connecting to the host. Defaults to HTTP. + type: string + initialDelaySeconds: + description: 'Number of seconds after the container has started before liveness probes are initiated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + type: integer + format: int32 + periodSeconds: + description: How often (in seconds) to perform the probe. Default to 10 seconds. Minimum value is 1. + type: integer + format: int32 + successThreshold: + description: Minimum consecutive successes for the probe to be considered successful after having failed. Defaults to 1. Must be 1 for liveness and startup. Minimum value is 1. + type: integer + format: int32 + tcpSocket: + description: 'TCPSocket specifies an action involving a TCP port. TCP hooks not yet supported TODO: implement a realistic TCP lifecycle hook' + type: object + required: + - port + properties: + host: + description: 'Optional: Host name to connect to, defaults to the pod IP.' + type: string + port: + description: Number or name of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + timeoutSeconds: + description: 'Number of seconds after which the probe times out. Defaults to 1 second. Minimum value is 1. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + type: integer + format: int32 + stdin: + description: Whether this container should allocate a buffer for stdin in the container runtime. If this is not set, reads from stdin in the container will always result in EOF. Default is false. + type: boolean + stdinOnce: + description: Whether the container runtime should close the stdin channel after it has been opened by a single attach. When stdin is true the stdin stream will remain open across multiple attach sessions. If stdinOnce is set to true, stdin is opened on container start, is empty until the first client attaches to stdin, and then remains open and accepts data until the client disconnects, at which time stdin is closed and remains closed until the container is restarted. If this flag is false, a container processes that reads from stdin will never receive an EOF. Default is false + type: boolean + terminationMessagePath: + description: 'Optional: Path at which the file to which the container''s termination message will be written is mounted into the container''s filesystem. Message written is intended to be brief final status, such as an assertion failure message. Will be truncated by the node if greater than 4096 bytes. The total message length across all containers will be limited to 12kb. Defaults to /dev/termination-log. Cannot be updated.' + type: string + terminationMessagePolicy: + description: Indicate how the termination message should be populated. File will use the contents of terminationMessagePath to populate the container status message on both success and failure. FallbackToLogsOnError will use the last chunk of container log output if the termination message file is empty and the container exited with an error. The log output is limited to 2048 bytes or 80 lines, whichever is smaller. Defaults to File. Cannot be updated. + type: string + tty: + description: Whether this container should allocate a TTY for itself, also requires 'stdin' to be true. Default is false. + type: boolean + volumeDevices: + description: volumeDevices is the list of block devices to be used by the container. + type: array + items: + description: volumeDevice describes a mapping of a raw block device within a container. + type: object + required: + - devicePath + - name + properties: + devicePath: + description: devicePath is the path inside of the container that the device will be mapped to. + type: string + name: + description: name must match the name of a persistentVolumeClaim in the pod + type: string + volumeMounts: + description: Pod volumes to mount into the container's filesystem. Cannot be updated. + type: array + items: + description: VolumeMount describes a mounting of a Volume within a container. + type: object + required: + - mountPath + - name + properties: + mountPath: + description: Path within the container at which the volume should be mounted. Must not contain ':'. + type: string + mountPropagation: + description: mountPropagation determines how mounts are propagated from the host to container and the other way around. When not set, MountPropagationNone is used. This field is beta in 1.10. + type: string + name: + description: This must match the Name of a Volume. + type: string + readOnly: + description: Mounted read-only if true, read-write otherwise (false or unspecified). Defaults to false. + type: boolean + subPath: + description: Path within the volume from which the container's volume should be mounted. Defaults to "" (volume's root). + type: string + subPathExpr: + description: Expanded path within the volume from which the container's volume should be mounted. Behaves similarly to SubPath but environment variable references $(VAR_NAME) are expanded using the container's environment. Defaults to "" (volume's root). SubPathExpr and SubPath are mutually exclusive. + type: string + workingDir: + description: Container's working directory. If not specified, the container runtime's default will be used, which might be configured in the container image. Cannot be updated. + type: string + dnsConfig: + description: Specifies the DNS parameters of a pod. Parameters specified here will be merged to the generated DNS configuration based on DNSPolicy. + type: object + properties: + nameservers: + description: A list of DNS name server IP addresses. This will be appended to the base nameservers generated from DNSPolicy. Duplicated nameservers will be removed. + type: array + items: + type: string + options: + description: A list of DNS resolver options. This will be merged with the base options generated from DNSPolicy. Duplicated entries will be removed. Resolution options given in Options will override those that appear in the base DNSPolicy. + type: array + items: + description: PodDNSConfigOption defines DNS resolver options of a pod. + type: object + properties: + name: + description: Required. + type: string + value: + type: string + searches: + description: A list of DNS search domains for host-name lookup. This will be appended to the base search paths generated from DNSPolicy. Duplicated search paths will be removed. + type: array + items: + type: string + dnsPolicy: + description: Set DNS policy for the pod. Defaults to "ClusterFirst". Valid values are 'ClusterFirstWithHostNet', 'ClusterFirst', 'Default' or 'None'. DNS parameters given in DNSConfig will be merged with the policy selected with DNSPolicy. To have DNS options set along with hostNetwork, you have to specify DNS policy explicitly to 'ClusterFirstWithHostNet'. + type: string + enableServiceLinks: + description: 'EnableServiceLinks indicates whether information about services should be injected into pod''s environment variables, matching the syntax of Docker links. Optional: Defaults to true.' + type: boolean + ephemeralContainers: + description: List of ephemeral containers run in this pod. Ephemeral containers may be run in an existing pod to perform user-initiated actions such as debugging. This list cannot be specified when creating a pod, and it cannot be modified by updating the pod spec. In order to add an ephemeral container to an existing pod, use the pod's ephemeralcontainers subresource. This field is alpha-level and is only honored by servers that enable the EphemeralContainers feature. + type: array + items: + description: An EphemeralContainer is a container that may be added temporarily to an existing pod for user-initiated activities such as debugging. Ephemeral containers have no resource or scheduling guarantees, and they will not be restarted when they exit or when a pod is removed or restarted. If an ephemeral container causes a pod to exceed its resource allocation, the pod may be evicted. Ephemeral containers may not be added by directly updating the pod spec. They must be added via the pod's ephemeralcontainers subresource, and they will appear in the pod spec once added. This is an alpha feature enabled by the EphemeralContainers feature flag. + type: object + required: + - name + properties: + args: + description: 'Arguments to the entrypoint. The docker image''s CMD is used if this is not provided. Variable references $(VAR_NAME) are expanded using the container''s environment. If a variable cannot be resolved, the reference in the input string will be unchanged. The $(VAR_NAME) syntax can be escaped with a double $$, ie: $$(VAR_NAME). Escaped references will never be expanded, regardless of whether the variable exists or not. Cannot be updated. More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell' + type: array + items: + type: string + command: + description: 'Entrypoint array. Not executed within a shell. The docker image''s ENTRYPOINT is used if this is not provided. Variable references $(VAR_NAME) are expanded using the container''s environment. If a variable cannot be resolved, the reference in the input string will be unchanged. The $(VAR_NAME) syntax can be escaped with a double $$, ie: $$(VAR_NAME). Escaped references will never be expanded, regardless of whether the variable exists or not. Cannot be updated. More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell' + type: array + items: + type: string + env: + description: List of environment variables to set in the container. Cannot be updated. + type: array + items: + description: EnvVar represents an environment variable present in a Container. + type: object + required: + - name + properties: + name: + description: Name of the environment variable. Must be a C_IDENTIFIER. + type: string + value: + description: 'Variable references $(VAR_NAME) are expanded using the previous defined environment variables in the container and any service environment variables. If a variable cannot be resolved, the reference in the input string will be unchanged. The $(VAR_NAME) syntax can be escaped with a double $$, ie: $$(VAR_NAME). Escaped references will never be expanded, regardless of whether the variable exists or not. Defaults to "".' + type: string + valueFrom: + description: Source for the environment variable's value. Cannot be used if value is not empty. + type: object + properties: + configMapKeyRef: + description: Selects a key of a ConfigMap. + type: object + required: + - key + properties: + key: + description: The key to select. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + optional: + description: Specify whether the ConfigMap or its key must be defined + type: boolean + fieldRef: + description: 'Selects a field of the pod: supports metadata.name, metadata.namespace, `metadata.labels['''']`, `metadata.annotations['''']`, spec.nodeName, spec.serviceAccountName, status.hostIP, status.podIP, status.podIPs.' + type: object + required: + - fieldPath + properties: + apiVersion: + description: Version of the schema the FieldPath is written in terms of, defaults to "v1". + type: string + fieldPath: + description: Path of the field to select in the specified API version. + type: string + resourceFieldRef: + description: 'Selects a resource of the container: only resources limits and requests (limits.cpu, limits.memory, limits.ephemeral-storage, requests.cpu, requests.memory and requests.ephemeral-storage) are currently supported.' + type: object + required: + - resource + properties: + containerName: + description: 'Container name: required for volumes, optional for env vars' + type: string + divisor: + description: Specifies the output format of the exposed resources, defaults to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource to select' + type: string + secretKeyRef: + description: Selects a key of a secret in the pod's namespace + type: object + required: + - key + properties: + key: + description: The key of the secret to select from. Must be a valid secret key. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + optional: + description: Specify whether the Secret or its key must be defined + type: boolean + envFrom: + description: List of sources to populate environment variables in the container. The keys defined within a source must be a C_IDENTIFIER. All invalid keys will be reported as an event when the container is starting. When a key exists in multiple sources, the value associated with the last source will take precedence. Values defined by an Env with a duplicate key will take precedence. Cannot be updated. + type: array + items: + description: EnvFromSource represents the source of a set of ConfigMaps + type: object + properties: + configMapRef: + description: The ConfigMap to select from + type: object + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + optional: + description: Specify whether the ConfigMap must be defined + type: boolean + prefix: + description: An optional identifier to prepend to each key in the ConfigMap. Must be a C_IDENTIFIER. + type: string + secretRef: + description: The Secret to select from + type: object + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + optional: + description: Specify whether the Secret must be defined + type: boolean + image: + description: 'Docker image name. More info: https://kubernetes.io/docs/concepts/containers/images' + type: string + imagePullPolicy: + description: 'Image pull policy. One of Always, Never, IfNotPresent. Defaults to Always if :latest tag is specified, or IfNotPresent otherwise. Cannot be updated. More info: https://kubernetes.io/docs/concepts/containers/images#updating-images' + type: string + lifecycle: + description: Lifecycle is not allowed for ephemeral containers. + type: object + properties: + postStart: + description: 'PostStart is called immediately after a container is created. If the handler fails, the container is terminated and restarted according to its restart policy. Other management of the container blocks until the hook completes. More info: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks' + type: object + properties: + exec: + description: One and only one of the following should be specified. Exec specifies the action to take. + type: object + properties: + command: + description: Command is the command line to execute inside the container, the working directory for the command is root ('/') in the container's filesystem. The command is simply exec'd, it is not run inside a shell, so traditional shell instructions ('|', etc) won't work. To use a shell, you need to explicitly call out to that shell. Exit status of 0 is treated as live/healthy and non-zero is unhealthy. + type: array + items: + type: string + httpGet: + description: HTTPGet specifies the http request to perform. + type: object + required: + - port + properties: + host: + description: Host name to connect to, defaults to the pod IP. You probably want to set "Host" in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the request. HTTP allows repeated headers. + type: array + items: + description: HTTPHeader describes a custom header to be used in HTTP probes + type: object + required: + - name + - value + properties: + name: + description: The header field name + type: string + value: + description: The header field value + type: string + path: + description: Path to access on the HTTP server. + type: string + port: + description: Name or number of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + description: Scheme to use for connecting to the host. Defaults to HTTP. + type: string + tcpSocket: + description: 'TCPSocket specifies an action involving a TCP port. TCP hooks not yet supported TODO: implement a realistic TCP lifecycle hook' + type: object + required: + - port + properties: + host: + description: 'Optional: Host name to connect to, defaults to the pod IP.' + type: string + port: + description: Number or name of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + preStop: + description: 'PreStop is called immediately before a container is terminated due to an API request or management event such as liveness/startup probe failure, preemption, resource contention, etc. The handler is not called if the container crashes or exits. The reason for termination is passed to the handler. The Pod''s termination grace period countdown begins before the PreStop hooked is executed. Regardless of the outcome of the handler, the container will eventually terminate within the Pod''s termination grace period. Other management of the container blocks until the hook completes or until the termination grace period is reached. More info: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks' + type: object + properties: + exec: + description: One and only one of the following should be specified. Exec specifies the action to take. + type: object + properties: + command: + description: Command is the command line to execute inside the container, the working directory for the command is root ('/') in the container's filesystem. The command is simply exec'd, it is not run inside a shell, so traditional shell instructions ('|', etc) won't work. To use a shell, you need to explicitly call out to that shell. Exit status of 0 is treated as live/healthy and non-zero is unhealthy. + type: array + items: + type: string + httpGet: + description: HTTPGet specifies the http request to perform. + type: object + required: + - port + properties: + host: + description: Host name to connect to, defaults to the pod IP. You probably want to set "Host" in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the request. HTTP allows repeated headers. + type: array + items: + description: HTTPHeader describes a custom header to be used in HTTP probes + type: object + required: + - name + - value + properties: + name: + description: The header field name + type: string + value: + description: The header field value + type: string + path: + description: Path to access on the HTTP server. + type: string + port: + description: Name or number of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + description: Scheme to use for connecting to the host. Defaults to HTTP. + type: string + tcpSocket: + description: 'TCPSocket specifies an action involving a TCP port. TCP hooks not yet supported TODO: implement a realistic TCP lifecycle hook' + type: object + required: + - port + properties: + host: + description: 'Optional: Host name to connect to, defaults to the pod IP.' + type: string + port: + description: Number or name of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + livenessProbe: + description: Probes are not allowed for ephemeral containers. + type: object + properties: + exec: + description: One and only one of the following should be specified. Exec specifies the action to take. + type: object + properties: + command: + description: Command is the command line to execute inside the container, the working directory for the command is root ('/') in the container's filesystem. The command is simply exec'd, it is not run inside a shell, so traditional shell instructions ('|', etc) won't work. To use a shell, you need to explicitly call out to that shell. Exit status of 0 is treated as live/healthy and non-zero is unhealthy. + type: array + items: + type: string + failureThreshold: + description: Minimum consecutive failures for the probe to be considered failed after having succeeded. Defaults to 3. Minimum value is 1. + type: integer + format: int32 + httpGet: + description: HTTPGet specifies the http request to perform. + type: object + required: + - port + properties: + host: + description: Host name to connect to, defaults to the pod IP. You probably want to set "Host" in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the request. HTTP allows repeated headers. + type: array + items: + description: HTTPHeader describes a custom header to be used in HTTP probes + type: object + required: + - name + - value + properties: + name: + description: The header field name + type: string + value: + description: The header field value + type: string + path: + description: Path to access on the HTTP server. + type: string + port: + description: Name or number of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + description: Scheme to use for connecting to the host. Defaults to HTTP. + type: string + initialDelaySeconds: + description: 'Number of seconds after the container has started before liveness probes are initiated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + type: integer + format: int32 + periodSeconds: + description: How often (in seconds) to perform the probe. Default to 10 seconds. Minimum value is 1. + type: integer + format: int32 + successThreshold: + description: Minimum consecutive successes for the probe to be considered successful after having failed. Defaults to 1. Must be 1 for liveness and startup. Minimum value is 1. + type: integer + format: int32 + tcpSocket: + description: 'TCPSocket specifies an action involving a TCP port. TCP hooks not yet supported TODO: implement a realistic TCP lifecycle hook' + type: object + required: + - port + properties: + host: + description: 'Optional: Host name to connect to, defaults to the pod IP.' + type: string + port: + description: Number or name of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + timeoutSeconds: + description: 'Number of seconds after which the probe times out. Defaults to 1 second. Minimum value is 1. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + type: integer + format: int32 + name: + description: Name of the ephemeral container specified as a DNS_LABEL. This name must be unique among all containers, init containers and ephemeral containers. + type: string + ports: + description: Ports are not allowed for ephemeral containers. + type: array + items: + description: ContainerPort represents a network port in a single container. + type: object + required: + - containerPort + properties: + containerPort: + description: Number of port to expose on the pod's IP address. This must be a valid port number, 0 < x < 65536. + type: integer + format: int32 + hostIP: + description: What host IP to bind the external port to. + type: string + hostPort: + description: Number of port to expose on the host. If specified, this must be a valid port number, 0 < x < 65536. If HostNetwork is specified, this must match ContainerPort. Most containers do not need this. + type: integer + format: int32 + name: + description: If specified, this must be an IANA_SVC_NAME and unique within the pod. Each named port in a pod must have a unique name. Name for the port that can be referred to by services. + type: string + protocol: + description: Protocol for port. Must be UDP, TCP, or SCTP. Defaults to "TCP". + type: string + default: TCP + readinessProbe: + description: Probes are not allowed for ephemeral containers. + type: object + properties: + exec: + description: One and only one of the following should be specified. Exec specifies the action to take. + type: object + properties: + command: + description: Command is the command line to execute inside the container, the working directory for the command is root ('/') in the container's filesystem. The command is simply exec'd, it is not run inside a shell, so traditional shell instructions ('|', etc) won't work. To use a shell, you need to explicitly call out to that shell. Exit status of 0 is treated as live/healthy and non-zero is unhealthy. + type: array + items: + type: string + failureThreshold: + description: Minimum consecutive failures for the probe to be considered failed after having succeeded. Defaults to 3. Minimum value is 1. + type: integer + format: int32 + httpGet: + description: HTTPGet specifies the http request to perform. + type: object + required: + - port + properties: + host: + description: Host name to connect to, defaults to the pod IP. You probably want to set "Host" in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the request. HTTP allows repeated headers. + type: array + items: + description: HTTPHeader describes a custom header to be used in HTTP probes + type: object + required: + - name + - value + properties: + name: + description: The header field name + type: string + value: + description: The header field value + type: string + path: + description: Path to access on the HTTP server. + type: string + port: + description: Name or number of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + description: Scheme to use for connecting to the host. Defaults to HTTP. + type: string + initialDelaySeconds: + description: 'Number of seconds after the container has started before liveness probes are initiated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + type: integer + format: int32 + periodSeconds: + description: How often (in seconds) to perform the probe. Default to 10 seconds. Minimum value is 1. + type: integer + format: int32 + successThreshold: + description: Minimum consecutive successes for the probe to be considered successful after having failed. Defaults to 1. Must be 1 for liveness and startup. Minimum value is 1. + type: integer + format: int32 + tcpSocket: + description: 'TCPSocket specifies an action involving a TCP port. TCP hooks not yet supported TODO: implement a realistic TCP lifecycle hook' + type: object + required: + - port + properties: + host: + description: 'Optional: Host name to connect to, defaults to the pod IP.' + type: string + port: + description: Number or name of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + timeoutSeconds: + description: 'Number of seconds after which the probe times out. Defaults to 1 second. Minimum value is 1. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + type: integer + format: int32 + resources: + description: Resources are not allowed for ephemeral containers. Ephemeral containers use spare resources already allocated to the pod. + type: object + properties: + limits: + description: 'Limits describes the maximum amount of compute resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' + type: object + additionalProperties: + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + requests: + description: 'Requests describes the minimum amount of compute resources required. If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, otherwise to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' + type: object + additionalProperties: + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + securityContext: + description: SecurityContext is not allowed for ephemeral containers. + type: object + properties: + allowPrivilegeEscalation: + description: 'AllowPrivilegeEscalation controls whether a process can gain more privileges than its parent process. This bool directly controls if the no_new_privs flag will be set on the container process. AllowPrivilegeEscalation is true always when the container is: 1) run as Privileged 2) has CAP_SYS_ADMIN' + type: boolean + capabilities: + description: The capabilities to add/drop when running containers. Defaults to the default set of capabilities granted by the container runtime. + type: object + properties: + add: + description: Added capabilities + type: array + items: + description: Capability represent POSIX capabilities type + type: string + drop: + description: Removed capabilities + type: array + items: + description: Capability represent POSIX capabilities type + type: string + privileged: + description: Run container in privileged mode. Processes in privileged containers are essentially equivalent to root on the host. Defaults to false. + type: boolean + procMount: + description: procMount denotes the type of proc mount to use for the containers. The default is DefaultProcMount which uses the container runtime defaults for readonly paths and masked paths. This requires the ProcMountType feature flag to be enabled. + type: string + readOnlyRootFilesystem: + description: Whether this container has a read-only root filesystem. Default is false. + type: boolean + runAsGroup: + description: The GID to run the entrypoint of the container process. Uses runtime default if unset. May also be set in PodSecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence. + type: integer + format: int64 + runAsNonRoot: + description: Indicates that the container must run as a non-root user. If true, the Kubelet will validate the image at runtime to ensure that it does not run as UID 0 (root) and fail to start the container if it does. If unset or false, no such validation will be performed. May also be set in PodSecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence. + type: boolean + runAsUser: + description: The UID to run the entrypoint of the container process. Defaults to user specified in image metadata if unspecified. May also be set in PodSecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence. + type: integer + format: int64 + seLinuxOptions: + description: The SELinux context to be applied to the container. If unspecified, the container runtime will allocate a random SELinux context for each container. May also be set in PodSecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence. + type: object + properties: + level: + description: Level is SELinux level label that applies to the container. + type: string + role: + description: Role is a SELinux role label that applies to the container. + type: string + type: + description: Type is a SELinux type label that applies to the container. + type: string + user: + description: User is a SELinux user label that applies to the container. + type: string + seccompProfile: + description: The seccomp options to use by this container. If seccomp options are provided at both the pod & container level, the container options override the pod options. + type: object + required: + - type + properties: + localhostProfile: + description: localhostProfile indicates a profile defined in a file on the node should be used. The profile must be preconfigured on the node to work. Must be a descending path, relative to the kubelet's configured seccomp profile location. Must only be set if type is "Localhost". + type: string + type: + description: "type indicates which kind of seccomp profile will be applied. Valid options are: \n Localhost - a profile defined in a file on the node should be used. RuntimeDefault - the container runtime default profile should be used. Unconfined - no profile should be applied." + type: string + windowsOptions: + description: The Windows specific settings applied to all containers. If unspecified, the options from the PodSecurityContext will be used. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence. + type: object + properties: + gmsaCredentialSpec: + description: GMSACredentialSpec is where the GMSA admission webhook (https://github.com/kubernetes-sigs/windows-gmsa) inlines the contents of the GMSA credential spec named by the GMSACredentialSpecName field. + type: string + gmsaCredentialSpecName: + description: GMSACredentialSpecName is the name of the GMSA credential spec to use. + type: string + runAsUserName: + description: The UserName in Windows to run the entrypoint of the container process. Defaults to the user specified in image metadata if unspecified. May also be set in PodSecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence. + type: string + startupProbe: + description: Probes are not allowed for ephemeral containers. + type: object + properties: + exec: + description: One and only one of the following should be specified. Exec specifies the action to take. + type: object + properties: + command: + description: Command is the command line to execute inside the container, the working directory for the command is root ('/') in the container's filesystem. The command is simply exec'd, it is not run inside a shell, so traditional shell instructions ('|', etc) won't work. To use a shell, you need to explicitly call out to that shell. Exit status of 0 is treated as live/healthy and non-zero is unhealthy. + type: array + items: + type: string + failureThreshold: + description: Minimum consecutive failures for the probe to be considered failed after having succeeded. Defaults to 3. Minimum value is 1. + type: integer + format: int32 + httpGet: + description: HTTPGet specifies the http request to perform. + type: object + required: + - port + properties: + host: + description: Host name to connect to, defaults to the pod IP. You probably want to set "Host" in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the request. HTTP allows repeated headers. + type: array + items: + description: HTTPHeader describes a custom header to be used in HTTP probes + type: object + required: + - name + - value + properties: + name: + description: The header field name + type: string + value: + description: The header field value + type: string + path: + description: Path to access on the HTTP server. + type: string + port: + description: Name or number of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + description: Scheme to use for connecting to the host. Defaults to HTTP. + type: string + initialDelaySeconds: + description: 'Number of seconds after the container has started before liveness probes are initiated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + type: integer + format: int32 + periodSeconds: + description: How often (in seconds) to perform the probe. Default to 10 seconds. Minimum value is 1. + type: integer + format: int32 + successThreshold: + description: Minimum consecutive successes for the probe to be considered successful after having failed. Defaults to 1. Must be 1 for liveness and startup. Minimum value is 1. + type: integer + format: int32 + tcpSocket: + description: 'TCPSocket specifies an action involving a TCP port. TCP hooks not yet supported TODO: implement a realistic TCP lifecycle hook' + type: object + required: + - port + properties: + host: + description: 'Optional: Host name to connect to, defaults to the pod IP.' + type: string + port: + description: Number or name of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + timeoutSeconds: + description: 'Number of seconds after which the probe times out. Defaults to 1 second. Minimum value is 1. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + type: integer + format: int32 + stdin: + description: Whether this container should allocate a buffer for stdin in the container runtime. If this is not set, reads from stdin in the container will always result in EOF. Default is false. + type: boolean + stdinOnce: + description: Whether the container runtime should close the stdin channel after it has been opened by a single attach. When stdin is true the stdin stream will remain open across multiple attach sessions. If stdinOnce is set to true, stdin is opened on container start, is empty until the first client attaches to stdin, and then remains open and accepts data until the client disconnects, at which time stdin is closed and remains closed until the container is restarted. If this flag is false, a container processes that reads from stdin will never receive an EOF. Default is false + type: boolean + targetContainerName: + description: If set, the name of the container from PodSpec that this ephemeral container targets. The ephemeral container will be run in the namespaces (IPC, PID, etc) of this container. If not set then the ephemeral container is run in whatever namespaces are shared for the pod. Note that the container runtime must support this feature. + type: string + terminationMessagePath: + description: 'Optional: Path at which the file to which the container''s termination message will be written is mounted into the container''s filesystem. Message written is intended to be brief final status, such as an assertion failure message. Will be truncated by the node if greater than 4096 bytes. The total message length across all containers will be limited to 12kb. Defaults to /dev/termination-log. Cannot be updated.' + type: string + terminationMessagePolicy: + description: Indicate how the termination message should be populated. File will use the contents of terminationMessagePath to populate the container status message on both success and failure. FallbackToLogsOnError will use the last chunk of container log output if the termination message file is empty and the container exited with an error. The log output is limited to 2048 bytes or 80 lines, whichever is smaller. Defaults to File. Cannot be updated. + type: string + tty: + description: Whether this container should allocate a TTY for itself, also requires 'stdin' to be true. Default is false. + type: boolean + volumeDevices: + description: volumeDevices is the list of block devices to be used by the container. + type: array + items: + description: volumeDevice describes a mapping of a raw block device within a container. + type: object + required: + - devicePath + - name + properties: + devicePath: + description: devicePath is the path inside of the container that the device will be mapped to. + type: string + name: + description: name must match the name of a persistentVolumeClaim in the pod + type: string + volumeMounts: + description: Pod volumes to mount into the container's filesystem. Cannot be updated. + type: array + items: + description: VolumeMount describes a mounting of a Volume within a container. + type: object + required: + - mountPath + - name + properties: + mountPath: + description: Path within the container at which the volume should be mounted. Must not contain ':'. + type: string + mountPropagation: + description: mountPropagation determines how mounts are propagated from the host to container and the other way around. When not set, MountPropagationNone is used. This field is beta in 1.10. + type: string + name: + description: This must match the Name of a Volume. + type: string + readOnly: + description: Mounted read-only if true, read-write otherwise (false or unspecified). Defaults to false. + type: boolean + subPath: + description: Path within the volume from which the container's volume should be mounted. Defaults to "" (volume's root). + type: string + subPathExpr: + description: Expanded path within the volume from which the container's volume should be mounted. Behaves similarly to SubPath but environment variable references $(VAR_NAME) are expanded using the container's environment. Defaults to "" (volume's root). SubPathExpr and SubPath are mutually exclusive. + type: string + workingDir: + description: Container's working directory. If not specified, the container runtime's default will be used, which might be configured in the container image. Cannot be updated. + type: string + hostAliases: + description: HostAliases is an optional list of hosts and IPs that will be injected into the pod's hosts file if specified. This is only valid for non-hostNetwork pods. + type: array + items: + description: HostAlias holds the mapping between IP and hostnames that will be injected as an entry in the pod's hosts file. + type: object + properties: + hostnames: + description: Hostnames for the above IP address. + type: array + items: + type: string + ip: + description: IP address of the host file entry. + type: string + hostIPC: + description: 'Use the host''s ipc namespace. Optional: Default to false.' + type: boolean + hostNetwork: + description: Host networking requested for this pod. Use the host's network namespace. If this option is set, the ports that will be used must be specified. Default to false. + type: boolean + hostPID: + description: 'Use the host''s pid namespace. Optional: Default to false.' + type: boolean + hostname: + description: Specifies the hostname of the Pod If not specified, the pod's hostname will be set to a system-defined value. + type: string + imagePullSecrets: + description: 'ImagePullSecrets is an optional list of references to secrets in the same namespace to use for pulling any of the images used by this PodSpec. If specified, these secrets will be passed to individual puller implementations for them to use. For example, in the case of docker, only DockerConfig type secrets are honored. More info: https://kubernetes.io/docs/concepts/containers/images#specifying-imagepullsecrets-on-a-pod' + type: array + items: + description: LocalObjectReference contains enough information to let you locate the referenced object inside the same namespace. + type: object + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + initContainers: + description: 'List of initialization containers belonging to the pod. Init containers are executed in order prior to containers being started. If any init container fails, the pod is considered to have failed and is handled according to its restartPolicy. The name for an init container or normal container must be unique among all containers. Init containers may not have Lifecycle actions, Readiness probes, Liveness probes, or Startup probes. The resourceRequirements of an init container are taken into account during scheduling by finding the highest request/limit for each resource type, and then using the max of of that value or the sum of the normal containers. Limits are applied to init containers in a similar fashion. Init containers cannot currently be added or removed. Cannot be updated. More info: https://kubernetes.io/docs/concepts/workloads/pods/init-containers/' + type: array + items: + description: A single application container that you want to run within a pod. + type: object + required: + - name + properties: + args: + description: 'Arguments to the entrypoint. The docker image''s CMD is used if this is not provided. Variable references $(VAR_NAME) are expanded using the container''s environment. If a variable cannot be resolved, the reference in the input string will be unchanged. The $(VAR_NAME) syntax can be escaped with a double $$, ie: $$(VAR_NAME). Escaped references will never be expanded, regardless of whether the variable exists or not. Cannot be updated. More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell' + type: array + items: + type: string + command: + description: 'Entrypoint array. Not executed within a shell. The docker image''s ENTRYPOINT is used if this is not provided. Variable references $(VAR_NAME) are expanded using the container''s environment. If a variable cannot be resolved, the reference in the input string will be unchanged. The $(VAR_NAME) syntax can be escaped with a double $$, ie: $$(VAR_NAME). Escaped references will never be expanded, regardless of whether the variable exists or not. Cannot be updated. More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell' + type: array + items: + type: string + env: + description: List of environment variables to set in the container. Cannot be updated. + type: array + items: + description: EnvVar represents an environment variable present in a Container. + type: object + required: + - name + properties: + name: + description: Name of the environment variable. Must be a C_IDENTIFIER. + type: string + value: + description: 'Variable references $(VAR_NAME) are expanded using the previous defined environment variables in the container and any service environment variables. If a variable cannot be resolved, the reference in the input string will be unchanged. The $(VAR_NAME) syntax can be escaped with a double $$, ie: $$(VAR_NAME). Escaped references will never be expanded, regardless of whether the variable exists or not. Defaults to "".' + type: string + valueFrom: + description: Source for the environment variable's value. Cannot be used if value is not empty. + type: object + properties: + configMapKeyRef: + description: Selects a key of a ConfigMap. + type: object + required: + - key + properties: + key: + description: The key to select. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + optional: + description: Specify whether the ConfigMap or its key must be defined + type: boolean + fieldRef: + description: 'Selects a field of the pod: supports metadata.name, metadata.namespace, `metadata.labels['''']`, `metadata.annotations['''']`, spec.nodeName, spec.serviceAccountName, status.hostIP, status.podIP, status.podIPs.' + type: object + required: + - fieldPath + properties: + apiVersion: + description: Version of the schema the FieldPath is written in terms of, defaults to "v1". + type: string + fieldPath: + description: Path of the field to select in the specified API version. + type: string + resourceFieldRef: + description: 'Selects a resource of the container: only resources limits and requests (limits.cpu, limits.memory, limits.ephemeral-storage, requests.cpu, requests.memory and requests.ephemeral-storage) are currently supported.' + type: object + required: + - resource + properties: + containerName: + description: 'Container name: required for volumes, optional for env vars' + type: string + divisor: + description: Specifies the output format of the exposed resources, defaults to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource to select' + type: string + secretKeyRef: + description: Selects a key of a secret in the pod's namespace + type: object + required: + - key + properties: + key: + description: The key of the secret to select from. Must be a valid secret key. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + optional: + description: Specify whether the Secret or its key must be defined + type: boolean + envFrom: + description: List of sources to populate environment variables in the container. The keys defined within a source must be a C_IDENTIFIER. All invalid keys will be reported as an event when the container is starting. When a key exists in multiple sources, the value associated with the last source will take precedence. Values defined by an Env with a duplicate key will take precedence. Cannot be updated. + type: array + items: + description: EnvFromSource represents the source of a set of ConfigMaps + type: object + properties: + configMapRef: + description: The ConfigMap to select from + type: object + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + optional: + description: Specify whether the ConfigMap must be defined + type: boolean + prefix: + description: An optional identifier to prepend to each key in the ConfigMap. Must be a C_IDENTIFIER. + type: string + secretRef: + description: The Secret to select from + type: object + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + optional: + description: Specify whether the Secret must be defined + type: boolean + image: + description: 'Docker image name. More info: https://kubernetes.io/docs/concepts/containers/images This field is optional to allow higher level config management to default or override container images in workload controllers like Deployments and StatefulSets.' + type: string + imagePullPolicy: + description: 'Image pull policy. One of Always, Never, IfNotPresent. Defaults to Always if :latest tag is specified, or IfNotPresent otherwise. Cannot be updated. More info: https://kubernetes.io/docs/concepts/containers/images#updating-images' + type: string + lifecycle: + description: Actions that the management system should take in response to container lifecycle events. Cannot be updated. + type: object + properties: + postStart: + description: 'PostStart is called immediately after a container is created. If the handler fails, the container is terminated and restarted according to its restart policy. Other management of the container blocks until the hook completes. More info: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks' + type: object + properties: + exec: + description: One and only one of the following should be specified. Exec specifies the action to take. + type: object + properties: + command: + description: Command is the command line to execute inside the container, the working directory for the command is root ('/') in the container's filesystem. The command is simply exec'd, it is not run inside a shell, so traditional shell instructions ('|', etc) won't work. To use a shell, you need to explicitly call out to that shell. Exit status of 0 is treated as live/healthy and non-zero is unhealthy. + type: array + items: + type: string + httpGet: + description: HTTPGet specifies the http request to perform. + type: object + required: + - port + properties: + host: + description: Host name to connect to, defaults to the pod IP. You probably want to set "Host" in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the request. HTTP allows repeated headers. + type: array + items: + description: HTTPHeader describes a custom header to be used in HTTP probes + type: object + required: + - name + - value + properties: + name: + description: The header field name + type: string + value: + description: The header field value + type: string + path: + description: Path to access on the HTTP server. + type: string + port: + description: Name or number of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + description: Scheme to use for connecting to the host. Defaults to HTTP. + type: string + tcpSocket: + description: 'TCPSocket specifies an action involving a TCP port. TCP hooks not yet supported TODO: implement a realistic TCP lifecycle hook' + type: object + required: + - port + properties: + host: + description: 'Optional: Host name to connect to, defaults to the pod IP.' + type: string + port: + description: Number or name of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + preStop: + description: 'PreStop is called immediately before a container is terminated due to an API request or management event such as liveness/startup probe failure, preemption, resource contention, etc. The handler is not called if the container crashes or exits. The reason for termination is passed to the handler. The Pod''s termination grace period countdown begins before the PreStop hooked is executed. Regardless of the outcome of the handler, the container will eventually terminate within the Pod''s termination grace period. Other management of the container blocks until the hook completes or until the termination grace period is reached. More info: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks' + type: object + properties: + exec: + description: One and only one of the following should be specified. Exec specifies the action to take. + type: object + properties: + command: + description: Command is the command line to execute inside the container, the working directory for the command is root ('/') in the container's filesystem. The command is simply exec'd, it is not run inside a shell, so traditional shell instructions ('|', etc) won't work. To use a shell, you need to explicitly call out to that shell. Exit status of 0 is treated as live/healthy and non-zero is unhealthy. + type: array + items: + type: string + httpGet: + description: HTTPGet specifies the http request to perform. + type: object + required: + - port + properties: + host: + description: Host name to connect to, defaults to the pod IP. You probably want to set "Host" in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the request. HTTP allows repeated headers. + type: array + items: + description: HTTPHeader describes a custom header to be used in HTTP probes + type: object + required: + - name + - value + properties: + name: + description: The header field name + type: string + value: + description: The header field value + type: string + path: + description: Path to access on the HTTP server. + type: string + port: + description: Name or number of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + description: Scheme to use for connecting to the host. Defaults to HTTP. + type: string + tcpSocket: + description: 'TCPSocket specifies an action involving a TCP port. TCP hooks not yet supported TODO: implement a realistic TCP lifecycle hook' + type: object + required: + - port + properties: + host: + description: 'Optional: Host name to connect to, defaults to the pod IP.' + type: string + port: + description: Number or name of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + livenessProbe: + description: 'Periodic probe of container liveness. Container will be restarted if the probe fails. Cannot be updated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + type: object + properties: + exec: + description: One and only one of the following should be specified. Exec specifies the action to take. + type: object + properties: + command: + description: Command is the command line to execute inside the container, the working directory for the command is root ('/') in the container's filesystem. The command is simply exec'd, it is not run inside a shell, so traditional shell instructions ('|', etc) won't work. To use a shell, you need to explicitly call out to that shell. Exit status of 0 is treated as live/healthy and non-zero is unhealthy. + type: array + items: + type: string + failureThreshold: + description: Minimum consecutive failures for the probe to be considered failed after having succeeded. Defaults to 3. Minimum value is 1. + type: integer + format: int32 + httpGet: + description: HTTPGet specifies the http request to perform. + type: object + required: + - port + properties: + host: + description: Host name to connect to, defaults to the pod IP. You probably want to set "Host" in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the request. HTTP allows repeated headers. + type: array + items: + description: HTTPHeader describes a custom header to be used in HTTP probes + type: object + required: + - name + - value + properties: + name: + description: The header field name + type: string + value: + description: The header field value + type: string + path: + description: Path to access on the HTTP server. + type: string + port: + description: Name or number of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + description: Scheme to use for connecting to the host. Defaults to HTTP. + type: string + initialDelaySeconds: + description: 'Number of seconds after the container has started before liveness probes are initiated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + type: integer + format: int32 + periodSeconds: + description: How often (in seconds) to perform the probe. Default to 10 seconds. Minimum value is 1. + type: integer + format: int32 + successThreshold: + description: Minimum consecutive successes for the probe to be considered successful after having failed. Defaults to 1. Must be 1 for liveness and startup. Minimum value is 1. + type: integer + format: int32 + tcpSocket: + description: 'TCPSocket specifies an action involving a TCP port. TCP hooks not yet supported TODO: implement a realistic TCP lifecycle hook' + type: object + required: + - port + properties: + host: + description: 'Optional: Host name to connect to, defaults to the pod IP.' + type: string + port: + description: Number or name of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + timeoutSeconds: + description: 'Number of seconds after which the probe times out. Defaults to 1 second. Minimum value is 1. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + type: integer + format: int32 + name: + description: Name of the container specified as a DNS_LABEL. Each container in a pod must have a unique name (DNS_LABEL). Cannot be updated. + type: string + ports: + description: List of ports to expose from the container. Exposing a port here gives the system additional information about the network connections a container uses, but is primarily informational. Not specifying a port here DOES NOT prevent that port from being exposed. Any port which is listening on the default "0.0.0.0" address inside a container will be accessible from the network. Cannot be updated. + type: array + items: + description: ContainerPort represents a network port in a single container. + type: object + required: + - containerPort + properties: + containerPort: + description: Number of port to expose on the pod's IP address. This must be a valid port number, 0 < x < 65536. + type: integer + format: int32 + hostIP: + description: What host IP to bind the external port to. + type: string + hostPort: + description: Number of port to expose on the host. If specified, this must be a valid port number, 0 < x < 65536. If HostNetwork is specified, this must match ContainerPort. Most containers do not need this. + type: integer + format: int32 + name: + description: If specified, this must be an IANA_SVC_NAME and unique within the pod. Each named port in a pod must have a unique name. Name for the port that can be referred to by services. + type: string + protocol: + description: Protocol for port. Must be UDP, TCP, or SCTP. Defaults to "TCP". + type: string + default: TCP + x-kubernetes-list-map-keys: + - containerPort + - protocol + x-kubernetes-list-type: map + readinessProbe: + description: 'Periodic probe of container service readiness. Container will be removed from service endpoints if the probe fails. Cannot be updated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + type: object + properties: + exec: + description: One and only one of the following should be specified. Exec specifies the action to take. + type: object + properties: + command: + description: Command is the command line to execute inside the container, the working directory for the command is root ('/') in the container's filesystem. The command is simply exec'd, it is not run inside a shell, so traditional shell instructions ('|', etc) won't work. To use a shell, you need to explicitly call out to that shell. Exit status of 0 is treated as live/healthy and non-zero is unhealthy. + type: array + items: + type: string + failureThreshold: + description: Minimum consecutive failures for the probe to be considered failed after having succeeded. Defaults to 3. Minimum value is 1. + type: integer + format: int32 + httpGet: + description: HTTPGet specifies the http request to perform. + type: object + required: + - port + properties: + host: + description: Host name to connect to, defaults to the pod IP. You probably want to set "Host" in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the request. HTTP allows repeated headers. + type: array + items: + description: HTTPHeader describes a custom header to be used in HTTP probes + type: object + required: + - name + - value + properties: + name: + description: The header field name + type: string + value: + description: The header field value + type: string + path: + description: Path to access on the HTTP server. + type: string + port: + description: Name or number of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + description: Scheme to use for connecting to the host. Defaults to HTTP. + type: string + initialDelaySeconds: + description: 'Number of seconds after the container has started before liveness probes are initiated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + type: integer + format: int32 + periodSeconds: + description: How often (in seconds) to perform the probe. Default to 10 seconds. Minimum value is 1. + type: integer + format: int32 + successThreshold: + description: Minimum consecutive successes for the probe to be considered successful after having failed. Defaults to 1. Must be 1 for liveness and startup. Minimum value is 1. + type: integer + format: int32 + tcpSocket: + description: 'TCPSocket specifies an action involving a TCP port. TCP hooks not yet supported TODO: implement a realistic TCP lifecycle hook' + type: object + required: + - port + properties: + host: + description: 'Optional: Host name to connect to, defaults to the pod IP.' + type: string + port: + description: Number or name of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + timeoutSeconds: + description: 'Number of seconds after which the probe times out. Defaults to 1 second. Minimum value is 1. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + type: integer + format: int32 + resources: + description: 'Compute Resources required by this container. Cannot be updated. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' + type: object + properties: + limits: + description: 'Limits describes the maximum amount of compute resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' + type: object + additionalProperties: + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + requests: + description: 'Requests describes the minimum amount of compute resources required. If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, otherwise to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' + type: object + additionalProperties: + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + securityContext: + description: 'Security options the pod should run with. More info: https://kubernetes.io/docs/concepts/policy/security-context/ More info: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/' + type: object + properties: + allowPrivilegeEscalation: + description: 'AllowPrivilegeEscalation controls whether a process can gain more privileges than its parent process. This bool directly controls if the no_new_privs flag will be set on the container process. AllowPrivilegeEscalation is true always when the container is: 1) run as Privileged 2) has CAP_SYS_ADMIN' + type: boolean + capabilities: + description: The capabilities to add/drop when running containers. Defaults to the default set of capabilities granted by the container runtime. + type: object + properties: + add: + description: Added capabilities + type: array + items: + description: Capability represent POSIX capabilities type + type: string + drop: + description: Removed capabilities + type: array + items: + description: Capability represent POSIX capabilities type + type: string + privileged: + description: Run container in privileged mode. Processes in privileged containers are essentially equivalent to root on the host. Defaults to false. + type: boolean + procMount: + description: procMount denotes the type of proc mount to use for the containers. The default is DefaultProcMount which uses the container runtime defaults for readonly paths and masked paths. This requires the ProcMountType feature flag to be enabled. + type: string + readOnlyRootFilesystem: + description: Whether this container has a read-only root filesystem. Default is false. + type: boolean + runAsGroup: + description: The GID to run the entrypoint of the container process. Uses runtime default if unset. May also be set in PodSecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence. + type: integer + format: int64 + runAsNonRoot: + description: Indicates that the container must run as a non-root user. If true, the Kubelet will validate the image at runtime to ensure that it does not run as UID 0 (root) and fail to start the container if it does. If unset or false, no such validation will be performed. May also be set in PodSecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence. + type: boolean + runAsUser: + description: The UID to run the entrypoint of the container process. Defaults to user specified in image metadata if unspecified. May also be set in PodSecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence. + type: integer + format: int64 + seLinuxOptions: + description: The SELinux context to be applied to the container. If unspecified, the container runtime will allocate a random SELinux context for each container. May also be set in PodSecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence. + type: object + properties: + level: + description: Level is SELinux level label that applies to the container. + type: string + role: + description: Role is a SELinux role label that applies to the container. + type: string + type: + description: Type is a SELinux type label that applies to the container. + type: string + user: + description: User is a SELinux user label that applies to the container. + type: string + seccompProfile: + description: The seccomp options to use by this container. If seccomp options are provided at both the pod & container level, the container options override the pod options. + type: object + required: + - type + properties: + localhostProfile: + description: localhostProfile indicates a profile defined in a file on the node should be used. The profile must be preconfigured on the node to work. Must be a descending path, relative to the kubelet's configured seccomp profile location. Must only be set if type is "Localhost". + type: string + type: + description: "type indicates which kind of seccomp profile will be applied. Valid options are: \n Localhost - a profile defined in a file on the node should be used. RuntimeDefault - the container runtime default profile should be used. Unconfined - no profile should be applied." + type: string + windowsOptions: + description: The Windows specific settings applied to all containers. If unspecified, the options from the PodSecurityContext will be used. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence. + type: object + properties: + gmsaCredentialSpec: + description: GMSACredentialSpec is where the GMSA admission webhook (https://github.com/kubernetes-sigs/windows-gmsa) inlines the contents of the GMSA credential spec named by the GMSACredentialSpecName field. + type: string + gmsaCredentialSpecName: + description: GMSACredentialSpecName is the name of the GMSA credential spec to use. + type: string + runAsUserName: + description: The UserName in Windows to run the entrypoint of the container process. Defaults to the user specified in image metadata if unspecified. May also be set in PodSecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence. + type: string + startupProbe: + description: 'StartupProbe indicates that the Pod has successfully initialized. If specified, no other probes are executed until this completes successfully. If this probe fails, the Pod will be restarted, just as if the livenessProbe failed. This can be used to provide different probe parameters at the beginning of a Pod''s lifecycle, when it might take a long time to load data or warm a cache, than during steady-state operation. This cannot be updated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + type: object + properties: + exec: + description: One and only one of the following should be specified. Exec specifies the action to take. + type: object + properties: + command: + description: Command is the command line to execute inside the container, the working directory for the command is root ('/') in the container's filesystem. The command is simply exec'd, it is not run inside a shell, so traditional shell instructions ('|', etc) won't work. To use a shell, you need to explicitly call out to that shell. Exit status of 0 is treated as live/healthy and non-zero is unhealthy. + type: array + items: + type: string + failureThreshold: + description: Minimum consecutive failures for the probe to be considered failed after having succeeded. Defaults to 3. Minimum value is 1. + type: integer + format: int32 + httpGet: + description: HTTPGet specifies the http request to perform. + type: object + required: + - port + properties: + host: + description: Host name to connect to, defaults to the pod IP. You probably want to set "Host" in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the request. HTTP allows repeated headers. + type: array + items: + description: HTTPHeader describes a custom header to be used in HTTP probes + type: object + required: + - name + - value + properties: + name: + description: The header field name + type: string + value: + description: The header field value + type: string + path: + description: Path to access on the HTTP server. + type: string + port: + description: Name or number of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + description: Scheme to use for connecting to the host. Defaults to HTTP. + type: string + initialDelaySeconds: + description: 'Number of seconds after the container has started before liveness probes are initiated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + type: integer + format: int32 + periodSeconds: + description: How often (in seconds) to perform the probe. Default to 10 seconds. Minimum value is 1. + type: integer + format: int32 + successThreshold: + description: Minimum consecutive successes for the probe to be considered successful after having failed. Defaults to 1. Must be 1 for liveness and startup. Minimum value is 1. + type: integer + format: int32 + tcpSocket: + description: 'TCPSocket specifies an action involving a TCP port. TCP hooks not yet supported TODO: implement a realistic TCP lifecycle hook' + type: object + required: + - port + properties: + host: + description: 'Optional: Host name to connect to, defaults to the pod IP.' + type: string + port: + description: Number or name of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + timeoutSeconds: + description: 'Number of seconds after which the probe times out. Defaults to 1 second. Minimum value is 1. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + type: integer + format: int32 + stdin: + description: Whether this container should allocate a buffer for stdin in the container runtime. If this is not set, reads from stdin in the container will always result in EOF. Default is false. + type: boolean + stdinOnce: + description: Whether the container runtime should close the stdin channel after it has been opened by a single attach. When stdin is true the stdin stream will remain open across multiple attach sessions. If stdinOnce is set to true, stdin is opened on container start, is empty until the first client attaches to stdin, and then remains open and accepts data until the client disconnects, at which time stdin is closed and remains closed until the container is restarted. If this flag is false, a container processes that reads from stdin will never receive an EOF. Default is false + type: boolean + terminationMessagePath: + description: 'Optional: Path at which the file to which the container''s termination message will be written is mounted into the container''s filesystem. Message written is intended to be brief final status, such as an assertion failure message. Will be truncated by the node if greater than 4096 bytes. The total message length across all containers will be limited to 12kb. Defaults to /dev/termination-log. Cannot be updated.' + type: string + terminationMessagePolicy: + description: Indicate how the termination message should be populated. File will use the contents of terminationMessagePath to populate the container status message on both success and failure. FallbackToLogsOnError will use the last chunk of container log output if the termination message file is empty and the container exited with an error. The log output is limited to 2048 bytes or 80 lines, whichever is smaller. Defaults to File. Cannot be updated. + type: string + tty: + description: Whether this container should allocate a TTY for itself, also requires 'stdin' to be true. Default is false. + type: boolean + volumeDevices: + description: volumeDevices is the list of block devices to be used by the container. + type: array + items: + description: volumeDevice describes a mapping of a raw block device within a container. + type: object + required: + - devicePath + - name + properties: + devicePath: + description: devicePath is the path inside of the container that the device will be mapped to. + type: string + name: + description: name must match the name of a persistentVolumeClaim in the pod + type: string + volumeMounts: + description: Pod volumes to mount into the container's filesystem. Cannot be updated. + type: array + items: + description: VolumeMount describes a mounting of a Volume within a container. + type: object + required: + - mountPath + - name + properties: + mountPath: + description: Path within the container at which the volume should be mounted. Must not contain ':'. + type: string + mountPropagation: + description: mountPropagation determines how mounts are propagated from the host to container and the other way around. When not set, MountPropagationNone is used. This field is beta in 1.10. + type: string + name: + description: This must match the Name of a Volume. + type: string + readOnly: + description: Mounted read-only if true, read-write otherwise (false or unspecified). Defaults to false. + type: boolean + subPath: + description: Path within the volume from which the container's volume should be mounted. Defaults to "" (volume's root). + type: string + subPathExpr: + description: Expanded path within the volume from which the container's volume should be mounted. Behaves similarly to SubPath but environment variable references $(VAR_NAME) are expanded using the container's environment. Defaults to "" (volume's root). SubPathExpr and SubPath are mutually exclusive. + type: string + workingDir: + description: Container's working directory. If not specified, the container runtime's default will be used, which might be configured in the container image. Cannot be updated. + type: string + nodeName: + description: NodeName is a request to schedule this pod onto a specific node. If it is non-empty, the scheduler simply schedules this pod onto that node, assuming that it fits resource requirements. + type: string + nodeSelector: + description: 'NodeSelector is a selector which must be true for the pod to fit on a node. Selector which must match a node''s labels for the pod to be scheduled on that node. More info: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/' + type: object + additionalProperties: + type: string + overhead: + description: 'Overhead represents the resource overhead associated with running a pod for a given RuntimeClass. This field will be autopopulated at admission time by the RuntimeClass admission controller. If the RuntimeClass admission controller is enabled, overhead must not be set in Pod create requests. The RuntimeClass admission controller will reject Pod create requests which have the overhead already set. If RuntimeClass is configured and selected in the PodSpec, Overhead will be set to the value defined in the corresponding RuntimeClass, otherwise it will remain unset and treated as zero. More info: https://git.k8s.io/enhancements/keps/sig-node/20190226-pod-overhead.md This field is alpha-level as of Kubernetes v1.16, and is only honored by servers that enable the PodOverhead feature.' + type: object + additionalProperties: + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + preemptionPolicy: + description: PreemptionPolicy is the Policy for preempting pods with lower priority. One of Never, PreemptLowerPriority. Defaults to PreemptLowerPriority if unset. This field is beta-level, gated by the NonPreemptingPriority feature-gate. + type: string + priority: + description: The priority value. Various system components use this field to find the priority of the pod. When Priority Admission Controller is enabled, it prevents users from setting this field. The admission controller populates this field from PriorityClassName. The higher the value, the higher the priority. + type: integer + format: int32 + priorityClassName: + description: If specified, indicates the pod's priority. "system-node-critical" and "system-cluster-critical" are two special keywords which indicate the highest priorities with the former being the highest priority. Any other name must be defined by creating a PriorityClass object with that name. If not specified, the pod priority will be default or zero if there is no default. + type: string + readinessGates: + description: 'If specified, all readiness gates will be evaluated for pod readiness. A pod is ready when all its containers are ready AND all conditions specified in the readiness gates have status equal to "True" More info: https://git.k8s.io/enhancements/keps/sig-network/0007-pod-ready%2B%2B.md' + type: array + items: + description: PodReadinessGate contains the reference to a pod condition + type: object + required: + - conditionType + properties: + conditionType: + description: ConditionType refers to a condition in the pod's condition list with matching type. + type: string + restartPolicy: + description: 'Restart policy for all containers within the pod. One of Always, OnFailure, Never. Default to Always. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#restart-policy' + type: string + runtimeClassName: + description: 'RuntimeClassName refers to a RuntimeClass object in the node.k8s.io group, which should be used to run this pod. If no RuntimeClass resource matches the named class, the pod will not be run. If unset or empty, the "legacy" RuntimeClass will be used, which is an implicit class with an empty definition that uses the default runtime handler. More info: https://git.k8s.io/enhancements/keps/sig-node/runtime-class.md This is a beta feature as of Kubernetes v1.14.' + type: string + schedulerName: + description: If specified, the pod will be dispatched by specified scheduler. If not specified, the pod will be dispatched by default scheduler. + type: string + securityContext: + description: 'SecurityContext holds pod-level security attributes and common container settings. Optional: Defaults to empty. See type description for default values of each field.' + type: object + properties: + fsGroup: + description: "A special supplemental group that applies to all containers in a pod. Some volume types allow the Kubelet to change the ownership of that volume to be owned by the pod: \n 1. The owning GID will be the FSGroup 2. The setgid bit is set (new files created in the volume will be owned by FSGroup) 3. The permission bits are OR'd with rw-rw---- \n If unset, the Kubelet will not modify the ownership and permissions of any volume." + type: integer + format: int64 + fsGroupChangePolicy: + description: 'fsGroupChangePolicy defines behavior of changing ownership and permission of the volume before being exposed inside Pod. This field will only apply to volume types which support fsGroup based ownership(and permissions). It will have no effect on ephemeral volume types such as: secret, configmaps and emptydir. Valid values are "OnRootMismatch" and "Always". If not specified, "Always" is used.' + type: string + runAsGroup: + description: The GID to run the entrypoint of the container process. Uses runtime default if unset. May also be set in SecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence for that container. + type: integer + format: int64 + runAsNonRoot: + description: Indicates that the container must run as a non-root user. If true, the Kubelet will validate the image at runtime to ensure that it does not run as UID 0 (root) and fail to start the container if it does. If unset or false, no such validation will be performed. May also be set in SecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence. + type: boolean + runAsUser: + description: The UID to run the entrypoint of the container process. Defaults to user specified in image metadata if unspecified. May also be set in SecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence for that container. + type: integer + format: int64 + seLinuxOptions: + description: The SELinux context to be applied to all containers. If unspecified, the container runtime will allocate a random SELinux context for each container. May also be set in SecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence for that container. + type: object + properties: + level: + description: Level is SELinux level label that applies to the container. + type: string + role: + description: Role is a SELinux role label that applies to the container. + type: string + type: + description: Type is a SELinux type label that applies to the container. + type: string + user: + description: User is a SELinux user label that applies to the container. + type: string + seccompProfile: + description: The seccomp options to use by the containers in this pod. + type: object + required: + - type + properties: + localhostProfile: + description: localhostProfile indicates a profile defined in a file on the node should be used. The profile must be preconfigured on the node to work. Must be a descending path, relative to the kubelet's configured seccomp profile location. Must only be set if type is "Localhost". + type: string + type: + description: "type indicates which kind of seccomp profile will be applied. Valid options are: \n Localhost - a profile defined in a file on the node should be used. RuntimeDefault - the container runtime default profile should be used. Unconfined - no profile should be applied." + type: string + supplementalGroups: + description: A list of groups applied to the first process run in each container, in addition to the container's primary GID. If unspecified, no groups will be added to any container. + type: array + items: + type: integer + format: int64 + sysctls: + description: Sysctls hold a list of namespaced sysctls used for the pod. Pods with unsupported sysctls (by the container runtime) might fail to launch. + type: array + items: + description: Sysctl defines a kernel parameter to be set + type: object + required: + - name + - value + properties: + name: + description: Name of a property to set + type: string + value: + description: Value of a property to set + type: string + windowsOptions: + description: The Windows specific settings applied to all containers. If unspecified, the options within a container's SecurityContext will be used. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence. + type: object + properties: + gmsaCredentialSpec: + description: GMSACredentialSpec is where the GMSA admission webhook (https://github.com/kubernetes-sigs/windows-gmsa) inlines the contents of the GMSA credential spec named by the GMSACredentialSpecName field. + type: string + gmsaCredentialSpecName: + description: GMSACredentialSpecName is the name of the GMSA credential spec to use. + type: string + runAsUserName: + description: The UserName in Windows to run the entrypoint of the container process. Defaults to the user specified in image metadata if unspecified. May also be set in PodSecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence. + type: string + serviceAccount: + description: 'DeprecatedServiceAccount is a depreciated alias for ServiceAccountName. Deprecated: Use serviceAccountName instead.' + type: string + serviceAccountName: + description: 'ServiceAccountName is the name of the ServiceAccount to use to run this pod. More info: https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/' + type: string + setHostnameAsFQDN: + description: If true the pod's hostname will be configured as the pod's FQDN, rather than the leaf name (the default). In Linux containers, this means setting the FQDN in the hostname field of the kernel (the nodename field of struct utsname). In Windows containers, this means setting the registry value of hostname for the registry key HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters to FQDN. If a pod does not have FQDN, this has no effect. Default to false. + type: boolean + shareProcessNamespace: + description: 'Share a single process namespace between all of the containers in a pod. When this is set containers will be able to view and signal processes from other containers in the same pod, and the first process in each container will not be assigned PID 1. HostPID and ShareProcessNamespace cannot both be set. Optional: Default to false.' + type: boolean + subdomain: + description: If specified, the fully qualified Pod hostname will be "...svc.". If not specified, the pod will not have a domainname at all. + type: string + terminationGracePeriodSeconds: + description: Optional duration in seconds the pod needs to terminate gracefully. May be decreased in delete request. Value must be non-negative integer. The value zero indicates delete immediately. If this value is nil, the default grace period will be used instead. The grace period is the duration in seconds after the processes running in the pod are sent a termination signal and the time when the processes are forcibly halted with a kill signal. Set this value longer than the expected cleanup time for your process. Defaults to 30 seconds. + type: integer + format: int64 + tolerations: + description: If specified, the pod's tolerations. + type: array + items: + description: The pod this Toleration is attached to tolerates any taint that matches the triple using the matching operator . + type: object + properties: + effect: + description: Effect indicates the taint effect to match. Empty means match all taint effects. When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: Key is the taint key that the toleration applies to. Empty means match all taint keys. If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: Operator represents a key's relationship to the value. Valid operators are Exists and Equal. Defaults to Equal. Exists is equivalent to wildcard for value, so that a pod can tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: TolerationSeconds represents the period of time the toleration (which must be of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, it is not set, which means tolerate the taint forever (do not evict). Zero and negative values will be treated as 0 (evict immediately) by the system. + type: integer + format: int64 + value: + description: Value is the taint value the toleration matches to. If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + topologySpreadConstraints: + description: TopologySpreadConstraints describes how a group of pods ought to spread across topology domains. Scheduler will schedule pods in a way which abides by the constraints. All topologySpreadConstraints are ANDed. + type: array + items: + description: TopologySpreadConstraint specifies how to spread matching pods among the given topology. + type: object + required: + - maxSkew + - topologyKey + - whenUnsatisfiable + properties: + labelSelector: + description: LabelSelector is used to find matching pods. Pods that match this label selector are counted to determine the number of pods in their corresponding topology domain. + type: object + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + type: array + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + type: array + items: + type: string + matchLabels: + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + additionalProperties: + type: string + maxSkew: + description: 'MaxSkew describes the degree to which pods may be unevenly distributed. When `whenUnsatisfiable=DoNotSchedule`, it is the maximum permitted difference between the number of matching pods in the target topology and the global minimum. For example, in a 3-zone cluster, MaxSkew is set to 1, and pods with the same labelSelector spread as 1/1/0: | zone1 | zone2 | zone3 | | P | P | | - if MaxSkew is 1, incoming pod can only be scheduled to zone3 to become 1/1/1; scheduling it onto zone1(zone2) would make the ActualSkew(2-0) on zone1(zone2) violate MaxSkew(1). - if MaxSkew is 2, incoming pod can be scheduled onto any zone. When `whenUnsatisfiable=ScheduleAnyway`, it is used to give higher precedence to topologies that satisfy it. It''s a required field. Default value is 1 and 0 is not allowed.' + type: integer + format: int32 + topologyKey: + description: TopologyKey is the key of node labels. Nodes that have a label with this key and identical values are considered to be in the same topology. We consider each as a "bucket", and try to put balanced number of pods into each bucket. It's a required field. + type: string + whenUnsatisfiable: + description: 'WhenUnsatisfiable indicates how to deal with a pod if it doesn''t satisfy the spread constraint. - DoNotSchedule (default) tells the scheduler not to schedule it. - ScheduleAnyway tells the scheduler to schedule the pod in any location, but giving higher precedence to topologies that would help reduce the skew. A constraint is considered "Unsatisfiable" for an incoming pod if and only if every possible node assigment for that pod would violate "MaxSkew" on some topology. For example, in a 3-zone cluster, MaxSkew is set to 1, and pods with the same labelSelector spread as 3/1/1: | zone1 | zone2 | zone3 | | P P P | P | P | If WhenUnsatisfiable is set to DoNotSchedule, incoming pod can only be scheduled to zone2(zone3) to become 3/2/1(3/1/2) as ActualSkew(2-1) on zone2(zone3) satisfies MaxSkew(1). In other words, the cluster can still be imbalanced, but scheduler won''t make it *more* imbalanced. It''s a required field.' + type: string + x-kubernetes-list-map-keys: + - topologyKey + - whenUnsatisfiable + x-kubernetes-list-type: map + volumes: + description: 'List of volumes that can be mounted by containers belonging to the pod. More info: https://kubernetes.io/docs/concepts/storage/volumes' + type: array + items: + description: Volume represents a named volume in a pod that may be accessed by any container in the pod. + type: object + required: + - name + properties: + awsElasticBlockStore: + description: 'AWSElasticBlockStore represents an AWS Disk resource that is attached to a kubelet''s host machine and then exposed to the pod. More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore' + type: object + required: + - volumeID + properties: + fsType: + description: 'Filesystem type of the volume that you want to mount. Tip: Ensure that the filesystem type is supported by the host operating system. Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore TODO: how do we prevent errors in the filesystem from compromising the machine' + type: string + partition: + description: 'The partition in the volume that you want to mount. If omitted, the default is to mount by volume name. Examples: For volume /dev/sda1, you specify the partition as "1". Similarly, the volume partition for /dev/sda is "0" (or you can leave the property empty).' + type: integer + format: int32 + readOnly: + description: 'Specify "true" to force and set the ReadOnly property in VolumeMounts to "true". If omitted, the default is "false". More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore' + type: boolean + volumeID: + description: 'Unique ID of the persistent disk resource in AWS (Amazon EBS volume). More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore' + type: string + azureDisk: + description: AzureDisk represents an Azure Data Disk mount on the host and bind mount to the pod. + type: object + required: + - diskName + - diskURI + properties: + cachingMode: + description: 'Host Caching mode: None, Read Only, Read Write.' + type: string + diskName: + description: The Name of the data disk in the blob storage + type: string + diskURI: + description: The URI the data disk in the blob storage + type: string + fsType: + description: Filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + type: string + kind: + description: 'Expected values Shared: multiple blob disks per storage account Dedicated: single blob disk per storage account Managed: azure managed data disk (only in managed availability set). defaults to shared' + type: string + readOnly: + description: Defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts. + type: boolean + azureFile: + description: AzureFile represents an Azure File Service mount on the host and bind mount to the pod. + type: object + required: + - secretName + - shareName + properties: + readOnly: + description: Defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts. + type: boolean + secretName: + description: the name of secret that contains Azure Storage Account Name and Key + type: string + shareName: + description: Share Name + type: string + cephfs: + description: CephFS represents a Ceph FS mount on the host that shares a pod's lifetime + type: object + required: + - monitors + properties: + monitors: + description: 'Required: Monitors is a collection of Ceph monitors More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it' + type: array + items: + type: string + path: + description: 'Optional: Used as the mounted root, rather than the full Ceph tree, default is /' + type: string + readOnly: + description: 'Optional: Defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts. More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it' + type: boolean + secretFile: + description: 'Optional: SecretFile is the path to key ring for User, default is /etc/ceph/user.secret More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it' + type: string + secretRef: + description: 'Optional: SecretRef is reference to the authentication secret for User, default is empty. More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it' + type: object + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + user: + description: 'Optional: User is the rados user name, default is admin More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it' + type: string + cinder: + description: 'Cinder represents a cinder volume attached and mounted on kubelets host machine. More info: https://examples.k8s.io/mysql-cinder-pd/README.md' + type: object + required: + - volumeID + properties: + fsType: + description: 'Filesystem type to mount. Must be a filesystem type supported by the host operating system. Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. More info: https://examples.k8s.io/mysql-cinder-pd/README.md' + type: string + readOnly: + description: 'Optional: Defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts. More info: https://examples.k8s.io/mysql-cinder-pd/README.md' + type: boolean + secretRef: + description: 'Optional: points to a secret object containing parameters used to connect to OpenStack.' + type: object + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + volumeID: + description: 'volume id used to identify the volume in cinder. More info: https://examples.k8s.io/mysql-cinder-pd/README.md' + type: string + configMap: + description: ConfigMap represents a configMap that should populate this volume + type: object + properties: + defaultMode: + description: 'Optional: mode bits used to set permissions on created files by default. Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. Defaults to 0644. Directories within the path are not affected by this setting. This might be in conflict with other options that affect the file mode, like fsGroup, and the result can be other mode bits set.' + type: integer + format: int32 + items: + description: If unspecified, each key-value pair in the Data field of the referenced ConfigMap will be projected into the volume as a file whose name is the key and content is the value. If specified, the listed keys will be projected into the specified paths, and unlisted keys will not be present. If a key is specified which is not present in the ConfigMap, the volume setup will error unless it is marked optional. Paths must be relative and may not contain the '..' path or start with '..'. + type: array + items: + description: Maps a string key to a path within a volume. + type: object + required: + - key + - path + properties: + key: + description: The key to project. + type: string + mode: + description: 'Optional: mode bits used to set permissions on this file. Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. If not specified, the volume defaultMode will be used. This might be in conflict with other options that affect the file mode, like fsGroup, and the result can be other mode bits set.' + type: integer + format: int32 + path: + description: The relative path of the file to map the key to. May not be an absolute path. May not contain the path element '..'. May not start with the string '..'. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + optional: + description: Specify whether the ConfigMap or its keys must be defined + type: boolean + csi: + description: CSI (Container Storage Interface) represents ephemeral storage that is handled by certain external CSI drivers (Beta feature). + type: object + required: + - driver + properties: + driver: + description: Driver is the name of the CSI driver that handles this volume. Consult with your admin for the correct name as registered in the cluster. + type: string + fsType: + description: Filesystem type to mount. Ex. "ext4", "xfs", "ntfs". If not provided, the empty value is passed to the associated CSI driver which will determine the default filesystem to apply. + type: string + nodePublishSecretRef: + description: NodePublishSecretRef is a reference to the secret object containing sensitive information to pass to the CSI driver to complete the CSI NodePublishVolume and NodeUnpublishVolume calls. This field is optional, and may be empty if no secret is required. If the secret object contains more than one secret, all secret references are passed. + type: object + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + readOnly: + description: Specifies a read-only configuration for the volume. Defaults to false (read/write). + type: boolean + volumeAttributes: + description: VolumeAttributes stores driver-specific properties that are passed to the CSI driver. Consult your driver's documentation for supported values. + type: object + additionalProperties: + type: string + downwardAPI: + description: DownwardAPI represents downward API about the pod that should populate this volume + type: object + properties: + defaultMode: + description: 'Optional: mode bits to use on created files by default. Must be a Optional: mode bits used to set permissions on created files by default. Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. Defaults to 0644. Directories within the path are not affected by this setting. This might be in conflict with other options that affect the file mode, like fsGroup, and the result can be other mode bits set.' + type: integer + format: int32 + items: + description: Items is a list of downward API volume file + type: array + items: + description: DownwardAPIVolumeFile represents information to create the file containing the pod field + type: object + required: + - path + properties: + fieldRef: + description: 'Required: Selects a field of the pod: only annotations, labels, name and namespace are supported.' + type: object + required: + - fieldPath + properties: + apiVersion: + description: Version of the schema the FieldPath is written in terms of, defaults to "v1". + type: string + fieldPath: + description: Path of the field to select in the specified API version. + type: string + mode: + description: 'Optional: mode bits used to set permissions on this file, must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. If not specified, the volume defaultMode will be used. This might be in conflict with other options that affect the file mode, like fsGroup, and the result can be other mode bits set.' + type: integer + format: int32 + path: + description: 'Required: Path is the relative path name of the file to be created. Must not be absolute or contain the ''..'' path. Must be utf-8 encoded. The first item of the relative path must not start with ''..''' + type: string + resourceFieldRef: + description: 'Selects a resource of the container: only resources limits and requests (limits.cpu, limits.memory, requests.cpu and requests.memory) are currently supported.' + type: object + required: + - resource + properties: + containerName: + description: 'Container name: required for volumes, optional for env vars' + type: string + divisor: + description: Specifies the output format of the exposed resources, defaults to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource to select' + type: string + emptyDir: + description: 'EmptyDir represents a temporary directory that shares a pod''s lifetime. More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir' + type: object + properties: + medium: + description: 'What type of storage medium should back this directory. The default is "" which means to use the node''s default medium. Must be an empty string (default) or Memory. More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir' + type: string + sizeLimit: + description: 'Total amount of local storage required for this EmptyDir volume. The size limit is also applicable for memory medium. The maximum usage on memory medium EmptyDir would be the minimum value between the SizeLimit specified here and the sum of memory limits of all containers in a pod. The default is nil which means that the limit is undefined. More info: http://kubernetes.io/docs/user-guide/volumes#emptydir' + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + ephemeral: + description: "Ephemeral represents a volume that is handled by a cluster storage driver (Alpha feature). The volume's lifecycle is tied to the pod that defines it - it will be created before the pod starts, and deleted when the pod is removed. \n Use this if: a) the volume is only needed while the pod runs, b) features of normal volumes like restoring from snapshot or capacity tracking are needed, c) the storage driver is specified through a storage class, and d) the storage driver supports dynamic volume provisioning through a PersistentVolumeClaim (see EphemeralVolumeSource for more information on the connection between this volume type and PersistentVolumeClaim). \n Use PersistentVolumeClaim or one of the vendor-specific APIs for volumes that persist for longer than the lifecycle of an individual pod. \n Use CSI for light-weight local ephemeral volumes if the CSI driver is meant to be used that way - see the documentation of the driver for more information. \n A pod can use both types of ephemeral volumes and persistent volumes at the same time." + type: object + properties: + readOnly: + description: Specifies a read-only configuration for the volume. Defaults to false (read/write). + type: boolean + volumeClaimTemplate: + description: "Will be used to create a stand-alone PVC to provision the volume. The pod in which this EphemeralVolumeSource is embedded will be the owner of the PVC, i.e. the PVC will be deleted together with the pod. The name of the PVC will be `-` where `` is the name from the `PodSpec.Volumes` array entry. Pod validation will reject the pod if the concatenated name is not valid for a PVC (for example, too long). \n An existing PVC with that name that is not owned by the pod will *not* be used for the pod to avoid using an unrelated volume by mistake. Starting the pod is then blocked until the unrelated PVC is removed. If such a pre-created PVC is meant to be used by the pod, the PVC has to updated with an owner reference to the pod once the pod exists. Normally this should not be necessary, but it may be useful when manually reconstructing a broken cluster. \n This field is read-only and no changes will be made by Kubernetes to the PVC after it has been created. \n Required, must not be nil." + type: object + required: + - spec + properties: + metadata: + description: May contain labels and annotations that will be copied into the PVC when creating it. No other fields are allowed and will be rejected during validation. + type: object + spec: + description: The specification for the PersistentVolumeClaim. The entire content is copied unchanged into the PVC that gets created from this template. The same fields as in a PersistentVolumeClaim are also valid here. + type: object + properties: + accessModes: + description: 'AccessModes contains the desired access modes the volume should have. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1' + type: array + items: + type: string + dataSource: + description: 'This field can be used to specify either: * An existing VolumeSnapshot object (snapshot.storage.k8s.io/VolumeSnapshot) * An existing PVC (PersistentVolumeClaim) * An existing custom resource that implements data population (Alpha) In order to use custom resource types that implement data population, the AnyVolumeDataSource feature gate must be enabled. If the provisioner or an external controller can support the specified data source, it will create a new volume based on the contents of the specified data source.' + type: object + required: + - kind + - name + properties: + apiGroup: + description: APIGroup is the group for the resource being referenced. If APIGroup is not specified, the specified Kind must be in the core API group. For any other third-party types, APIGroup is required. + type: string + kind: + description: Kind is the type of resource being referenced + type: string + name: + description: Name is the name of resource being referenced + type: string + resources: + description: 'Resources represents the minimum resources the volume should have. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#resources' + type: object + properties: + limits: + description: 'Limits describes the maximum amount of compute resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' + type: object + additionalProperties: + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + requests: + description: 'Requests describes the minimum amount of compute resources required. If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, otherwise to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' + type: object + additionalProperties: + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + selector: + description: A label query over volumes to consider for binding. + type: object + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + type: array + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + type: array + items: + type: string + matchLabels: + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + additionalProperties: + type: string + storageClassName: + description: 'Name of the StorageClass required by the claim. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#class-1' + type: string + volumeMode: + description: volumeMode defines what type of volume is required by the claim. Value of Filesystem is implied when not included in claim spec. + type: string + volumeName: + description: VolumeName is the binding reference to the PersistentVolume backing this claim. + type: string + fc: + description: FC represents a Fibre Channel resource that is attached to a kubelet's host machine and then exposed to the pod. + type: object + properties: + fsType: + description: 'Filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. TODO: how do we prevent errors in the filesystem from compromising the machine' + type: string + lun: + description: 'Optional: FC target lun number' + type: integer + format: int32 + readOnly: + description: 'Optional: Defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts.' + type: boolean + targetWWNs: + description: 'Optional: FC target worldwide names (WWNs)' + type: array + items: + type: string + wwids: + description: 'Optional: FC volume world wide identifiers (wwids) Either wwids or combination of targetWWNs and lun must be set, but not both simultaneously.' + type: array + items: + type: string + flexVolume: + description: FlexVolume represents a generic volume resource that is provisioned/attached using an exec based plugin. + type: object + required: + - driver + properties: + driver: + description: Driver is the name of the driver to use for this volume. + type: string + fsType: + description: Filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. "ext4", "xfs", "ntfs". The default filesystem depends on FlexVolume script. + type: string + options: + description: 'Optional: Extra command options if any.' + type: object + additionalProperties: + type: string + readOnly: + description: 'Optional: Defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts.' + type: boolean + secretRef: + description: 'Optional: SecretRef is reference to the secret object containing sensitive information to pass to the plugin scripts. This may be empty if no secret object is specified. If the secret object contains more than one secret, all secrets are passed to the plugin scripts.' + type: object + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + flocker: + description: Flocker represents a Flocker volume attached to a kubelet's host machine. This depends on the Flocker control service being running + type: object + properties: + datasetName: + description: Name of the dataset stored as metadata -> name on the dataset for Flocker should be considered as deprecated + type: string + datasetUUID: + description: UUID of the dataset. This is unique identifier of a Flocker dataset + type: string + gcePersistentDisk: + description: 'GCEPersistentDisk represents a GCE Disk resource that is attached to a kubelet''s host machine and then exposed to the pod. More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk' + type: object + required: + - pdName + properties: + fsType: + description: 'Filesystem type of the volume that you want to mount. Tip: Ensure that the filesystem type is supported by the host operating system. Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk TODO: how do we prevent errors in the filesystem from compromising the machine' + type: string + partition: + description: 'The partition in the volume that you want to mount. If omitted, the default is to mount by volume name. Examples: For volume /dev/sda1, you specify the partition as "1". Similarly, the volume partition for /dev/sda is "0" (or you can leave the property empty). More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk' + type: integer + format: int32 + pdName: + description: 'Unique name of the PD resource in GCE. Used to identify the disk in GCE. More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk' + type: string + readOnly: + description: 'ReadOnly here will force the ReadOnly setting in VolumeMounts. Defaults to false. More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk' + type: boolean + gitRepo: + description: 'GitRepo represents a git repository at a particular revision. DEPRECATED: GitRepo is deprecated. To provision a container with a git repo, mount an EmptyDir into an InitContainer that clones the repo using git, then mount the EmptyDir into the Pod''s container.' + type: object + required: + - repository + properties: + directory: + description: Target directory name. Must not contain or start with '..'. If '.' is supplied, the volume directory will be the git repository. Otherwise, if specified, the volume will contain the git repository in the subdirectory with the given name. + type: string + repository: + description: Repository URL + type: string + revision: + description: Commit hash for the specified revision. + type: string + glusterfs: + description: 'Glusterfs represents a Glusterfs mount on the host that shares a pod''s lifetime. More info: https://examples.k8s.io/volumes/glusterfs/README.md' + type: object + required: + - endpoints + - path + properties: + endpoints: + description: 'EndpointsName is the endpoint name that details Glusterfs topology. More info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod' + type: string + path: + description: 'Path is the Glusterfs volume path. More info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod' + type: string + readOnly: + description: 'ReadOnly here will force the Glusterfs volume to be mounted with read-only permissions. Defaults to false. More info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod' + type: boolean + hostPath: + description: 'HostPath represents a pre-existing file or directory on the host machine that is directly exposed to the container. This is generally used for system agents or other privileged things that are allowed to see the host machine. Most containers will NOT need this. More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath --- TODO(jonesdl) We need to restrict who can use host directory mounts and who can/can not mount host directories as read/write.' + type: object + required: + - path + properties: + path: + description: 'Path of the directory on the host. If the path is a symlink, it will follow the link to the real path. More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath' + type: string + type: + description: 'Type for HostPath Volume Defaults to "" More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath' + type: string + iscsi: + description: 'ISCSI represents an ISCSI Disk resource that is attached to a kubelet''s host machine and then exposed to the pod. More info: https://examples.k8s.io/volumes/iscsi/README.md' + type: object + required: + - iqn + - lun + - targetPortal + properties: + chapAuthDiscovery: + description: whether support iSCSI Discovery CHAP authentication + type: boolean + chapAuthSession: + description: whether support iSCSI Session CHAP authentication + type: boolean + fsType: + description: 'Filesystem type of the volume that you want to mount. Tip: Ensure that the filesystem type is supported by the host operating system. Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. More info: https://kubernetes.io/docs/concepts/storage/volumes#iscsi TODO: how do we prevent errors in the filesystem from compromising the machine' + type: string + initiatorName: + description: Custom iSCSI Initiator Name. If initiatorName is specified with iscsiInterface simultaneously, new iSCSI interface : will be created for the connection. + type: string + iqn: + description: Target iSCSI Qualified Name. + type: string + iscsiInterface: + description: iSCSI Interface Name that uses an iSCSI transport. Defaults to 'default' (tcp). + type: string + lun: + description: iSCSI Target Lun number. + type: integer + format: int32 + portals: + description: iSCSI Target Portal List. The portal is either an IP or ip_addr:port if the port is other than default (typically TCP ports 860 and 3260). + type: array + items: + type: string + readOnly: + description: ReadOnly here will force the ReadOnly setting in VolumeMounts. Defaults to false. + type: boolean + secretRef: + description: CHAP Secret for iSCSI target and initiator authentication + type: object + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + targetPortal: + description: iSCSI Target Portal. The Portal is either an IP or ip_addr:port if the port is other than default (typically TCP ports 860 and 3260). + type: string + name: + description: 'Volume''s name. Must be a DNS_LABEL and unique within the pod. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + nfs: + description: 'NFS represents an NFS mount on the host that shares a pod''s lifetime More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs' + type: object + required: + - path + - server + properties: + path: + description: 'Path that is exported by the NFS server. More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs' + type: string + readOnly: + description: 'ReadOnly here will force the NFS export to be mounted with read-only permissions. Defaults to false. More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs' + type: boolean + server: + description: 'Server is the hostname or IP address of the NFS server. More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs' + type: string + persistentVolumeClaim: + description: 'PersistentVolumeClaimVolumeSource represents a reference to a PersistentVolumeClaim in the same namespace. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims' + type: object + required: + - claimName + properties: + claimName: + description: 'ClaimName is the name of a PersistentVolumeClaim in the same namespace as the pod using this volume. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims' + type: string + readOnly: + description: Will force the ReadOnly setting in VolumeMounts. Default false. + type: boolean + photonPersistentDisk: + description: PhotonPersistentDisk represents a PhotonController persistent disk attached and mounted on kubelets host machine + type: object + required: + - pdID + properties: + fsType: + description: Filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + type: string + pdID: + description: ID that identifies Photon Controller persistent disk + type: string + portworxVolume: + description: PortworxVolume represents a portworx volume attached and mounted on kubelets host machine + type: object + required: + - volumeID + properties: + fsType: + description: FSType represents the filesystem type to mount Must be a filesystem type supported by the host operating system. Ex. "ext4", "xfs". Implicitly inferred to be "ext4" if unspecified. + type: string + readOnly: + description: Defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts. + type: boolean + volumeID: + description: VolumeID uniquely identifies a Portworx volume + type: string + projected: + description: Items for all in one resources secrets, configmaps, and downward API + type: object + properties: + defaultMode: + description: Mode bits used to set permissions on created files by default. Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. Directories within the path are not affected by this setting. This might be in conflict with other options that affect the file mode, like fsGroup, and the result can be other mode bits set. + type: integer + format: int32 + sources: + description: list of volume projections + type: array + items: + description: Projection that may be projected along with other supported volume types + type: object + properties: + configMap: + description: information about the configMap data to project + type: object + properties: + items: + description: If unspecified, each key-value pair in the Data field of the referenced ConfigMap will be projected into the volume as a file whose name is the key and content is the value. If specified, the listed keys will be projected into the specified paths, and unlisted keys will not be present. If a key is specified which is not present in the ConfigMap, the volume setup will error unless it is marked optional. Paths must be relative and may not contain the '..' path or start with '..'. + type: array + items: + description: Maps a string key to a path within a volume. + type: object + required: + - key + - path + properties: + key: + description: The key to project. + type: string + mode: + description: 'Optional: mode bits used to set permissions on this file. Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. If not specified, the volume defaultMode will be used. This might be in conflict with other options that affect the file mode, like fsGroup, and the result can be other mode bits set.' + type: integer + format: int32 + path: + description: The relative path of the file to map the key to. May not be an absolute path. May not contain the path element '..'. May not start with the string '..'. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + optional: + description: Specify whether the ConfigMap or its keys must be defined + type: boolean + downwardAPI: + description: information about the downwardAPI data to project + type: object + properties: + items: + description: Items is a list of DownwardAPIVolume file + type: array + items: + description: DownwardAPIVolumeFile represents information to create the file containing the pod field + type: object + required: + - path + properties: + fieldRef: + description: 'Required: Selects a field of the pod: only annotations, labels, name and namespace are supported.' + type: object + required: + - fieldPath + properties: + apiVersion: + description: Version of the schema the FieldPath is written in terms of, defaults to "v1". + type: string + fieldPath: + description: Path of the field to select in the specified API version. + type: string + mode: + description: 'Optional: mode bits used to set permissions on this file, must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. If not specified, the volume defaultMode will be used. This might be in conflict with other options that affect the file mode, like fsGroup, and the result can be other mode bits set.' + type: integer + format: int32 + path: + description: 'Required: Path is the relative path name of the file to be created. Must not be absolute or contain the ''..'' path. Must be utf-8 encoded. The first item of the relative path must not start with ''..''' + type: string + resourceFieldRef: + description: 'Selects a resource of the container: only resources limits and requests (limits.cpu, limits.memory, requests.cpu and requests.memory) are currently supported.' + type: object + required: + - resource + properties: + containerName: + description: 'Container name: required for volumes, optional for env vars' + type: string + divisor: + description: Specifies the output format of the exposed resources, defaults to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource to select' + type: string + secret: + description: information about the secret data to project + type: object + properties: + items: + description: If unspecified, each key-value pair in the Data field of the referenced Secret will be projected into the volume as a file whose name is the key and content is the value. If specified, the listed keys will be projected into the specified paths, and unlisted keys will not be present. If a key is specified which is not present in the Secret, the volume setup will error unless it is marked optional. Paths must be relative and may not contain the '..' path or start with '..'. + type: array + items: + description: Maps a string key to a path within a volume. + type: object + required: + - key + - path + properties: + key: + description: The key to project. + type: string + mode: + description: 'Optional: mode bits used to set permissions on this file. Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. If not specified, the volume defaultMode will be used. This might be in conflict with other options that affect the file mode, like fsGroup, and the result can be other mode bits set.' + type: integer + format: int32 + path: + description: The relative path of the file to map the key to. May not be an absolute path. May not contain the path element '..'. May not start with the string '..'. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + optional: + description: Specify whether the Secret or its key must be defined + type: boolean + serviceAccountToken: + description: information about the serviceAccountToken data to project + type: object + required: + - path + properties: + audience: + description: Audience is the intended audience of the token. A recipient of a token must identify itself with an identifier specified in the audience of the token, and otherwise should reject the token. The audience defaults to the identifier of the apiserver. + type: string + expirationSeconds: + description: ExpirationSeconds is the requested duration of validity of the service account token. As the token approaches expiration, the kubelet volume plugin will proactively rotate the service account token. The kubelet will start trying to rotate the token if the token is older than 80 percent of its time to live or if the token is older than 24 hours.Defaults to 1 hour and must be at least 10 minutes. + type: integer + format: int64 + path: + description: Path is the path relative to the mount point of the file to project the token into. + type: string + quobyte: + description: Quobyte represents a Quobyte mount on the host that shares a pod's lifetime + type: object + required: + - registry + - volume + properties: + group: + description: Group to map volume access to Default is no group + type: string + readOnly: + description: ReadOnly here will force the Quobyte volume to be mounted with read-only permissions. Defaults to false. + type: boolean + registry: + description: Registry represents a single or multiple Quobyte Registry services specified as a string as host:port pair (multiple entries are separated with commas) which acts as the central registry for volumes + type: string + tenant: + description: Tenant owning the given Quobyte volume in the Backend Used with dynamically provisioned Quobyte volumes, value is set by the plugin + type: string + user: + description: User to map volume access to Defaults to serivceaccount user + type: string + volume: + description: Volume is a string that references an already created Quobyte volume by name. + type: string + rbd: + description: 'RBD represents a Rados Block Device mount on the host that shares a pod''s lifetime. More info: https://examples.k8s.io/volumes/rbd/README.md' + type: object + required: + - image + - monitors + properties: + fsType: + description: 'Filesystem type of the volume that you want to mount. Tip: Ensure that the filesystem type is supported by the host operating system. Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. More info: https://kubernetes.io/docs/concepts/storage/volumes#rbd TODO: how do we prevent errors in the filesystem from compromising the machine' + type: string + image: + description: 'The rados image name. More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it' + type: string + keyring: + description: 'Keyring is the path to key ring for RBDUser. Default is /etc/ceph/keyring. More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it' + type: string + monitors: + description: 'A collection of Ceph monitors. More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it' + type: array + items: + type: string + pool: + description: 'The rados pool name. Default is rbd. More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it' + type: string + readOnly: + description: 'ReadOnly here will force the ReadOnly setting in VolumeMounts. Defaults to false. More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it' + type: boolean + secretRef: + description: 'SecretRef is name of the authentication secret for RBDUser. If provided overrides keyring. Default is nil. More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it' + type: object + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + user: + description: 'The rados user name. Default is admin. More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it' + type: string + scaleIO: + description: ScaleIO represents a ScaleIO persistent volume attached and mounted on Kubernetes nodes. + type: object + required: + - gateway + - secretRef + - system + properties: + fsType: + description: Filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. "ext4", "xfs", "ntfs". Default is "xfs". + type: string + gateway: + description: The host address of the ScaleIO API Gateway. + type: string + protectionDomain: + description: The name of the ScaleIO Protection Domain for the configured storage. + type: string + readOnly: + description: Defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts. + type: boolean + secretRef: + description: SecretRef references to the secret for ScaleIO user and other sensitive information. If this is not provided, Login operation will fail. + type: object + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + sslEnabled: + description: Flag to enable/disable SSL communication with Gateway, default false + type: boolean + storageMode: + description: Indicates whether the storage for a volume should be ThickProvisioned or ThinProvisioned. Default is ThinProvisioned. + type: string + storagePool: + description: The ScaleIO Storage Pool associated with the protection domain. + type: string + system: + description: The name of the storage system as configured in ScaleIO. + type: string + volumeName: + description: The name of a volume already created in the ScaleIO system that is associated with this volume source. + type: string + secret: + description: 'Secret represents a secret that should populate this volume. More info: https://kubernetes.io/docs/concepts/storage/volumes#secret' + type: object + properties: + defaultMode: + description: 'Optional: mode bits used to set permissions on created files by default. Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. Defaults to 0644. Directories within the path are not affected by this setting. This might be in conflict with other options that affect the file mode, like fsGroup, and the result can be other mode bits set.' + type: integer + format: int32 + items: + description: If unspecified, each key-value pair in the Data field of the referenced Secret will be projected into the volume as a file whose name is the key and content is the value. If specified, the listed keys will be projected into the specified paths, and unlisted keys will not be present. If a key is specified which is not present in the Secret, the volume setup will error unless it is marked optional. Paths must be relative and may not contain the '..' path or start with '..'. + type: array + items: + description: Maps a string key to a path within a volume. + type: object + required: + - key + - path + properties: + key: + description: The key to project. + type: string + mode: + description: 'Optional: mode bits used to set permissions on this file. Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. If not specified, the volume defaultMode will be used. This might be in conflict with other options that affect the file mode, like fsGroup, and the result can be other mode bits set.' + type: integer + format: int32 + path: + description: The relative path of the file to map the key to. May not be an absolute path. May not contain the path element '..'. May not start with the string '..'. + type: string + optional: + description: Specify whether the Secret or its keys must be defined + type: boolean + secretName: + description: 'Name of the secret in the pod''s namespace to use. More info: https://kubernetes.io/docs/concepts/storage/volumes#secret' + type: string + storageos: + description: StorageOS represents a StorageOS volume attached and mounted on Kubernetes nodes. + type: object + properties: + fsType: + description: Filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + type: string + readOnly: + description: Defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts. + type: boolean + secretRef: + description: SecretRef specifies the secret to use for obtaining the StorageOS API credentials. If not specified, default values will be attempted. + type: object + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + volumeName: + description: VolumeName is the human-readable name of the StorageOS volume. Volume names are only unique within a namespace. + type: string + volumeNamespace: + description: VolumeNamespace specifies the scope of the volume within StorageOS. If no namespace is specified then the Pod's namespace will be used. This allows the Kubernetes name scoping to be mirrored within StorageOS for tighter integration. Set VolumeName to any name to override the default behaviour. Set to "default" if you are not using namespaces within StorageOS. Namespaces that do not pre-exist within StorageOS will be created. + type: string + vsphereVolume: + description: VsphereVolume represents a vSphere volume attached and mounted on kubelets host machine + type: object + required: + - volumePath + properties: + fsType: + description: Filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + type: string + storagePolicyID: + description: Storage Policy Based Management (SPBM) profile ID associated with the StoragePolicyName. + type: string + storagePolicyName: + description: Storage Policy Based Management (SPBM) profile name. + type: string + volumePath: + description: Path that identifies vSphere volume vmdk + type: string + permissions: + type: array + items: + description: StrategyDeploymentPermissions describe the rbac rules and service account needed by the install strategy + type: object + required: + - rules + - serviceAccountName + properties: + rules: + type: array + items: + description: PolicyRule holds information that describes a policy rule, but does not contain information about who the rule applies to or which namespace the rule applies to. + type: object + required: + - verbs + properties: + apiGroups: + description: APIGroups is the name of the APIGroup that contains the resources. If multiple API groups are specified, any action requested against one of the enumerated resources in any API group will be allowed. + type: array + items: + type: string + nonResourceURLs: + description: NonResourceURLs is a set of partial urls that a user should have access to. *s are allowed, but only as the full, final step in the path Since non-resource URLs are not namespaced, this field is only applicable for ClusterRoles referenced from a ClusterRoleBinding. Rules can either apply to API resources (such as "pods" or "secrets") or non-resource URL paths (such as "/api"), but not both. + type: array + items: + type: string + resourceNames: + description: ResourceNames is an optional white list of names that the rule applies to. An empty set means that everything is allowed. + type: array + items: + type: string + resources: + description: Resources is a list of resources this rule applies to. ResourceAll represents all resources. + type: array + items: + type: string + verbs: + description: Verbs is a list of Verbs that apply to ALL the ResourceKinds and AttributeRestrictions contained in this rule. VerbAll represents all kinds. + type: array + items: + type: string + serviceAccountName: + type: string + strategy: + type: string + installModes: + description: InstallModes specify supported installation types + type: array + items: + description: InstallMode associates an InstallModeType with a flag representing if the CSV supports it + type: object + required: + - supported + - type + properties: + supported: + type: boolean + type: + description: InstallModeType is a supported type of install mode for CSV installation + type: string + keywords: + type: array + items: + type: string + labels: + description: Map of string keys and values that can be used to organize and categorize (scope and select) objects. + type: object + additionalProperties: + type: string + links: + type: array + items: + type: object + properties: + name: + type: string + url: + type: string + maintainers: + type: array + items: + type: object + properties: + email: + type: string + name: + type: string + maturity: + type: string + minKubeVersion: + type: string + nativeAPIs: + type: array + items: + description: GroupVersionKind unambiguously identifies a kind. It doesn't anonymously include GroupVersion to avoid automatic coersion. It doesn't use a GroupVersion to avoid custom marshalling + type: object + required: + - group + - kind + - version + properties: + group: + type: string + kind: + type: string + version: + type: string + provider: + type: object + properties: + name: + type: string + url: + type: string + relatedImages: + description: List any related images, or other container images that your Operator might require to perform their functions. This list should also include operand images as well. All image references should be specified by digest (SHA) and not by tag. This field is only used during catalog creation and plays no part in cluster runtime. + type: array + items: + type: object + required: + - image + - name + properties: + image: + type: string + name: + type: string + replaces: + description: The name of a CSV this one replaces. Should match the `metadata.Name` field of the old CSV. + type: string + selector: + description: Label selector for related resources. + type: object + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + type: array + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + type: array + items: + type: string + matchLabels: + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + additionalProperties: + type: string + skips: + description: The name(s) of one or more CSV(s) that should be skipped in the upgrade graph. Should match the `metadata.Name` field of the CSV that should be skipped. This field is only used during catalog creation and plays no part in cluster runtime. + type: array + items: + type: string + version: + description: OperatorVersion is a wrapper around semver.Version which supports correct marshaling to YAML and JSON. + type: string + webhookdefinitions: + type: array + items: + description: WebhookDescription provides details to OLM about required webhooks + type: object + required: + - admissionReviewVersions + - generateName + - sideEffects + - type + properties: + admissionReviewVersions: + type: array + items: + type: string + containerPort: + type: integer + format: int32 + default: 443 + maximum: 65535 + minimum: 1 + conversionCRDs: + type: array + items: + type: string + deploymentName: + type: string + failurePolicy: + type: string + generateName: + type: string + matchPolicy: + description: MatchPolicyType specifies the type of match policy + type: string + objectSelector: + description: A label selector is a label query over a set of resources. The result of matchLabels and matchExpressions are ANDed. An empty label selector matches all objects. A null label selector matches no objects. + type: object + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + type: array + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + type: array + items: + type: string + matchLabels: + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + additionalProperties: + type: string + reinvocationPolicy: + description: ReinvocationPolicyType specifies what type of policy the admission hook uses. + type: string + rules: + type: array + items: + description: RuleWithOperations is a tuple of Operations and Resources. It is recommended to make sure that all the tuple expansions are valid. + type: object + properties: + apiGroups: + description: APIGroups is the API groups the resources belong to. '*' is all groups. If '*' is present, the length of the slice must be one. Required. + type: array + items: + type: string + apiVersions: + description: APIVersions is the API versions the resources belong to. '*' is all versions. If '*' is present, the length of the slice must be one. Required. + type: array + items: + type: string + operations: + description: Operations is the operations the admission hook cares about - CREATE, UPDATE, DELETE, CONNECT or * for all of those operations and any future admission operations that are added. If '*' is present, the length of the slice must be one. Required. + type: array + items: + type: string + resources: + description: "Resources is a list of resources this rule applies to. \n For example: 'pods' means pods. 'pods/log' means the log subresource of pods. '*' means all resources, but not subresources. 'pods/*' means all subresources of pods. '*/scale' means all scale subresources. '*/*' means all resources and their subresources. \n If wildcard is present, the validation rule will ensure resources do not overlap with each other. \n Depending on the enclosing object, subresources might not be allowed. Required." + type: array + items: + type: string + scope: + description: scope specifies the scope of this rule. Valid values are "Cluster", "Namespaced", and "*" "Cluster" means that only cluster-scoped resources will match this rule. Namespace API objects are cluster-scoped. "Namespaced" means that only namespaced resources will match this rule. "*" means that there are no scope restrictions. Subresources match the scope of their parent resource. Default is "*". + type: string + sideEffects: + type: string + targetPort: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + timeoutSeconds: + type: integer + format: int32 + type: + description: WebhookAdmissionType is the type of admission webhooks supported by OLM + type: string + enum: + - ValidatingAdmissionWebhook + - MutatingAdmissionWebhook + - ConversionWebhook + webhookPath: + type: string + status: + description: ClusterServiceVersionStatus represents information about the status of a CSV. Status may trail the actual state of a system. + type: object + properties: + certsLastUpdated: + description: Last time the owned APIService certs were updated + type: string + format: date-time + certsRotateAt: + description: Time the owned APIService certs will rotate next + type: string + format: date-time + cleanup: + description: CleanupStatus represents information about the status of cleanup while a CSV is pending deletion + type: object + properties: + pendingDeletion: + description: PendingDeletion is the list of custom resource objects that are pending deletion and blocked on finalizers. This indicates the progress of cleanup that is blocking CSV deletion or operator uninstall. + type: array + items: + description: ResourceList represents a list of resources which are of the same Group/Kind + type: object + required: + - group + - instances + - kind + properties: + group: + type: string + instances: + type: array + items: + type: object + required: + - name + properties: + name: + type: string + namespace: + description: Namespace can be empty for cluster-scoped resources + type: string + kind: + type: string + conditions: + description: List of conditions, a history of state transitions + type: array + items: + description: Conditions appear in the status as a record of state transitions on the ClusterServiceVersion + type: object + properties: + lastTransitionTime: + description: Last time the status transitioned from one status to another. + type: string + format: date-time + lastUpdateTime: + description: Last time we updated the status + type: string + format: date-time + message: + description: A human readable message indicating details about why the ClusterServiceVersion is in this condition. + type: string + phase: + description: Condition of the ClusterServiceVersion + type: string + reason: + description: A brief CamelCase message indicating details about why the ClusterServiceVersion is in this state. e.g. 'RequirementsNotMet' + type: string + lastTransitionTime: + description: Last time the status transitioned from one status to another. + type: string + format: date-time + lastUpdateTime: + description: Last time we updated the status + type: string + format: date-time + message: + description: A human readable message indicating details about why the ClusterServiceVersion is in this condition. + type: string + phase: + description: Current condition of the ClusterServiceVersion + type: string + reason: + description: A brief CamelCase message indicating details about why the ClusterServiceVersion is in this state. e.g. 'RequirementsNotMet' + type: string + requirementStatus: + description: The status of each requirement for this CSV + type: array + items: + type: object + required: + - group + - kind + - message + - name + - status + - version + properties: + dependents: + type: array + items: + description: DependentStatus is the status for a dependent requirement (to prevent infinite nesting) + type: object + required: + - group + - kind + - status + - version + properties: + group: + type: string + kind: + type: string + message: + type: string + status: + description: StatusReason is a camelcased reason for the status of a RequirementStatus or DependentStatus + type: string + uuid: + type: string + version: + type: string + group: + type: string + kind: + type: string + message: + type: string + name: + type: string + status: + description: StatusReason is a camelcased reason for the status of a RequirementStatus or DependentStatus + type: string + uuid: + type: string + version: + type: string + served: true + storage: true + subresources: + status: {} + diff --git a/staging/operator-lifecycle-manager/deploy/upstream/manifests/0.18.3/0000_50_olm_00-installplans.crd.yaml b/staging/operator-lifecycle-manager/deploy/upstream/manifests/0.18.3/0000_50_olm_00-installplans.crd.yaml new file mode 100644 index 0000000000..6536fe1b79 --- /dev/null +++ b/staging/operator-lifecycle-manager/deploy/upstream/manifests/0.18.3/0000_50_olm_00-installplans.crd.yaml @@ -0,0 +1,265 @@ +--- +# Source: olm/crds/0000_50_olm_00-installplans.crd.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.4.1 + creationTimestamp: null + name: installplans.operators.coreos.com +spec: + group: operators.coreos.com + names: + categories: + - olm + kind: InstallPlan + listKind: InstallPlanList + plural: installplans + shortNames: + - ip + singular: installplan + scope: Namespaced + versions: + - additionalPrinterColumns: + - description: The first CSV in the list of clusterServiceVersionNames + jsonPath: .spec.clusterServiceVersionNames[0] + name: CSV + type: string + - description: The approval mode + jsonPath: .spec.approval + name: Approval + type: string + - jsonPath: .spec.approved + name: Approved + type: boolean + name: v1alpha1 + schema: + openAPIV3Schema: + description: InstallPlan defines the installation of a set of operators. + type: object + required: + - metadata + - spec + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More 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. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: InstallPlanSpec defines a set of Application resources to be installed + type: object + required: + - approval + - approved + - clusterServiceVersionNames + properties: + approval: + description: Approval is the user approval policy for an InstallPlan. It must be one of "Automatic" or "Manual". + type: string + approved: + type: boolean + clusterServiceVersionNames: + type: array + items: + type: string + generation: + type: integer + source: + type: string + sourceNamespace: + type: string + status: + description: "InstallPlanStatus represents the information about the status of steps required to complete installation. \n Status may trail the actual state of a system." + type: object + required: + - catalogSources + - phase + properties: + attenuatedServiceAccountRef: + description: AttenuatedServiceAccountRef references the service account that is used to do scoped operator install. + type: object + properties: + apiVersion: + description: API version of the referent. + type: string + fieldPath: + description: 'If referring to a piece of an object instead of an entire object, this string should contain a valid JSON/Go field access statement, such as desiredState.manifest.containers[2]. For example, if the object reference is to a container within a pod, this would take on a value like: "spec.containers{name}" (where "name" refers to the name of the container that triggered the event) or if no container name is specified "spec.containers[2]" (container with index 2 in this pod). This syntax is chosen only to have some well-defined way of referencing a part of an object. TODO: this design is not final and this field is subject to change in the future.' + type: string + kind: + description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + namespace: + description: 'Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/' + type: string + resourceVersion: + description: 'Specific resourceVersion to which this reference is made, if any. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency' + type: string + uid: + description: 'UID of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids' + type: string + bundleLookups: + description: BundleLookups is the set of in-progress requests to pull and unpackage bundle content to the cluster. + type: array + items: + description: BundleLookup is a request to pull and unpackage the content of a bundle to the cluster. + type: object + required: + - catalogSourceRef + - identifier + - path + - replaces + properties: + catalogSourceRef: + description: CatalogSourceRef is a reference to the CatalogSource the bundle path was resolved from. + type: object + properties: + apiVersion: + description: API version of the referent. + type: string + fieldPath: + description: 'If referring to a piece of an object instead of an entire object, this string should contain a valid JSON/Go field access statement, such as desiredState.manifest.containers[2]. For example, if the object reference is to a container within a pod, this would take on a value like: "spec.containers{name}" (where "name" refers to the name of the container that triggered the event) or if no container name is specified "spec.containers[2]" (container with index 2 in this pod). This syntax is chosen only to have some well-defined way of referencing a part of an object. TODO: this design is not final and this field is subject to change in the future.' + type: string + kind: + description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + namespace: + description: 'Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/' + type: string + resourceVersion: + description: 'Specific resourceVersion to which this reference is made, if any. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency' + type: string + uid: + description: 'UID of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids' + type: string + conditions: + description: Conditions represents the overall state of a BundleLookup. + type: array + items: + type: object + required: + - status + - type + properties: + lastTransitionTime: + description: Last time the condition transitioned from one status to another. + type: string + format: date-time + lastUpdateTime: + description: Last time the condition was probed. + type: string + format: date-time + message: + description: A human readable message indicating details about the transition. + type: string + reason: + description: The reason for the condition's last transition. + type: string + status: + description: Status of the condition, one of True, False, Unknown. + type: string + type: + description: Type of condition. + type: string + identifier: + description: Identifier is the catalog-unique name of the operator (the name of the CSV for bundles that contain CSVs) + type: string + path: + description: Path refers to the location of a bundle to pull. It's typically an image reference. + type: string + properties: + description: The effective properties of the unpacked bundle. + type: string + replaces: + description: Replaces is the name of the bundle to replace with the one found at Path. + type: string + catalogSources: + type: array + items: + type: string + conditions: + type: array + items: + description: InstallPlanCondition represents the overall status of the execution of an InstallPlan. + type: object + properties: + lastTransitionTime: + type: string + format: date-time + lastUpdateTime: + type: string + format: date-time + message: + type: string + reason: + description: ConditionReason is a camelcased reason for the state transition. + type: string + status: + type: string + type: + description: InstallPlanConditionType describes the state of an InstallPlan at a certain point as a whole. + type: string + message: + description: Message is a human-readable message containing detailed information that may be important to understanding why the plan has its current status. + type: string + phase: + description: InstallPlanPhase is the current status of a InstallPlan as a whole. + type: string + plan: + type: array + items: + description: Step represents the status of an individual step in an InstallPlan. + type: object + required: + - resolving + - resource + - status + properties: + resolving: + type: string + resource: + description: StepResource represents the status of a resource to be tracked by an InstallPlan. + type: object + required: + - group + - kind + - name + - sourceName + - sourceNamespace + - version + properties: + group: + type: string + kind: + type: string + manifest: + type: string + name: + type: string + sourceName: + type: string + sourceNamespace: + type: string + version: + type: string + status: + description: StepStatus is the current status of a particular resource an in InstallPlan + type: string + startTime: + description: StartTime is the time when the controller began applying the resources listed in the plan to the cluster. + type: string + format: date-time + served: true + storage: true + subresources: + status: {} + diff --git a/staging/operator-lifecycle-manager/deploy/upstream/manifests/0.18.3/0000_50_olm_00-namespace.yaml b/staging/operator-lifecycle-manager/deploy/upstream/manifests/0.18.3/0000_50_olm_00-namespace.yaml new file mode 100644 index 0000000000..13917074d8 --- /dev/null +++ b/staging/operator-lifecycle-manager/deploy/upstream/manifests/0.18.3/0000_50_olm_00-namespace.yaml @@ -0,0 +1,12 @@ +--- +# Source: olm/templates/0000_50_olm_00-namespace.yaml +apiVersion: v1 +kind: Namespace +metadata: + name: olm +--- +# Source: olm/templates/0000_50_olm_00-namespace.yaml +apiVersion: v1 +kind: Namespace +metadata: + name: operators diff --git a/staging/operator-lifecycle-manager/deploy/upstream/manifests/0.18.3/0000_50_olm_00-operatorconditions.crd.yaml b/staging/operator-lifecycle-manager/deploy/upstream/manifests/0.18.3/0000_50_olm_00-operatorconditions.crd.yaml new file mode 100644 index 0000000000..334b0c2d33 --- /dev/null +++ b/staging/operator-lifecycle-manager/deploy/upstream/manifests/0.18.3/0000_50_olm_00-operatorconditions.crd.yaml @@ -0,0 +1,308 @@ +--- +# Source: olm/crds/0000_50_olm_00-operatorconditions.crd.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.4.1 + creationTimestamp: null + name: operatorconditions.operators.coreos.com +spec: + group: operators.coreos.com + names: + categories: + - olm + kind: OperatorCondition + listKind: OperatorConditionList + plural: operatorconditions + shortNames: + - condition + singular: operatorcondition + scope: Namespaced + versions: + - name: v1 + schema: + openAPIV3Schema: + description: OperatorCondition is a Custom Resource of type `OperatorCondition` which is used to convey information to OLM about the state of an operator. + type: object + required: + - metadata + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More 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. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: OperatorConditionSpec allows a cluster admin to convey information about the state of an operator to OLM, potentially overriding state reported by the operator. + type: object + properties: + deployments: + type: array + items: + type: string + overrides: + type: array + items: + description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, type FooStatus struct{ // Represents the observations of a foo's current state. // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge // +listType=map // +listMapKey=type Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }" + type: object + required: + - message + - reason + - status + - type + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + type: string + format: date-time + message: + description: message is a human readable message indicating details about the transition. This may be an empty string. + type: string + maxLength: 32768 + observedGeneration: + description: observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance. + type: integer + format: int64 + minimum: 0 + reason: + description: reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty. + type: string + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + status: + description: status of the condition, one of True, False, Unknown. + type: string + enum: + - "True" + - "False" + - Unknown + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. --- Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful (see .node.status.conditions), the ability to deconflict is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + type: string + 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])$ + serviceAccounts: + type: array + items: + type: string + status: + description: OperatorConditionStatus allows an operator to convey information its state to OLM. The status may trail the actual state of a system. + type: object + properties: + conditions: + type: array + items: + description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, type FooStatus struct{ // Represents the observations of a foo's current state. // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge // +listType=map // +listMapKey=type Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }" + type: object + required: + - lastTransitionTime + - message + - reason + - status + - type + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + type: string + format: date-time + message: + description: message is a human readable message indicating details about the transition. This may be an empty string. + type: string + maxLength: 32768 + observedGeneration: + description: observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance. + type: integer + format: int64 + minimum: 0 + reason: + description: reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty. + type: string + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + status: + description: status of the condition, one of True, False, Unknown. + type: string + enum: + - "True" + - "False" + - Unknown + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. --- Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful (see .node.status.conditions), the ability to deconflict is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + type: string + 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])$ + served: true + storage: false + subresources: + status: {} + - name: v2 + schema: + openAPIV3Schema: + description: OperatorCondition is a Custom Resource of type `OperatorCondition` which is used to convey information to OLM about the state of an operator. + type: object + required: + - metadata + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More 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. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: OperatorConditionSpec allows an operator to report state to OLM and provides cluster admin with the ability to manually override state reported by the operator. + type: object + properties: + conditions: + type: array + items: + description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, type FooStatus struct{ // Represents the observations of a foo's current state. // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge // +listType=map // +listMapKey=type Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }" + type: object + required: + - lastTransitionTime + - message + - reason + - status + - type + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + type: string + format: date-time + message: + description: message is a human readable message indicating details about the transition. This may be an empty string. + type: string + maxLength: 32768 + observedGeneration: + description: observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance. + type: integer + format: int64 + minimum: 0 + reason: + description: reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty. + type: string + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + status: + description: status of the condition, one of True, False, Unknown. + type: string + enum: + - "True" + - "False" + - Unknown + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. --- Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful (see .node.status.conditions), the ability to deconflict is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + type: string + 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])$ + deployments: + type: array + items: + type: string + overrides: + type: array + items: + description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, type FooStatus struct{ // Represents the observations of a foo's current state. // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge // +listType=map // +listMapKey=type Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }" + type: object + required: + - message + - reason + - status + - type + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + type: string + format: date-time + message: + description: message is a human readable message indicating details about the transition. This may be an empty string. + type: string + maxLength: 32768 + observedGeneration: + description: observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance. + type: integer + format: int64 + minimum: 0 + reason: + description: reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty. + type: string + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + status: + description: status of the condition, one of True, False, Unknown. + type: string + enum: + - "True" + - "False" + - Unknown + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. --- Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful (see .node.status.conditions), the ability to deconflict is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + type: string + 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])$ + serviceAccounts: + type: array + items: + type: string + status: + description: OperatorConditionStatus allows OLM to convey which conditions have been observed. + type: object + properties: + conditions: + type: array + items: + description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, type FooStatus struct{ // Represents the observations of a foo's current state. // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge // +listType=map // +listMapKey=type Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }" + type: object + required: + - lastTransitionTime + - message + - reason + - status + - type + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + type: string + format: date-time + message: + description: message is a human readable message indicating details about the transition. This may be an empty string. + type: string + maxLength: 32768 + observedGeneration: + description: observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance. + type: integer + format: int64 + minimum: 0 + reason: + description: reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty. + type: string + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + status: + description: status of the condition, one of True, False, Unknown. + type: string + enum: + - "True" + - "False" + - Unknown + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. --- Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful (see .node.status.conditions), the ability to deconflict is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + type: string + 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])$ + served: true + storage: true + subresources: + status: {} + diff --git a/staging/operator-lifecycle-manager/deploy/upstream/manifests/0.18.3/0000_50_olm_00-operatorgroups.crd.yaml b/staging/operator-lifecycle-manager/deploy/upstream/manifests/0.18.3/0000_50_olm_00-operatorgroups.crd.yaml new file mode 100644 index 0000000000..f05dca4089 --- /dev/null +++ b/staging/operator-lifecycle-manager/deploy/upstream/manifests/0.18.3/0000_50_olm_00-operatorgroups.crd.yaml @@ -0,0 +1,235 @@ +--- +# Source: olm/crds/0000_50_olm_00-operatorgroups.crd.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.4.1 + creationTimestamp: null + name: operatorgroups.operators.coreos.com +spec: + group: operators.coreos.com + names: + categories: + - olm + kind: OperatorGroup + listKind: OperatorGroupList + plural: operatorgroups + shortNames: + - og + singular: operatorgroup + scope: Namespaced + versions: + - name: v1 + schema: + openAPIV3Schema: + description: OperatorGroup is the unit of multitenancy for OLM managed operators. It constrains the installation of operators in its namespace to a specified set of target namespaces. + type: object + required: + - metadata + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More 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. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: OperatorGroupSpec is the spec for an OperatorGroup resource. + type: object + properties: + selector: + description: Selector selects the OperatorGroup's target namespaces. + type: object + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + type: array + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + type: array + items: + type: string + matchLabels: + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + additionalProperties: + type: string + serviceAccountName: + description: ServiceAccountName is the admin specified service account which will be used to deploy operator(s) in this operator group. + type: string + staticProvidedAPIs: + description: Static tells OLM not to update the OperatorGroup's providedAPIs annotation + type: boolean + targetNamespaces: + description: TargetNamespaces is an explicit set of namespaces to target. If it is set, Selector is ignored. + type: array + items: + type: string + x-kubernetes-list-type: set + status: + description: OperatorGroupStatus is the status for an OperatorGroupResource. + type: object + required: + - lastUpdated + properties: + lastUpdated: + description: LastUpdated is a timestamp of the last time the OperatorGroup's status was Updated. + type: string + format: date-time + namespaces: + description: Namespaces is the set of target namespaces for the OperatorGroup. + type: array + items: + type: string + x-kubernetes-list-type: set + serviceAccountRef: + description: ServiceAccountRef references the service account object specified. + type: object + properties: + apiVersion: + description: API version of the referent. + type: string + fieldPath: + description: 'If referring to a piece of an object instead of an entire object, this string should contain a valid JSON/Go field access statement, such as desiredState.manifest.containers[2]. For example, if the object reference is to a container within a pod, this would take on a value like: "spec.containers{name}" (where "name" refers to the name of the container that triggered the event) or if no container name is specified "spec.containers[2]" (container with index 2 in this pod). This syntax is chosen only to have some well-defined way of referencing a part of an object. TODO: this design is not final and this field is subject to change in the future.' + type: string + kind: + description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + namespace: + description: 'Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/' + type: string + resourceVersion: + description: 'Specific resourceVersion to which this reference is made, if any. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency' + type: string + uid: + description: 'UID of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids' + type: string + served: true + storage: true + subresources: + status: {} + - name: v1alpha2 + schema: + openAPIV3Schema: + description: OperatorGroup is the unit of multitenancy for OLM managed operators. It constrains the installation of operators in its namespace to a specified set of target namespaces. + type: object + required: + - metadata + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More 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. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: OperatorGroupSpec is the spec for an OperatorGroup resource. + type: object + properties: + selector: + description: Selector selects the OperatorGroup's target namespaces. + type: object + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + type: array + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + type: array + items: + type: string + matchLabels: + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + additionalProperties: + type: string + serviceAccountName: + description: ServiceAccountName is the admin specified service account which will be used to deploy operator(s) in this operator group. + type: string + staticProvidedAPIs: + description: Static tells OLM not to update the OperatorGroup's providedAPIs annotation + type: boolean + targetNamespaces: + description: TargetNamespaces is an explicit set of namespaces to target. If it is set, Selector is ignored. + type: array + items: + type: string + status: + description: OperatorGroupStatus is the status for an OperatorGroupResource. + type: object + required: + - lastUpdated + properties: + lastUpdated: + description: LastUpdated is a timestamp of the last time the OperatorGroup's status was Updated. + type: string + format: date-time + namespaces: + description: Namespaces is the set of target namespaces for the OperatorGroup. + type: array + items: + type: string + serviceAccountRef: + description: ServiceAccountRef references the service account object specified. + type: object + properties: + apiVersion: + description: API version of the referent. + type: string + fieldPath: + description: 'If referring to a piece of an object instead of an entire object, this string should contain a valid JSON/Go field access statement, such as desiredState.manifest.containers[2]. For example, if the object reference is to a container within a pod, this would take on a value like: "spec.containers{name}" (where "name" refers to the name of the container that triggered the event) or if no container name is specified "spec.containers[2]" (container with index 2 in this pod). This syntax is chosen only to have some well-defined way of referencing a part of an object. TODO: this design is not final and this field is subject to change in the future.' + type: string + kind: + description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + namespace: + description: 'Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/' + type: string + resourceVersion: + description: 'Specific resourceVersion to which this reference is made, if any. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency' + type: string + uid: + description: 'UID of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids' + type: string + served: true + storage: false + subresources: + status: {} + diff --git a/staging/operator-lifecycle-manager/deploy/upstream/manifests/0.18.3/0000_50_olm_00-operators.crd.yaml b/staging/operator-lifecycle-manager/deploy/upstream/manifests/0.18.3/0000_50_olm_00-operators.crd.yaml new file mode 100644 index 0000000000..31b582b3b8 --- /dev/null +++ b/staging/operator-lifecycle-manager/deploy/upstream/manifests/0.18.3/0000_50_olm_00-operators.crd.yaml @@ -0,0 +1,140 @@ +--- +# Source: olm/crds/0000_50_olm_00-operators.crd.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.4.1 + creationTimestamp: null + name: operators.operators.coreos.com +spec: + group: operators.coreos.com + names: + categories: + - olm + kind: Operator + listKind: OperatorList + plural: operators + singular: operator + scope: Cluster + versions: + - name: v1 + schema: + openAPIV3Schema: + description: Operator represents a cluster operator. + type: object + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More 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. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: OperatorSpec defines the desired state of Operator + type: object + status: + description: OperatorStatus defines the observed state of an Operator and its components + type: object + properties: + components: + description: Components describes resources that compose the operator. + type: object + required: + - labelSelector + properties: + labelSelector: + description: LabelSelector is a label query over a set of resources used to select the operator's components + type: object + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + type: array + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + type: array + items: + type: string + matchLabels: + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + additionalProperties: + type: string + refs: + description: Refs are a set of references to the operator's component resources, selected with LabelSelector. + type: array + items: + description: RichReference is a reference to a resource, enriched with its status conditions. + type: object + properties: + apiVersion: + description: API version of the referent. + type: string + conditions: + description: Conditions represents the latest state of the component. + type: array + items: + description: Condition represent the latest available observations of an component's state. + type: object + required: + - status + - type + properties: + lastTransitionTime: + description: Last time the condition transitioned from one status to another. + type: string + format: date-time + lastUpdateTime: + description: Last time the condition was probed + type: string + format: date-time + message: + description: A human readable message indicating details about the transition. + type: string + reason: + description: The reason for the condition's last transition. + type: string + status: + description: Status of the condition, one of True, False, Unknown. + type: string + type: + description: Type of condition. + type: string + fieldPath: + description: 'If referring to a piece of an object instead of an entire object, this string should contain a valid JSON/Go field access statement, such as desiredState.manifest.containers[2]. For example, if the object reference is to a container within a pod, this would take on a value like: "spec.containers{name}" (where "name" refers to the name of the container that triggered the event) or if no container name is specified "spec.containers[2]" (container with index 2 in this pod). This syntax is chosen only to have some well-defined way of referencing a part of an object. TODO: this design is not final and this field is subject to change in the future.' + type: string + kind: + description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + namespace: + description: 'Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/' + type: string + resourceVersion: + description: 'Specific resourceVersion to which this reference is made, if any. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency' + type: string + uid: + description: 'UID of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids' + type: string + served: true + storage: true + subresources: + status: {} + diff --git a/staging/operator-lifecycle-manager/deploy/upstream/manifests/0.18.3/0000_50_olm_00-subscriptions.crd.yaml b/staging/operator-lifecycle-manager/deploy/upstream/manifests/0.18.3/0000_50_olm_00-subscriptions.crd.yaml new file mode 100644 index 0000000000..d9769df42b --- /dev/null +++ b/staging/operator-lifecycle-manager/deploy/upstream/manifests/0.18.3/0000_50_olm_00-subscriptions.crd.yaml @@ -0,0 +1,1344 @@ +--- +# Source: olm/crds/0000_50_olm_00-subscriptions.crd.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.4.1 + creationTimestamp: null + name: subscriptions.operators.coreos.com +spec: + group: operators.coreos.com + names: + categories: + - olm + kind: Subscription + listKind: SubscriptionList + plural: subscriptions + shortNames: + - sub + - subs + singular: subscription + scope: Namespaced + versions: + - additionalPrinterColumns: + - description: The package subscribed to + jsonPath: .spec.name + name: Package + type: string + - description: The catalog source for the specified package + jsonPath: .spec.source + name: Source + type: string + - description: The channel of updates to subscribe to + jsonPath: .spec.channel + name: Channel + type: string + name: v1alpha1 + schema: + openAPIV3Schema: + description: Subscription keeps operators up to date by tracking changes to Catalogs. + type: object + required: + - metadata + - spec + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More 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. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: SubscriptionSpec defines an Application that can be installed + type: object + required: + - name + - source + - sourceNamespace + properties: + channel: + type: string + config: + description: SubscriptionConfig contains configuration specified for a subscription. + type: object + properties: + env: + description: Env is a list of environment variables to set in the container. Cannot be updated. + type: array + items: + description: EnvVar represents an environment variable present in a Container. + type: object + required: + - name + properties: + name: + description: Name of the environment variable. Must be a C_IDENTIFIER. + type: string + value: + description: 'Variable references $(VAR_NAME) are expanded using the previous defined environment variables in the container and any service environment variables. If a variable cannot be resolved, the reference in the input string will be unchanged. The $(VAR_NAME) syntax can be escaped with a double $$, ie: $$(VAR_NAME). Escaped references will never be expanded, regardless of whether the variable exists or not. Defaults to "".' + type: string + valueFrom: + description: Source for the environment variable's value. Cannot be used if value is not empty. + type: object + properties: + configMapKeyRef: + description: Selects a key of a ConfigMap. + type: object + required: + - key + properties: + key: + description: The key to select. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + optional: + description: Specify whether the ConfigMap or its key must be defined + type: boolean + fieldRef: + description: 'Selects a field of the pod: supports metadata.name, metadata.namespace, `metadata.labels['''']`, `metadata.annotations['''']`, spec.nodeName, spec.serviceAccountName, status.hostIP, status.podIP, status.podIPs.' + type: object + required: + - fieldPath + properties: + apiVersion: + description: Version of the schema the FieldPath is written in terms of, defaults to "v1". + type: string + fieldPath: + description: Path of the field to select in the specified API version. + type: string + resourceFieldRef: + description: 'Selects a resource of the container: only resources limits and requests (limits.cpu, limits.memory, limits.ephemeral-storage, requests.cpu, requests.memory and requests.ephemeral-storage) are currently supported.' + type: object + required: + - resource + properties: + containerName: + description: 'Container name: required for volumes, optional for env vars' + type: string + divisor: + description: Specifies the output format of the exposed resources, defaults to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource to select' + type: string + secretKeyRef: + description: Selects a key of a secret in the pod's namespace + type: object + required: + - key + properties: + key: + description: The key of the secret to select from. Must be a valid secret key. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + optional: + description: Specify whether the Secret or its key must be defined + type: boolean + envFrom: + description: EnvFrom is a list of sources to populate environment variables in the container. The keys defined within a source must be a C_IDENTIFIER. All invalid keys will be reported as an event when the container is starting. When a key exists in multiple sources, the value associated with the last source will take precedence. Values defined by an Env with a duplicate key will take precedence. Immutable. + type: array + items: + description: EnvFromSource represents the source of a set of ConfigMaps + type: object + properties: + configMapRef: + description: The ConfigMap to select from + type: object + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + optional: + description: Specify whether the ConfigMap must be defined + type: boolean + prefix: + description: An optional identifier to prepend to each key in the ConfigMap. Must be a C_IDENTIFIER. + type: string + secretRef: + description: The Secret to select from + type: object + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + optional: + description: Specify whether the Secret must be defined + type: boolean + nodeSelector: + description: 'NodeSelector is a selector which must be true for the pod to fit on a node. Selector which must match a node''s labels for the pod to be scheduled on that node. More info: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/' + type: object + additionalProperties: + type: string + resources: + description: 'Resources represents compute resources required by this container. Immutable. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' + type: object + properties: + limits: + description: 'Limits describes the maximum amount of compute resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' + type: object + additionalProperties: + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + requests: + description: 'Requests describes the minimum amount of compute resources required. If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, otherwise to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' + type: object + additionalProperties: + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + selector: + description: Selector is the label selector for pods to be configured. Existing ReplicaSets whose pods are selected by this will be the ones affected by this deployment. It must match the pod template's labels. + type: object + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + type: array + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + type: array + items: + type: string + matchLabels: + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + additionalProperties: + type: string + tolerations: + description: Tolerations are the pod's tolerations. + type: array + items: + description: The pod this Toleration is attached to tolerates any taint that matches the triple using the matching operator . + type: object + properties: + effect: + description: Effect indicates the taint effect to match. Empty means match all taint effects. When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: Key is the taint key that the toleration applies to. Empty means match all taint keys. If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: Operator represents a key's relationship to the value. Valid operators are Exists and Equal. Defaults to Equal. Exists is equivalent to wildcard for value, so that a pod can tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: TolerationSeconds represents the period of time the toleration (which must be of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, it is not set, which means tolerate the taint forever (do not evict). Zero and negative values will be treated as 0 (evict immediately) by the system. + type: integer + format: int64 + value: + description: Value is the taint value the toleration matches to. If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + volumeMounts: + description: List of VolumeMounts to set in the container. + type: array + items: + description: VolumeMount describes a mounting of a Volume within a container. + type: object + required: + - mountPath + - name + properties: + mountPath: + description: Path within the container at which the volume should be mounted. Must not contain ':'. + type: string + mountPropagation: + description: mountPropagation determines how mounts are propagated from the host to container and the other way around. When not set, MountPropagationNone is used. This field is beta in 1.10. + type: string + name: + description: This must match the Name of a Volume. + type: string + readOnly: + description: Mounted read-only if true, read-write otherwise (false or unspecified). Defaults to false. + type: boolean + subPath: + description: Path within the volume from which the container's volume should be mounted. Defaults to "" (volume's root). + type: string + subPathExpr: + description: Expanded path within the volume from which the container's volume should be mounted. Behaves similarly to SubPath but environment variable references $(VAR_NAME) are expanded using the container's environment. Defaults to "" (volume's root). SubPathExpr and SubPath are mutually exclusive. + type: string + volumes: + description: List of Volumes to set in the podSpec. + type: array + items: + description: Volume represents a named volume in a pod that may be accessed by any container in the pod. + type: object + required: + - name + properties: + awsElasticBlockStore: + description: 'AWSElasticBlockStore represents an AWS Disk resource that is attached to a kubelet''s host machine and then exposed to the pod. More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore' + type: object + required: + - volumeID + properties: + fsType: + description: 'Filesystem type of the volume that you want to mount. Tip: Ensure that the filesystem type is supported by the host operating system. Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore TODO: how do we prevent errors in the filesystem from compromising the machine' + type: string + partition: + description: 'The partition in the volume that you want to mount. If omitted, the default is to mount by volume name. Examples: For volume /dev/sda1, you specify the partition as "1". Similarly, the volume partition for /dev/sda is "0" (or you can leave the property empty).' + type: integer + format: int32 + readOnly: + description: 'Specify "true" to force and set the ReadOnly property in VolumeMounts to "true". If omitted, the default is "false". More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore' + type: boolean + volumeID: + description: 'Unique ID of the persistent disk resource in AWS (Amazon EBS volume). More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore' + type: string + azureDisk: + description: AzureDisk represents an Azure Data Disk mount on the host and bind mount to the pod. + type: object + required: + - diskName + - diskURI + properties: + cachingMode: + description: 'Host Caching mode: None, Read Only, Read Write.' + type: string + diskName: + description: The Name of the data disk in the blob storage + type: string + diskURI: + description: The URI the data disk in the blob storage + type: string + fsType: + description: Filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + type: string + kind: + description: 'Expected values Shared: multiple blob disks per storage account Dedicated: single blob disk per storage account Managed: azure managed data disk (only in managed availability set). defaults to shared' + type: string + readOnly: + description: Defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts. + type: boolean + azureFile: + description: AzureFile represents an Azure File Service mount on the host and bind mount to the pod. + type: object + required: + - secretName + - shareName + properties: + readOnly: + description: Defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts. + type: boolean + secretName: + description: the name of secret that contains Azure Storage Account Name and Key + type: string + shareName: + description: Share Name + type: string + cephfs: + description: CephFS represents a Ceph FS mount on the host that shares a pod's lifetime + type: object + required: + - monitors + properties: + monitors: + description: 'Required: Monitors is a collection of Ceph monitors More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it' + type: array + items: + type: string + path: + description: 'Optional: Used as the mounted root, rather than the full Ceph tree, default is /' + type: string + readOnly: + description: 'Optional: Defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts. More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it' + type: boolean + secretFile: + description: 'Optional: SecretFile is the path to key ring for User, default is /etc/ceph/user.secret More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it' + type: string + secretRef: + description: 'Optional: SecretRef is reference to the authentication secret for User, default is empty. More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it' + type: object + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + user: + description: 'Optional: User is the rados user name, default is admin More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it' + type: string + cinder: + description: 'Cinder represents a cinder volume attached and mounted on kubelets host machine. More info: https://examples.k8s.io/mysql-cinder-pd/README.md' + type: object + required: + - volumeID + properties: + fsType: + description: 'Filesystem type to mount. Must be a filesystem type supported by the host operating system. Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. More info: https://examples.k8s.io/mysql-cinder-pd/README.md' + type: string + readOnly: + description: 'Optional: Defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts. More info: https://examples.k8s.io/mysql-cinder-pd/README.md' + type: boolean + secretRef: + description: 'Optional: points to a secret object containing parameters used to connect to OpenStack.' + type: object + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + volumeID: + description: 'volume id used to identify the volume in cinder. More info: https://examples.k8s.io/mysql-cinder-pd/README.md' + type: string + configMap: + description: ConfigMap represents a configMap that should populate this volume + type: object + properties: + defaultMode: + description: 'Optional: mode bits used to set permissions on created files by default. Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. Defaults to 0644. Directories within the path are not affected by this setting. This might be in conflict with other options that affect the file mode, like fsGroup, and the result can be other mode bits set.' + type: integer + format: int32 + items: + description: If unspecified, each key-value pair in the Data field of the referenced ConfigMap will be projected into the volume as a file whose name is the key and content is the value. If specified, the listed keys will be projected into the specified paths, and unlisted keys will not be present. If a key is specified which is not present in the ConfigMap, the volume setup will error unless it is marked optional. Paths must be relative and may not contain the '..' path or start with '..'. + type: array + items: + description: Maps a string key to a path within a volume. + type: object + required: + - key + - path + properties: + key: + description: The key to project. + type: string + mode: + description: 'Optional: mode bits used to set permissions on this file. Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. If not specified, the volume defaultMode will be used. This might be in conflict with other options that affect the file mode, like fsGroup, and the result can be other mode bits set.' + type: integer + format: int32 + path: + description: The relative path of the file to map the key to. May not be an absolute path. May not contain the path element '..'. May not start with the string '..'. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + optional: + description: Specify whether the ConfigMap or its keys must be defined + type: boolean + csi: + description: CSI (Container Storage Interface) represents ephemeral storage that is handled by certain external CSI drivers (Beta feature). + type: object + required: + - driver + properties: + driver: + description: Driver is the name of the CSI driver that handles this volume. Consult with your admin for the correct name as registered in the cluster. + type: string + fsType: + description: Filesystem type to mount. Ex. "ext4", "xfs", "ntfs". If not provided, the empty value is passed to the associated CSI driver which will determine the default filesystem to apply. + type: string + nodePublishSecretRef: + description: NodePublishSecretRef is a reference to the secret object containing sensitive information to pass to the CSI driver to complete the CSI NodePublishVolume and NodeUnpublishVolume calls. This field is optional, and may be empty if no secret is required. If the secret object contains more than one secret, all secret references are passed. + type: object + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + readOnly: + description: Specifies a read-only configuration for the volume. Defaults to false (read/write). + type: boolean + volumeAttributes: + description: VolumeAttributes stores driver-specific properties that are passed to the CSI driver. Consult your driver's documentation for supported values. + type: object + additionalProperties: + type: string + downwardAPI: + description: DownwardAPI represents downward API about the pod that should populate this volume + type: object + properties: + defaultMode: + description: 'Optional: mode bits to use on created files by default. Must be a Optional: mode bits used to set permissions on created files by default. Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. Defaults to 0644. Directories within the path are not affected by this setting. This might be in conflict with other options that affect the file mode, like fsGroup, and the result can be other mode bits set.' + type: integer + format: int32 + items: + description: Items is a list of downward API volume file + type: array + items: + description: DownwardAPIVolumeFile represents information to create the file containing the pod field + type: object + required: + - path + properties: + fieldRef: + description: 'Required: Selects a field of the pod: only annotations, labels, name and namespace are supported.' + type: object + required: + - fieldPath + properties: + apiVersion: + description: Version of the schema the FieldPath is written in terms of, defaults to "v1". + type: string + fieldPath: + description: Path of the field to select in the specified API version. + type: string + mode: + description: 'Optional: mode bits used to set permissions on this file, must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. If not specified, the volume defaultMode will be used. This might be in conflict with other options that affect the file mode, like fsGroup, and the result can be other mode bits set.' + type: integer + format: int32 + path: + description: 'Required: Path is the relative path name of the file to be created. Must not be absolute or contain the ''..'' path. Must be utf-8 encoded. The first item of the relative path must not start with ''..''' + type: string + resourceFieldRef: + description: 'Selects a resource of the container: only resources limits and requests (limits.cpu, limits.memory, requests.cpu and requests.memory) are currently supported.' + type: object + required: + - resource + properties: + containerName: + description: 'Container name: required for volumes, optional for env vars' + type: string + divisor: + description: Specifies the output format of the exposed resources, defaults to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource to select' + type: string + emptyDir: + description: 'EmptyDir represents a temporary directory that shares a pod''s lifetime. More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir' + type: object + properties: + medium: + description: 'What type of storage medium should back this directory. The default is "" which means to use the node''s default medium. Must be an empty string (default) or Memory. More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir' + type: string + sizeLimit: + description: 'Total amount of local storage required for this EmptyDir volume. The size limit is also applicable for memory medium. The maximum usage on memory medium EmptyDir would be the minimum value between the SizeLimit specified here and the sum of memory limits of all containers in a pod. The default is nil which means that the limit is undefined. More info: http://kubernetes.io/docs/user-guide/volumes#emptydir' + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + ephemeral: + description: "Ephemeral represents a volume that is handled by a cluster storage driver (Alpha feature). The volume's lifecycle is tied to the pod that defines it - it will be created before the pod starts, and deleted when the pod is removed. \n Use this if: a) the volume is only needed while the pod runs, b) features of normal volumes like restoring from snapshot or capacity tracking are needed, c) the storage driver is specified through a storage class, and d) the storage driver supports dynamic volume provisioning through a PersistentVolumeClaim (see EphemeralVolumeSource for more information on the connection between this volume type and PersistentVolumeClaim). \n Use PersistentVolumeClaim or one of the vendor-specific APIs for volumes that persist for longer than the lifecycle of an individual pod. \n Use CSI for light-weight local ephemeral volumes if the CSI driver is meant to be used that way - see the documentation of the driver for more information. \n A pod can use both types of ephemeral volumes and persistent volumes at the same time." + type: object + properties: + readOnly: + description: Specifies a read-only configuration for the volume. Defaults to false (read/write). + type: boolean + volumeClaimTemplate: + description: "Will be used to create a stand-alone PVC to provision the volume. The pod in which this EphemeralVolumeSource is embedded will be the owner of the PVC, i.e. the PVC will be deleted together with the pod. The name of the PVC will be `-` where `` is the name from the `PodSpec.Volumes` array entry. Pod validation will reject the pod if the concatenated name is not valid for a PVC (for example, too long). \n An existing PVC with that name that is not owned by the pod will *not* be used for the pod to avoid using an unrelated volume by mistake. Starting the pod is then blocked until the unrelated PVC is removed. If such a pre-created PVC is meant to be used by the pod, the PVC has to updated with an owner reference to the pod once the pod exists. Normally this should not be necessary, but it may be useful when manually reconstructing a broken cluster. \n This field is read-only and no changes will be made by Kubernetes to the PVC after it has been created. \n Required, must not be nil." + type: object + required: + - spec + properties: + metadata: + description: May contain labels and annotations that will be copied into the PVC when creating it. No other fields are allowed and will be rejected during validation. + type: object + spec: + description: The specification for the PersistentVolumeClaim. The entire content is copied unchanged into the PVC that gets created from this template. The same fields as in a PersistentVolumeClaim are also valid here. + type: object + properties: + accessModes: + description: 'AccessModes contains the desired access modes the volume should have. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1' + type: array + items: + type: string + dataSource: + description: 'This field can be used to specify either: * An existing VolumeSnapshot object (snapshot.storage.k8s.io/VolumeSnapshot) * An existing PVC (PersistentVolumeClaim) * An existing custom resource that implements data population (Alpha) In order to use custom resource types that implement data population, the AnyVolumeDataSource feature gate must be enabled. If the provisioner or an external controller can support the specified data source, it will create a new volume based on the contents of the specified data source.' + type: object + required: + - kind + - name + properties: + apiGroup: + description: APIGroup is the group for the resource being referenced. If APIGroup is not specified, the specified Kind must be in the core API group. For any other third-party types, APIGroup is required. + type: string + kind: + description: Kind is the type of resource being referenced + type: string + name: + description: Name is the name of resource being referenced + type: string + resources: + description: 'Resources represents the minimum resources the volume should have. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#resources' + type: object + properties: + limits: + description: 'Limits describes the maximum amount of compute resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' + type: object + additionalProperties: + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + requests: + description: 'Requests describes the minimum amount of compute resources required. If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, otherwise to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' + type: object + additionalProperties: + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + selector: + description: A label query over volumes to consider for binding. + type: object + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + type: array + items: + description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + type: object + required: + - key + - operator + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + type: array + items: + type: string + matchLabels: + description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + additionalProperties: + type: string + storageClassName: + description: 'Name of the StorageClass required by the claim. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#class-1' + type: string + volumeMode: + description: volumeMode defines what type of volume is required by the claim. Value of Filesystem is implied when not included in claim spec. + type: string + volumeName: + description: VolumeName is the binding reference to the PersistentVolume backing this claim. + type: string + fc: + description: FC represents a Fibre Channel resource that is attached to a kubelet's host machine and then exposed to the pod. + type: object + properties: + fsType: + description: 'Filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. TODO: how do we prevent errors in the filesystem from compromising the machine' + type: string + lun: + description: 'Optional: FC target lun number' + type: integer + format: int32 + readOnly: + description: 'Optional: Defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts.' + type: boolean + targetWWNs: + description: 'Optional: FC target worldwide names (WWNs)' + type: array + items: + type: string + wwids: + description: 'Optional: FC volume world wide identifiers (wwids) Either wwids or combination of targetWWNs and lun must be set, but not both simultaneously.' + type: array + items: + type: string + flexVolume: + description: FlexVolume represents a generic volume resource that is provisioned/attached using an exec based plugin. + type: object + required: + - driver + properties: + driver: + description: Driver is the name of the driver to use for this volume. + type: string + fsType: + description: Filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. "ext4", "xfs", "ntfs". The default filesystem depends on FlexVolume script. + type: string + options: + description: 'Optional: Extra command options if any.' + type: object + additionalProperties: + type: string + readOnly: + description: 'Optional: Defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts.' + type: boolean + secretRef: + description: 'Optional: SecretRef is reference to the secret object containing sensitive information to pass to the plugin scripts. This may be empty if no secret object is specified. If the secret object contains more than one secret, all secrets are passed to the plugin scripts.' + type: object + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + flocker: + description: Flocker represents a Flocker volume attached to a kubelet's host machine. This depends on the Flocker control service being running + type: object + properties: + datasetName: + description: Name of the dataset stored as metadata -> name on the dataset for Flocker should be considered as deprecated + type: string + datasetUUID: + description: UUID of the dataset. This is unique identifier of a Flocker dataset + type: string + gcePersistentDisk: + description: 'GCEPersistentDisk represents a GCE Disk resource that is attached to a kubelet''s host machine and then exposed to the pod. More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk' + type: object + required: + - pdName + properties: + fsType: + description: 'Filesystem type of the volume that you want to mount. Tip: Ensure that the filesystem type is supported by the host operating system. Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk TODO: how do we prevent errors in the filesystem from compromising the machine' + type: string + partition: + description: 'The partition in the volume that you want to mount. If omitted, the default is to mount by volume name. Examples: For volume /dev/sda1, you specify the partition as "1". Similarly, the volume partition for /dev/sda is "0" (or you can leave the property empty). More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk' + type: integer + format: int32 + pdName: + description: 'Unique name of the PD resource in GCE. Used to identify the disk in GCE. More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk' + type: string + readOnly: + description: 'ReadOnly here will force the ReadOnly setting in VolumeMounts. Defaults to false. More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk' + type: boolean + gitRepo: + description: 'GitRepo represents a git repository at a particular revision. DEPRECATED: GitRepo is deprecated. To provision a container with a git repo, mount an EmptyDir into an InitContainer that clones the repo using git, then mount the EmptyDir into the Pod''s container.' + type: object + required: + - repository + properties: + directory: + description: Target directory name. Must not contain or start with '..'. If '.' is supplied, the volume directory will be the git repository. Otherwise, if specified, the volume will contain the git repository in the subdirectory with the given name. + type: string + repository: + description: Repository URL + type: string + revision: + description: Commit hash for the specified revision. + type: string + glusterfs: + description: 'Glusterfs represents a Glusterfs mount on the host that shares a pod''s lifetime. More info: https://examples.k8s.io/volumes/glusterfs/README.md' + type: object + required: + - endpoints + - path + properties: + endpoints: + description: 'EndpointsName is the endpoint name that details Glusterfs topology. More info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod' + type: string + path: + description: 'Path is the Glusterfs volume path. More info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod' + type: string + readOnly: + description: 'ReadOnly here will force the Glusterfs volume to be mounted with read-only permissions. Defaults to false. More info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod' + type: boolean + hostPath: + description: 'HostPath represents a pre-existing file or directory on the host machine that is directly exposed to the container. This is generally used for system agents or other privileged things that are allowed to see the host machine. Most containers will NOT need this. More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath --- TODO(jonesdl) We need to restrict who can use host directory mounts and who can/can not mount host directories as read/write.' + type: object + required: + - path + properties: + path: + description: 'Path of the directory on the host. If the path is a symlink, it will follow the link to the real path. More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath' + type: string + type: + description: 'Type for HostPath Volume Defaults to "" More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath' + type: string + iscsi: + description: 'ISCSI represents an ISCSI Disk resource that is attached to a kubelet''s host machine and then exposed to the pod. More info: https://examples.k8s.io/volumes/iscsi/README.md' + type: object + required: + - iqn + - lun + - targetPortal + properties: + chapAuthDiscovery: + description: whether support iSCSI Discovery CHAP authentication + type: boolean + chapAuthSession: + description: whether support iSCSI Session CHAP authentication + type: boolean + fsType: + description: 'Filesystem type of the volume that you want to mount. Tip: Ensure that the filesystem type is supported by the host operating system. Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. More info: https://kubernetes.io/docs/concepts/storage/volumes#iscsi TODO: how do we prevent errors in the filesystem from compromising the machine' + type: string + initiatorName: + description: Custom iSCSI Initiator Name. If initiatorName is specified with iscsiInterface simultaneously, new iSCSI interface : will be created for the connection. + type: string + iqn: + description: Target iSCSI Qualified Name. + type: string + iscsiInterface: + description: iSCSI Interface Name that uses an iSCSI transport. Defaults to 'default' (tcp). + type: string + lun: + description: iSCSI Target Lun number. + type: integer + format: int32 + portals: + description: iSCSI Target Portal List. The portal is either an IP or ip_addr:port if the port is other than default (typically TCP ports 860 and 3260). + type: array + items: + type: string + readOnly: + description: ReadOnly here will force the ReadOnly setting in VolumeMounts. Defaults to false. + type: boolean + secretRef: + description: CHAP Secret for iSCSI target and initiator authentication + type: object + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + targetPortal: + description: iSCSI Target Portal. The Portal is either an IP or ip_addr:port if the port is other than default (typically TCP ports 860 and 3260). + type: string + name: + description: 'Volume''s name. Must be a DNS_LABEL and unique within the pod. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + nfs: + description: 'NFS represents an NFS mount on the host that shares a pod''s lifetime More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs' + type: object + required: + - path + - server + properties: + path: + description: 'Path that is exported by the NFS server. More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs' + type: string + readOnly: + description: 'ReadOnly here will force the NFS export to be mounted with read-only permissions. Defaults to false. More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs' + type: boolean + server: + description: 'Server is the hostname or IP address of the NFS server. More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs' + type: string + persistentVolumeClaim: + description: 'PersistentVolumeClaimVolumeSource represents a reference to a PersistentVolumeClaim in the same namespace. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims' + type: object + required: + - claimName + properties: + claimName: + description: 'ClaimName is the name of a PersistentVolumeClaim in the same namespace as the pod using this volume. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims' + type: string + readOnly: + description: Will force the ReadOnly setting in VolumeMounts. Default false. + type: boolean + photonPersistentDisk: + description: PhotonPersistentDisk represents a PhotonController persistent disk attached and mounted on kubelets host machine + type: object + required: + - pdID + properties: + fsType: + description: Filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + type: string + pdID: + description: ID that identifies Photon Controller persistent disk + type: string + portworxVolume: + description: PortworxVolume represents a portworx volume attached and mounted on kubelets host machine + type: object + required: + - volumeID + properties: + fsType: + description: FSType represents the filesystem type to mount Must be a filesystem type supported by the host operating system. Ex. "ext4", "xfs". Implicitly inferred to be "ext4" if unspecified. + type: string + readOnly: + description: Defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts. + type: boolean + volumeID: + description: VolumeID uniquely identifies a Portworx volume + type: string + projected: + description: Items for all in one resources secrets, configmaps, and downward API + type: object + properties: + defaultMode: + description: Mode bits used to set permissions on created files by default. Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. Directories within the path are not affected by this setting. This might be in conflict with other options that affect the file mode, like fsGroup, and the result can be other mode bits set. + type: integer + format: int32 + sources: + description: list of volume projections + type: array + items: + description: Projection that may be projected along with other supported volume types + type: object + properties: + configMap: + description: information about the configMap data to project + type: object + properties: + items: + description: If unspecified, each key-value pair in the Data field of the referenced ConfigMap will be projected into the volume as a file whose name is the key and content is the value. If specified, the listed keys will be projected into the specified paths, and unlisted keys will not be present. If a key is specified which is not present in the ConfigMap, the volume setup will error unless it is marked optional. Paths must be relative and may not contain the '..' path or start with '..'. + type: array + items: + description: Maps a string key to a path within a volume. + type: object + required: + - key + - path + properties: + key: + description: The key to project. + type: string + mode: + description: 'Optional: mode bits used to set permissions on this file. Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. If not specified, the volume defaultMode will be used. This might be in conflict with other options that affect the file mode, like fsGroup, and the result can be other mode bits set.' + type: integer + format: int32 + path: + description: The relative path of the file to map the key to. May not be an absolute path. May not contain the path element '..'. May not start with the string '..'. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + optional: + description: Specify whether the ConfigMap or its keys must be defined + type: boolean + downwardAPI: + description: information about the downwardAPI data to project + type: object + properties: + items: + description: Items is a list of DownwardAPIVolume file + type: array + items: + description: DownwardAPIVolumeFile represents information to create the file containing the pod field + type: object + required: + - path + properties: + fieldRef: + description: 'Required: Selects a field of the pod: only annotations, labels, name and namespace are supported.' + type: object + required: + - fieldPath + properties: + apiVersion: + description: Version of the schema the FieldPath is written in terms of, defaults to "v1". + type: string + fieldPath: + description: Path of the field to select in the specified API version. + type: string + mode: + description: 'Optional: mode bits used to set permissions on this file, must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. If not specified, the volume defaultMode will be used. This might be in conflict with other options that affect the file mode, like fsGroup, and the result can be other mode bits set.' + type: integer + format: int32 + path: + description: 'Required: Path is the relative path name of the file to be created. Must not be absolute or contain the ''..'' path. Must be utf-8 encoded. The first item of the relative path must not start with ''..''' + type: string + resourceFieldRef: + description: 'Selects a resource of the container: only resources limits and requests (limits.cpu, limits.memory, requests.cpu and requests.memory) are currently supported.' + type: object + required: + - resource + properties: + containerName: + description: 'Container name: required for volumes, optional for env vars' + type: string + divisor: + description: Specifies the output format of the exposed resources, defaults to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource to select' + type: string + secret: + description: information about the secret data to project + type: object + properties: + items: + description: If unspecified, each key-value pair in the Data field of the referenced Secret will be projected into the volume as a file whose name is the key and content is the value. If specified, the listed keys will be projected into the specified paths, and unlisted keys will not be present. If a key is specified which is not present in the Secret, the volume setup will error unless it is marked optional. Paths must be relative and may not contain the '..' path or start with '..'. + type: array + items: + description: Maps a string key to a path within a volume. + type: object + required: + - key + - path + properties: + key: + description: The key to project. + type: string + mode: + description: 'Optional: mode bits used to set permissions on this file. Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. If not specified, the volume defaultMode will be used. This might be in conflict with other options that affect the file mode, like fsGroup, and the result can be other mode bits set.' + type: integer + format: int32 + path: + description: The relative path of the file to map the key to. May not be an absolute path. May not contain the path element '..'. May not start with the string '..'. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + optional: + description: Specify whether the Secret or its key must be defined + type: boolean + serviceAccountToken: + description: information about the serviceAccountToken data to project + type: object + required: + - path + properties: + audience: + description: Audience is the intended audience of the token. A recipient of a token must identify itself with an identifier specified in the audience of the token, and otherwise should reject the token. The audience defaults to the identifier of the apiserver. + type: string + expirationSeconds: + description: ExpirationSeconds is the requested duration of validity of the service account token. As the token approaches expiration, the kubelet volume plugin will proactively rotate the service account token. The kubelet will start trying to rotate the token if the token is older than 80 percent of its time to live or if the token is older than 24 hours.Defaults to 1 hour and must be at least 10 minutes. + type: integer + format: int64 + path: + description: Path is the path relative to the mount point of the file to project the token into. + type: string + quobyte: + description: Quobyte represents a Quobyte mount on the host that shares a pod's lifetime + type: object + required: + - registry + - volume + properties: + group: + description: Group to map volume access to Default is no group + type: string + readOnly: + description: ReadOnly here will force the Quobyte volume to be mounted with read-only permissions. Defaults to false. + type: boolean + registry: + description: Registry represents a single or multiple Quobyte Registry services specified as a string as host:port pair (multiple entries are separated with commas) which acts as the central registry for volumes + type: string + tenant: + description: Tenant owning the given Quobyte volume in the Backend Used with dynamically provisioned Quobyte volumes, value is set by the plugin + type: string + user: + description: User to map volume access to Defaults to serivceaccount user + type: string + volume: + description: Volume is a string that references an already created Quobyte volume by name. + type: string + rbd: + description: 'RBD represents a Rados Block Device mount on the host that shares a pod''s lifetime. More info: https://examples.k8s.io/volumes/rbd/README.md' + type: object + required: + - image + - monitors + properties: + fsType: + description: 'Filesystem type of the volume that you want to mount. Tip: Ensure that the filesystem type is supported by the host operating system. Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. More info: https://kubernetes.io/docs/concepts/storage/volumes#rbd TODO: how do we prevent errors in the filesystem from compromising the machine' + type: string + image: + description: 'The rados image name. More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it' + type: string + keyring: + description: 'Keyring is the path to key ring for RBDUser. Default is /etc/ceph/keyring. More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it' + type: string + monitors: + description: 'A collection of Ceph monitors. More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it' + type: array + items: + type: string + pool: + description: 'The rados pool name. Default is rbd. More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it' + type: string + readOnly: + description: 'ReadOnly here will force the ReadOnly setting in VolumeMounts. Defaults to false. More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it' + type: boolean + secretRef: + description: 'SecretRef is name of the authentication secret for RBDUser. If provided overrides keyring. Default is nil. More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it' + type: object + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + user: + description: 'The rados user name. Default is admin. More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it' + type: string + scaleIO: + description: ScaleIO represents a ScaleIO persistent volume attached and mounted on Kubernetes nodes. + type: object + required: + - gateway + - secretRef + - system + properties: + fsType: + description: Filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. "ext4", "xfs", "ntfs". Default is "xfs". + type: string + gateway: + description: The host address of the ScaleIO API Gateway. + type: string + protectionDomain: + description: The name of the ScaleIO Protection Domain for the configured storage. + type: string + readOnly: + description: Defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts. + type: boolean + secretRef: + description: SecretRef references to the secret for ScaleIO user and other sensitive information. If this is not provided, Login operation will fail. + type: object + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + sslEnabled: + description: Flag to enable/disable SSL communication with Gateway, default false + type: boolean + storageMode: + description: Indicates whether the storage for a volume should be ThickProvisioned or ThinProvisioned. Default is ThinProvisioned. + type: string + storagePool: + description: The ScaleIO Storage Pool associated with the protection domain. + type: string + system: + description: The name of the storage system as configured in ScaleIO. + type: string + volumeName: + description: The name of a volume already created in the ScaleIO system that is associated with this volume source. + type: string + secret: + description: 'Secret represents a secret that should populate this volume. More info: https://kubernetes.io/docs/concepts/storage/volumes#secret' + type: object + properties: + defaultMode: + description: 'Optional: mode bits used to set permissions on created files by default. Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. Defaults to 0644. Directories within the path are not affected by this setting. This might be in conflict with other options that affect the file mode, like fsGroup, and the result can be other mode bits set.' + type: integer + format: int32 + items: + description: If unspecified, each key-value pair in the Data field of the referenced Secret will be projected into the volume as a file whose name is the key and content is the value. If specified, the listed keys will be projected into the specified paths, and unlisted keys will not be present. If a key is specified which is not present in the Secret, the volume setup will error unless it is marked optional. Paths must be relative and may not contain the '..' path or start with '..'. + type: array + items: + description: Maps a string key to a path within a volume. + type: object + required: + - key + - path + properties: + key: + description: The key to project. + type: string + mode: + description: 'Optional: mode bits used to set permissions on this file. Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. If not specified, the volume defaultMode will be used. This might be in conflict with other options that affect the file mode, like fsGroup, and the result can be other mode bits set.' + type: integer + format: int32 + path: + description: The relative path of the file to map the key to. May not be an absolute path. May not contain the path element '..'. May not start with the string '..'. + type: string + optional: + description: Specify whether the Secret or its keys must be defined + type: boolean + secretName: + description: 'Name of the secret in the pod''s namespace to use. More info: https://kubernetes.io/docs/concepts/storage/volumes#secret' + type: string + storageos: + description: StorageOS represents a StorageOS volume attached and mounted on Kubernetes nodes. + type: object + properties: + fsType: + description: Filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + type: string + readOnly: + description: Defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts. + type: boolean + secretRef: + description: SecretRef specifies the secret to use for obtaining the StorageOS API credentials. If not specified, default values will be attempted. + type: object + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + volumeName: + description: VolumeName is the human-readable name of the StorageOS volume. Volume names are only unique within a namespace. + type: string + volumeNamespace: + description: VolumeNamespace specifies the scope of the volume within StorageOS. If no namespace is specified then the Pod's namespace will be used. This allows the Kubernetes name scoping to be mirrored within StorageOS for tighter integration. Set VolumeName to any name to override the default behaviour. Set to "default" if you are not using namespaces within StorageOS. Namespaces that do not pre-exist within StorageOS will be created. + type: string + vsphereVolume: + description: VsphereVolume represents a vSphere volume attached and mounted on kubelets host machine + type: object + required: + - volumePath + properties: + fsType: + description: Filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + type: string + storagePolicyID: + description: Storage Policy Based Management (SPBM) profile ID associated with the StoragePolicyName. + type: string + storagePolicyName: + description: Storage Policy Based Management (SPBM) profile name. + type: string + volumePath: + description: Path that identifies vSphere volume vmdk + type: string + installPlanApproval: + description: Approval is the user approval policy for an InstallPlan. It must be one of "Automatic" or "Manual". + type: string + name: + type: string + source: + type: string + sourceNamespace: + type: string + startingCSV: + type: string + status: + type: object + required: + - lastUpdated + properties: + catalogHealth: + description: CatalogHealth contains the Subscription's view of its relevant CatalogSources' status. It is used to determine SubscriptionStatusConditions related to CatalogSources. + type: array + items: + description: SubscriptionCatalogHealth describes the health of a CatalogSource the Subscription knows about. + type: object + required: + - catalogSourceRef + - healthy + - lastUpdated + properties: + catalogSourceRef: + description: CatalogSourceRef is a reference to a CatalogSource. + type: object + properties: + apiVersion: + description: API version of the referent. + type: string + fieldPath: + description: 'If referring to a piece of an object instead of an entire object, this string should contain a valid JSON/Go field access statement, such as desiredState.manifest.containers[2]. For example, if the object reference is to a container within a pod, this would take on a value like: "spec.containers{name}" (where "name" refers to the name of the container that triggered the event) or if no container name is specified "spec.containers[2]" (container with index 2 in this pod). This syntax is chosen only to have some well-defined way of referencing a part of an object. TODO: this design is not final and this field is subject to change in the future.' + type: string + kind: + description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + namespace: + description: 'Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/' + type: string + resourceVersion: + description: 'Specific resourceVersion to which this reference is made, if any. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency' + type: string + uid: + description: 'UID of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids' + type: string + healthy: + description: Healthy is true if the CatalogSource is healthy; false otherwise. + type: boolean + lastUpdated: + description: LastUpdated represents the last time that the CatalogSourceHealth changed + type: string + format: date-time + conditions: + description: Conditions is a list of the latest available observations about a Subscription's current state. + type: array + items: + description: SubscriptionCondition represents the latest available observations of a Subscription's state. + type: object + required: + - status + - type + properties: + lastHeartbeatTime: + description: LastHeartbeatTime is the last time we got an update on a given condition + type: string + format: date-time + lastTransitionTime: + description: LastTransitionTime is the last time the condition transit from one status to another + type: string + format: date-time + message: + description: Message is a human-readable message indicating details about last transition. + type: string + reason: + description: Reason is a one-word CamelCase reason for the condition's last transition. + type: string + status: + description: Status is the status of the condition, one of True, False, Unknown. + type: string + type: + description: Type is the type of Subscription condition. + type: string + currentCSV: + description: CurrentCSV is the CSV the Subscription is progressing to. + type: string + installPlanGeneration: + description: InstallPlanGeneration is the current generation of the installplan + type: integer + installPlanRef: + description: InstallPlanRef is a reference to the latest InstallPlan that contains the Subscription's current CSV. + type: object + properties: + apiVersion: + description: API version of the referent. + type: string + fieldPath: + description: 'If referring to a piece of an object instead of an entire object, this string should contain a valid JSON/Go field access statement, such as desiredState.manifest.containers[2]. For example, if the object reference is to a container within a pod, this would take on a value like: "spec.containers{name}" (where "name" refers to the name of the container that triggered the event) or if no container name is specified "spec.containers[2]" (container with index 2 in this pod). This syntax is chosen only to have some well-defined way of referencing a part of an object. TODO: this design is not final and this field is subject to change in the future.' + type: string + kind: + description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + namespace: + description: 'Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/' + type: string + resourceVersion: + description: 'Specific resourceVersion to which this reference is made, if any. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency' + type: string + uid: + description: 'UID of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids' + type: string + installedCSV: + description: InstalledCSV is the CSV currently installed by the Subscription. + type: string + installplan: + description: 'Install is a reference to the latest InstallPlan generated for the Subscription. DEPRECATED: InstallPlanRef' + type: object + required: + - apiVersion + - kind + - name + - uuid + properties: + apiVersion: + type: string + kind: + type: string + name: + type: string + uuid: + description: UID is a type that holds unique ID values, including UUIDs. Because we don't ONLY use UUIDs, this is an alias to string. Being a type captures intent and helps make sure that UIDs and names do not get conflated. + type: string + lastUpdated: + description: LastUpdated represents the last time that the Subscription status was updated. + type: string + format: date-time + reason: + description: Reason is the reason the Subscription was transitioned to its current state. + type: string + state: + description: State represents the current state of the Subscription + type: string + served: true + storage: true + subresources: + status: {} + diff --git a/staging/operator-lifecycle-manager/deploy/upstream/manifests/0.18.3/0000_50_olm_01-olm-operator.serviceaccount.yaml b/staging/operator-lifecycle-manager/deploy/upstream/manifests/0.18.3/0000_50_olm_01-olm-operator.serviceaccount.yaml new file mode 100644 index 0000000000..e21d33d03d --- /dev/null +++ b/staging/operator-lifecycle-manager/deploy/upstream/manifests/0.18.3/0000_50_olm_01-olm-operator.serviceaccount.yaml @@ -0,0 +1,33 @@ +--- +# Source: olm/templates/0000_50_olm_01-olm-operator.serviceaccount.yaml +kind: ServiceAccount +apiVersion: v1 +metadata: + name: olm-operator-serviceaccount + namespace: olm +--- +# Source: olm/templates/0000_50_olm_01-olm-operator.serviceaccount.yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: system:controller:operator-lifecycle-manager +rules: +- apiGroups: ["*"] + resources: ["*"] + verbs: ["*"] +- nonResourceURLs: ["*"] + verbs: ["*"] +--- +# Source: olm/templates/0000_50_olm_01-olm-operator.serviceaccount.yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: olm-operator-binding-olm +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: system:controller:operator-lifecycle-manager +subjects: +- kind: ServiceAccount + name: olm-operator-serviceaccount + namespace: olm diff --git a/staging/operator-lifecycle-manager/deploy/upstream/manifests/0.18.3/0000_50_olm_07-olm-operator.deployment.yaml b/staging/operator-lifecycle-manager/deploy/upstream/manifests/0.18.3/0000_50_olm_07-olm-operator.deployment.yaml new file mode 100644 index 0000000000..9d5fe26b9e --- /dev/null +++ b/staging/operator-lifecycle-manager/deploy/upstream/manifests/0.18.3/0000_50_olm_07-olm-operator.deployment.yaml @@ -0,0 +1,60 @@ +--- +# Source: olm/templates/0000_50_olm_07-olm-operator.deployment.yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: olm-operator + namespace: olm + labels: + app: olm-operator +spec: + strategy: + type: RollingUpdate + replicas: 1 + selector: + matchLabels: + app: olm-operator + template: + metadata: + labels: + app: olm-operator + spec: + serviceAccountName: olm-operator-serviceaccount + containers: + - name: olm-operator + command: + - /bin/olm + args: + - --namespace + - $(OPERATOR_NAMESPACE) + - --writeStatusName + - "" + image: quay.io/operator-framework/olm@sha256:e74b2ac57963c7f3ba19122a8c31c9f2a0deb3c0c5cac9e5323ccffd0ca198ed + imagePullPolicy: IfNotPresent + ports: + - containerPort: 8080 + - containerPort: 8081 + name: metrics + protocol: TCP + livenessProbe: + httpGet: + path: /healthz + port: 8080 + readinessProbe: + httpGet: + path: /healthz + port: 8080 + terminationMessagePolicy: FallbackToLogsOnError + env: + - name: OPERATOR_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: OPERATOR_NAME + value: olm-operator + resources: + requests: + cpu: 10m + memory: 160Mi + nodeSelector: + kubernetes.io/os: linux diff --git a/staging/operator-lifecycle-manager/deploy/upstream/manifests/0.18.3/0000_50_olm_08-catalog-operator.deployment.yaml b/staging/operator-lifecycle-manager/deploy/upstream/manifests/0.18.3/0000_50_olm_08-catalog-operator.deployment.yaml new file mode 100644 index 0000000000..169fb6f6a5 --- /dev/null +++ b/staging/operator-lifecycle-manager/deploy/upstream/manifests/0.18.3/0000_50_olm_08-catalog-operator.deployment.yaml @@ -0,0 +1,54 @@ +--- +# Source: olm/templates/0000_50_olm_08-catalog-operator.deployment.yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: catalog-operator + namespace: olm + labels: + app: catalog-operator +spec: + strategy: + type: RollingUpdate + replicas: 1 + selector: + matchLabels: + app: catalog-operator + template: + metadata: + labels: + app: catalog-operator + spec: + serviceAccountName: olm-operator-serviceaccount + containers: + - name: catalog-operator + command: + - /bin/catalog + args: + - '-namespace' + - olm + - -configmapServerImage=quay.io/operator-framework/configmap-operator-registry:latest + - -util-image + - quay.io/operator-framework/olm@sha256:e74b2ac57963c7f3ba19122a8c31c9f2a0deb3c0c5cac9e5323ccffd0ca198ed + image: quay.io/operator-framework/olm@sha256:e74b2ac57963c7f3ba19122a8c31c9f2a0deb3c0c5cac9e5323ccffd0ca198ed + imagePullPolicy: IfNotPresent + ports: + - containerPort: 8080 + - containerPort: 8081 + name: metrics + protocol: TCP + livenessProbe: + httpGet: + path: /healthz + port: 8080 + readinessProbe: + httpGet: + path: /healthz + port: 8080 + terminationMessagePolicy: FallbackToLogsOnError + resources: + requests: + cpu: 10m + memory: 80Mi + nodeSelector: + kubernetes.io/os: linux diff --git a/staging/operator-lifecycle-manager/deploy/upstream/manifests/0.18.3/0000_50_olm_09-aggregated.clusterrole.yaml b/staging/operator-lifecycle-manager/deploy/upstream/manifests/0.18.3/0000_50_olm_09-aggregated.clusterrole.yaml new file mode 100644 index 0000000000..e7aa0af341 --- /dev/null +++ b/staging/operator-lifecycle-manager/deploy/upstream/manifests/0.18.3/0000_50_olm_09-aggregated.clusterrole.yaml @@ -0,0 +1,35 @@ +--- +# Source: olm/templates/0000_50_olm_09-aggregated.clusterrole.yaml +kind: ClusterRole +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: aggregate-olm-edit + labels: + # Add these permissions to the "admin" and "edit" default roles. + rbac.authorization.k8s.io/aggregate-to-admin: "true" + rbac.authorization.k8s.io/aggregate-to-edit: "true" +rules: +- apiGroups: ["operators.coreos.com"] + resources: ["subscriptions"] + verbs: ["create", "update", "patch", "delete"] +- apiGroups: ["operators.coreos.com"] + resources: ["clusterserviceversions", "catalogsources", "installplans", "subscriptions"] + verbs: ["delete"] +--- +# Source: olm/templates/0000_50_olm_09-aggregated.clusterrole.yaml +kind: ClusterRole +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: aggregate-olm-view + labels: + # Add these permissions to the "admin", "edit" and "view" default roles + rbac.authorization.k8s.io/aggregate-to-admin: "true" + rbac.authorization.k8s.io/aggregate-to-edit: "true" + rbac.authorization.k8s.io/aggregate-to-view: "true" +rules: +- apiGroups: ["operators.coreos.com"] + resources: ["clusterserviceversions", "catalogsources", "installplans", "subscriptions", "operatorgroups"] + verbs: ["get", "list", "watch"] +- apiGroups: ["packages.operators.coreos.com"] + resources: ["packagemanifests", "packagemanifests/icon"] + verbs: ["get", "list", "watch"] diff --git a/staging/operator-lifecycle-manager/deploy/upstream/manifests/0.18.3/0000_50_olm_13-operatorgroup-default.yaml b/staging/operator-lifecycle-manager/deploy/upstream/manifests/0.18.3/0000_50_olm_13-operatorgroup-default.yaml new file mode 100644 index 0000000000..56f5bca2bd --- /dev/null +++ b/staging/operator-lifecycle-manager/deploy/upstream/manifests/0.18.3/0000_50_olm_13-operatorgroup-default.yaml @@ -0,0 +1,17 @@ +--- +# Source: olm/templates/0000_50_olm_13-operatorgroup-default.yaml +apiVersion: operators.coreos.com/v1 +kind: OperatorGroup +metadata: + name: global-operators + namespace: operators +--- +# Source: olm/templates/0000_50_olm_13-operatorgroup-default.yaml +apiVersion: operators.coreos.com/v1 +kind: OperatorGroup +metadata: + name: olm-operators + namespace: olm +spec: + targetNamespaces: + - olm diff --git a/staging/operator-lifecycle-manager/deploy/upstream/manifests/0.18.3/0000_50_olm_15-packageserver.clusterserviceversion.yaml b/staging/operator-lifecycle-manager/deploy/upstream/manifests/0.18.3/0000_50_olm_15-packageserver.clusterserviceversion.yaml new file mode 100644 index 0000000000..ec592b9f38 --- /dev/null +++ b/staging/operator-lifecycle-manager/deploy/upstream/manifests/0.18.3/0000_50_olm_15-packageserver.clusterserviceversion.yaml @@ -0,0 +1,135 @@ +--- +# Source: olm/templates/0000_50_olm_15-packageserver.clusterserviceversion.yaml +apiVersion: operators.coreos.com/v1alpha1 +kind: ClusterServiceVersion +metadata: + name: packageserver + namespace: olm + labels: + olm.version: 0.18.3 +spec: + displayName: Package Server + description: Represents an Operator package that is available from a given CatalogSource which will resolve to a ClusterServiceVersion. + minKubeVersion: 1.11.0 + keywords: ['packagemanifests', 'olm', 'packages'] + maintainers: + - name: Red Hat + email: openshift-operators@redhat.com + provider: + name: Red Hat + links: + - name: Package Server + url: https://github.com/operator-framework/operator-lifecycle-manager/tree/master/pkg/package-server + installModes: + - type: OwnNamespace + supported: true + - type: SingleNamespace + supported: true + - type: MultiNamespace + supported: true + - type: AllNamespaces + supported: true + install: + strategy: deployment + spec: + clusterPermissions: + - serviceAccountName: olm-operator-serviceaccount + rules: + - apiGroups: + - authorization.k8s.io + resources: + - subjectaccessreviews + verbs: + - create + - get + - apiGroups: + - "" + resources: + - configmaps + verbs: + - get + - list + - watch + - apiGroups: + - "operators.coreos.com" + resources: + - catalogsources + verbs: + - get + - list + - watch + - apiGroups: + - "packages.operators.coreos.com" + resources: + - packagemanifests + verbs: + - get + - list + deployments: + - name: packageserver + spec: + strategy: + type: RollingUpdate + rollingUpdate: + maxUnavailable: 1 + maxSurge: 1 + replicas: 2 + selector: + matchLabels: + app: packageserver + template: + metadata: + labels: + app: packageserver + spec: + serviceAccountName: olm-operator-serviceaccount + nodeSelector: + kubernetes.io/os: linux + containers: + - name: packageserver + command: + - /bin/package-server + - -v=4 + - --secure-port + - "5443" + - --global-namespace + - olm + image: quay.io/operator-framework/olm@sha256:e74b2ac57963c7f3ba19122a8c31c9f2a0deb3c0c5cac9e5323ccffd0ca198ed + imagePullPolicy: Always + ports: + - containerPort: 5443 + livenessProbe: + httpGet: + scheme: HTTPS + path: /healthz + port: 5443 + readinessProbe: + httpGet: + scheme: HTTPS + path: /healthz + port: 5443 + terminationMessagePolicy: FallbackToLogsOnError + resources: + requests: + cpu: 10m + memory: 50Mi + securityContext: + runAsUser: 1000 + volumeMounts: + - name: tmpfs + mountPath: /tmp + volumes: + - name: tmpfs + emptyDir: {} + maturity: alpha + version: 0.18.3 + apiservicedefinitions: + owned: + - group: packages.operators.coreos.com + version: v1 + kind: PackageManifest + name: packagemanifests + displayName: PackageManifest + description: A PackageManifest is a resource generated from existing CatalogSources and their ConfigMaps + deploymentName: packageserver + containerPort: 5443 diff --git a/staging/operator-lifecycle-manager/deploy/upstream/manifests/0.18.3/0000_50_olm_17-upstream-operators.catalogsource.yaml b/staging/operator-lifecycle-manager/deploy/upstream/manifests/0.18.3/0000_50_olm_17-upstream-operators.catalogsource.yaml new file mode 100644 index 0000000000..50da73f93f --- /dev/null +++ b/staging/operator-lifecycle-manager/deploy/upstream/manifests/0.18.3/0000_50_olm_17-upstream-operators.catalogsource.yaml @@ -0,0 +1,15 @@ +--- +# Source: olm/templates/0000_50_olm_17-upstream-operators.catalogsource.yaml +apiVersion: operators.coreos.com/v1alpha1 +kind: CatalogSource +metadata: + name: operatorhubio-catalog + namespace: olm +spec: + sourceType: grpc + image: quay.io/operatorhubio/catalog:latest + displayName: Community Operators + publisher: OperatorHub.io + updateStrategy: + registryPoll: + interval: 60m diff --git a/staging/operator-lifecycle-manager/deploy/upstream/manifests/latest b/staging/operator-lifecycle-manager/deploy/upstream/manifests/latest index 26e7bfac65..dbfa849a36 120000 --- a/staging/operator-lifecycle-manager/deploy/upstream/manifests/latest +++ b/staging/operator-lifecycle-manager/deploy/upstream/manifests/latest @@ -1 +1 @@ -./0.18.1 \ No newline at end of file +./0.18.3 \ No newline at end of file diff --git a/staging/operator-lifecycle-manager/deploy/upstream/quickstart/crds.yaml b/staging/operator-lifecycle-manager/deploy/upstream/quickstart/crds.yaml index 3c3e418047..b1b3ed6f62 100644 --- a/staging/operator-lifecycle-manager/deploy/upstream/quickstart/crds.yaml +++ b/staging/operator-lifecycle-manager/deploy/upstream/quickstart/crds.yaml @@ -5409,6 +5409,170 @@ spec: 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])$ served: true + storage: false + subresources: + status: {} + - name: v2 + schema: + openAPIV3Schema: + description: OperatorCondition is a Custom Resource of type `OperatorCondition` which is used to convey information to OLM about the state of an operator. + type: object + required: + - metadata + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More 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. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: OperatorConditionSpec allows an operator to report state to OLM and provides cluster admin with the ability to manually override state reported by the operator. + type: object + properties: + conditions: + type: array + items: + description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, type FooStatus struct{ // Represents the observations of a foo's current state. // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge // +listType=map // +listMapKey=type Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }" + type: object + required: + - lastTransitionTime + - message + - reason + - status + - type + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + type: string + format: date-time + message: + description: message is a human readable message indicating details about the transition. This may be an empty string. + type: string + maxLength: 32768 + observedGeneration: + description: observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance. + type: integer + format: int64 + minimum: 0 + reason: + description: reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty. + type: string + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + status: + description: status of the condition, one of True, False, Unknown. + type: string + enum: + - "True" + - "False" + - Unknown + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. --- Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful (see .node.status.conditions), the ability to deconflict is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + type: string + 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])$ + deployments: + type: array + items: + type: string + overrides: + type: array + items: + description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, type FooStatus struct{ // Represents the observations of a foo's current state. // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge // +listType=map // +listMapKey=type Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }" + type: object + required: + - message + - reason + - status + - type + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + type: string + format: date-time + message: + description: message is a human readable message indicating details about the transition. This may be an empty string. + type: string + maxLength: 32768 + observedGeneration: + description: observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance. + type: integer + format: int64 + minimum: 0 + reason: + description: reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty. + type: string + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + status: + description: status of the condition, one of True, False, Unknown. + type: string + enum: + - "True" + - "False" + - Unknown + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. --- Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful (see .node.status.conditions), the ability to deconflict is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + type: string + 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])$ + serviceAccounts: + type: array + items: + type: string + status: + description: OperatorConditionStatus allows OLM to convey which conditions have been observed. + type: object + properties: + conditions: + type: array + items: + description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, type FooStatus struct{ // Represents the observations of a foo's current state. // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge // +listType=map // +listMapKey=type Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }" + type: object + required: + - lastTransitionTime + - message + - reason + - status + - type + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + type: string + format: date-time + message: + description: message is a human readable message indicating details about the transition. This may be an empty string. + type: string + maxLength: 32768 + observedGeneration: + description: observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance. + type: integer + format: int64 + minimum: 0 + reason: + description: reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty. + type: string + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + status: + description: status of the condition, one of True, False, Unknown. + type: string + enum: + - "True" + - "False" + - Unknown + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. --- Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful (see .node.status.conditions), the ability to deconflict is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + type: string + 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])$ + served: true storage: true subresources: status: {} diff --git a/staging/operator-lifecycle-manager/deploy/upstream/quickstart/install.sh b/staging/operator-lifecycle-manager/deploy/upstream/quickstart/install.sh index 6f4e9fab1b..35c65b35ce 100755 --- a/staging/operator-lifecycle-manager/deploy/upstream/quickstart/install.sh +++ b/staging/operator-lifecycle-manager/deploy/upstream/quickstart/install.sh @@ -14,6 +14,11 @@ if [[ ${#@} -lt 1 || ${#@} -gt 2 ]]; then exit 1 fi +if kubectl get deployment olm-operator -n openshift-operator-lifecycle-manager -o=jsonpath='{.spec}' > /dev/null 2>&1; then + echo "OLM is already installed in a different configuration. This is common if you are not running a vanilla Kubernetes cluster. Exiting..." + exit 1 +fi + release="$1" base_url="${2:-${default_base_url}}" url="${base_url}/${release}" diff --git a/staging/operator-lifecycle-manager/deploy/upstream/quickstart/olm.yaml b/staging/operator-lifecycle-manager/deploy/upstream/quickstart/olm.yaml index 810e8907b0..d011cbf1ee 100644 --- a/staging/operator-lifecycle-manager/deploy/upstream/quickstart/olm.yaml +++ b/staging/operator-lifecycle-manager/deploy/upstream/quickstart/olm.yaml @@ -68,7 +68,7 @@ spec: - $(OPERATOR_NAMESPACE) - --writeStatusName - "" - image: quay.io/operator-framework/olm@sha256:b706ee6583c4c3cf8059d44234c8a4505804adcc742bcddb3d1e2f6eff3d6519 + image: quay.io/operator-framework/olm@sha256:e74b2ac57963c7f3ba19122a8c31c9f2a0deb3c0c5cac9e5323ccffd0ca198ed imagePullPolicy: IfNotPresent ports: - containerPort: 8080 @@ -85,7 +85,6 @@ spec: port: 8080 terminationMessagePolicy: FallbackToLogsOnError env: - - name: OPERATOR_NAMESPACE valueFrom: fieldRef: @@ -96,8 +95,6 @@ spec: requests: cpu: 10m memory: 160Mi - - nodeSelector: kubernetes.io/os: linux --- @@ -130,8 +127,8 @@ spec: - olm - -configmapServerImage=quay.io/operator-framework/configmap-operator-registry:latest - -util-image - - quay.io/operator-framework/olm@sha256:b706ee6583c4c3cf8059d44234c8a4505804adcc742bcddb3d1e2f6eff3d6519 - image: quay.io/operator-framework/olm@sha256:b706ee6583c4c3cf8059d44234c8a4505804adcc742bcddb3d1e2f6eff3d6519 + - quay.io/operator-framework/olm@sha256:e74b2ac57963c7f3ba19122a8c31c9f2a0deb3c0c5cac9e5323ccffd0ca198ed + image: quay.io/operator-framework/olm@sha256:e74b2ac57963c7f3ba19122a8c31c9f2a0deb3c0c5cac9e5323ccffd0ca198ed imagePullPolicy: IfNotPresent ports: - containerPort: 8080 @@ -147,14 +144,10 @@ spec: path: /healthz port: 8080 terminationMessagePolicy: FallbackToLogsOnError - env: - resources: requests: cpu: 10m memory: 80Mi - - nodeSelector: kubernetes.io/os: linux --- @@ -210,7 +203,7 @@ metadata: name: packageserver namespace: olm labels: - olm.version: 0.18.1 + olm.version: 0.18.3 spec: displayName: Package Server description: Represents an Operator package that is available from a given CatalogSource which will resolve to a ClusterServiceVersion. @@ -298,7 +291,7 @@ spec: - "5443" - --global-namespace - olm - image: quay.io/operator-framework/olm@sha256:b706ee6583c4c3cf8059d44234c8a4505804adcc742bcddb3d1e2f6eff3d6519 + image: quay.io/operator-framework/olm@sha256:e74b2ac57963c7f3ba19122a8c31c9f2a0deb3c0c5cac9e5323ccffd0ca198ed imagePullPolicy: Always ports: - containerPort: 5443 @@ -326,7 +319,7 @@ spec: - name: tmpfs emptyDir: {} maturity: alpha - version: 0.18.1 + version: 0.18.3 apiservicedefinitions: owned: - group: packages.operators.coreos.com @@ -348,3 +341,6 @@ spec: image: quay.io/operatorhubio/catalog:latest displayName: Community Operators publisher: OperatorHub.io + updateStrategy: + registryPoll: + interval: 60m diff --git a/staging/operator-lifecycle-manager/deploy/upstream/values.yaml b/staging/operator-lifecycle-manager/deploy/upstream/values.yaml index 723fa98d55..ccfa1e0166 100644 --- a/staging/operator-lifecycle-manager/deploy/upstream/values.yaml +++ b/staging/operator-lifecycle-manager/deploy/upstream/values.yaml @@ -9,14 +9,14 @@ writePackageServerStatusName: "" olm: replicaCount: 1 image: - ref: quay.io/operator-framework/olm@sha256:b706ee6583c4c3cf8059d44234c8a4505804adcc742bcddb3d1e2f6eff3d6519 + ref: quay.io/operator-framework/olm@sha256:e74b2ac57963c7f3ba19122a8c31c9f2a0deb3c0c5cac9e5323ccffd0ca198ed pullPolicy: IfNotPresent service: internalPort: 8080 catalog: replicaCount: 1 image: - ref: quay.io/operator-framework/olm@sha256:b706ee6583c4c3cf8059d44234c8a4505804adcc742bcddb3d1e2f6eff3d6519 + ref: quay.io/operator-framework/olm@sha256:e74b2ac57963c7f3ba19122a8c31c9f2a0deb3c0c5cac9e5323ccffd0ca198ed pullPolicy: IfNotPresent service: internalPort: 8080 @@ -25,7 +25,7 @@ package: maxUnavailable: 1 maxSurge: 1 image: - ref: quay.io/operator-framework/olm@sha256:b706ee6583c4c3cf8059d44234c8a4505804adcc742bcddb3d1e2f6eff3d6519 + ref: quay.io/operator-framework/olm@sha256:e74b2ac57963c7f3ba19122a8c31c9f2a0deb3c0c5cac9e5323ccffd0ca198ed pullPolicy: Always service: internalPort: 5443 diff --git a/staging/operator-lifecycle-manager/doc/design/release.md b/staging/operator-lifecycle-manager/doc/design/release.md index 1843b88efc..755de20236 100644 --- a/staging/operator-lifecycle-manager/doc/design/release.md +++ b/staging/operator-lifecycle-manager/doc/design/release.md @@ -66,18 +66,29 @@ You need to have `gem` installed on your workstation. Execute the following comm gem install github_changelog_generator ``` -Afterward installing it may be worth modifying the `MAX_THREAD_NUMBER` to something lower similar to what is done here: . Note that the referenced PR has been merged, but the number is still too high. Although 1 is a very low value, it does seem to work more reliably. (On Fedora, the install location for the gem is `~/.gem/ruby/gems/github_changelog_generator-1.14.3/lib/github_changelog_generator/octo_fetcher.rb`.) - Make sure you have a GitHub API access token. You can generate one from [tokens](https://github.com/settings/tokens) -* Generate the changelog: +Run the following command to generate a changelog for this release: + ```bash # is the previous version. # is the new release you have made. -github_changelog_generator -u operator-framework -p operator-lifecycle-manager --since-tag= \ - --token= --future-release= --pr-label="**Other changes:**" -b CHANGELOG.md +GITHUB_TOKEN="" +github_changelog_generator \ + -u operator-framework \ + -p operator-lifecycle-manager \ + --token=${GITHUB_TOKEN} \ + --since-tag= \ + --future-release= \ + --pr-label="**Other changes:**" \ + -b CHANGELOG.md ``` -* Open a new PR with the changelog. + +**Note**: You may run into API rate limiting when attempting to generate the changelog. + +By default, all open and closed issues will be queried when running the above command, which can lead to API rate limiting. Lower the number of issues that will be queried by specifying the `--max-issues` flag (e.g. `--max-issues=100`) and re-run the above command with that flag provided. + +Ensure the `CHANGELOG.md` has been modified locally and open a PR with those generated changes. ## Step 7: Create a New GitHub Release diff --git a/staging/operator-lifecycle-manager/pkg/controller/operators/olm/operator.go b/staging/operator-lifecycle-manager/pkg/controller/operators/olm/operator.go index 94d3339f50..b71abe2115 100644 --- a/staging/operator-lifecycle-manager/pkg/controller/operators/olm/operator.go +++ b/staging/operator-lifecycle-manager/pkg/controller/operators/olm/operator.go @@ -38,6 +38,7 @@ import ( "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/operators/internal/pruning" "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/operators/olm/overrides" "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver" + resolvercache "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/cache" "github.com/operator-framework/operator-lifecycle-manager/pkg/lib/clients" csvutility "github.com/operator-framework/operator-lifecycle-manager/pkg/lib/csv" "github.com/operator-framework/operator-lifecycle-manager/pkg/lib/event" @@ -1364,7 +1365,7 @@ func (a *Operator) transitionCSVState(in v1alpha1.ClusterServiceVersion) (out *v out = in.DeepCopy() now := a.now() - operatorSurface, err := resolver.NewOperatorFromV1Alpha1CSV(out) + operatorSurface, err := resolvercache.NewOperatorFromV1Alpha1CSV(out) if err != nil { // If the resolver is unable to retrieve the operator info from the CSV the CSV requires changes, a syncError should not be returned. logger.WithError(err).Warn("Unable to retrieve operator information from CSV") @@ -1428,7 +1429,7 @@ func (a *Operator) transitionCSVState(in v1alpha1.ClusterServiceVersion) (out *v groupSurface := resolver.NewOperatorGroup(operatorGroup) otherGroupSurfaces := resolver.NewOperatorGroupSurfaces(otherGroups...) - providedAPIs := operatorSurface.ProvidedAPIs().StripPlural() + providedAPIs := operatorSurface.GetProvidedAPIs().StripPlural() switch result := a.apiReconciler.Reconcile(providedAPIs, groupSurface, otherGroupSurfaces...); { case operatorGroup.Spec.StaticProvidedAPIs && (result == resolver.AddAPIs || result == resolver.RemoveAPIs): diff --git a/staging/operator-lifecycle-manager/pkg/controller/operators/olm/operator_test.go b/staging/operator-lifecycle-manager/pkg/controller/operators/olm/operator_test.go index 5f6eae41a4..f7d9abbded 100644 --- a/staging/operator-lifecycle-manager/pkg/controller/operators/olm/operator_test.go +++ b/staging/operator-lifecycle-manager/pkg/controller/operators/olm/operator_test.go @@ -55,6 +55,7 @@ import ( olmerrors "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/errors" "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/install" "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver" + resolvercache "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/cache" "github.com/operator-framework/operator-lifecycle-manager/pkg/fakes" "github.com/operator-framework/operator-lifecycle-manager/pkg/lib/clientfake" csvutility "github.com/operator-framework/operator-lifecycle-manager/pkg/lib/csv" @@ -872,7 +873,7 @@ func TestTransitionCSV(t *testing.T) { logrus.SetLevel(logrus.DebugLevel) namespace := "ns" - apiHash, err := resolver.APIKeyToGVKHash(opregistry.APIKey{Group: "g1", Version: "v1", Kind: "c1"}) + apiHash, err := resolvercache.APIKeyToGVKHash(opregistry.APIKey{Group: "g1", Version: "v1", Kind: "c1"}) require.NoError(t, err) defaultOperatorGroup := &v1.OperatorGroup{ @@ -3693,7 +3694,7 @@ func TestSyncOperatorGroups(t *testing.T) { v1alpha1.CSVPhaseNone, ), labels.Set{resolver.APILabelKeyPrefix + "9f4c46c37bdff8d0": "provided"}) - operatorCSV.Spec.InstallStrategy.StrategySpec.DeploymentSpecs[0].Spec.Template.Spec.Containers[0].Env = []corev1.EnvVar{corev1.EnvVar{ + operatorCSV.Spec.InstallStrategy.StrategySpec.DeploymentSpecs[0].Spec.Template.Spec.Containers[0].Env = []corev1.EnvVar{{ Name: "OPERATOR_CONDITION_NAME", Value: operatorCSV.GetName(), }} @@ -4631,7 +4632,7 @@ func TestOperatorGroupConditions(t *testing.T) { }, expectError: true, expectedConditions: []metav1.Condition{ - metav1.Condition{ + { Type: v1.OperatorGroupServiceAccountCondition, Status: metav1.ConditionTrue, Reason: v1.OperatorGroupServiceAccountReason, @@ -4674,7 +4675,7 @@ func TestOperatorGroupConditions(t *testing.T) { }, expectError: true, expectedConditions: []metav1.Condition{ - metav1.Condition{ + { Type: v1.MutlipleOperatorGroupCondition, Status: metav1.ConditionTrue, Reason: v1.MultipleOperatorGroupsReason, diff --git a/staging/operator-lifecycle-manager/pkg/controller/operators/olm/operatorgroup.go b/staging/operator-lifecycle-manager/pkg/controller/operators/olm/operatorgroup.go index eb9139d89a..8c28ef0d1e 100644 --- a/staging/operator-lifecycle-manager/pkg/controller/operators/olm/operatorgroup.go +++ b/staging/operator-lifecycle-manager/pkg/controller/operators/olm/operatorgroup.go @@ -22,6 +22,7 @@ import ( "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/install" "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/operators/decorators" "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver" + "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/cache" hashutil "github.com/operator-framework/operator-lifecycle-manager/pkg/lib/kubernetes/pkg/util/hash" "github.com/operator-framework/operator-lifecycle-manager/pkg/lib/ownerutil" opregistry "github.com/operator-framework/operator-registry/pkg/registry" @@ -46,7 +47,7 @@ var ( ) func aggregationLabelFromAPIKey(k opregistry.APIKey, suffix string) (string, error) { - hash, err := resolver.APIKeyToGVKHash(k) + hash, err := cache.APIKeyToGVKHash(k) if err != nil { return "", err } @@ -188,7 +189,7 @@ func (a *Operator) syncOperatorGroups(obj interface{}) error { groupSurface := resolver.NewOperatorGroup(op) groupProvidedAPIs := groupSurface.ProvidedAPIs() providedAPIsForCSVs := a.providedAPIsFromCSVs(op, logger) - providedAPIsForGroup := make(resolver.APISet) + providedAPIsForGroup := make(cache.APISet) for api := range providedAPIsForCSVs { providedAPIsForGroup[api] = struct{}{} } @@ -309,26 +310,26 @@ func (a *Operator) providedAPIsFromCSVs(group *v1.OperatorGroup, logger *logrus. // TODO: Throw out CSVs that aren't members of the group due to group related failures? // Union the providedAPIsFromCSVs from existing members of the group - operatorSurface, err := resolver.NewOperatorFromV1Alpha1CSV(csv) + operatorSurface, err := cache.NewOperatorFromV1Alpha1CSV(csv) if err != nil { logger.WithError(err).Warn("could not create OperatorSurface from csv") continue } - for providedAPI := range operatorSurface.ProvidedAPIs().StripPlural() { + for providedAPI := range operatorSurface.GetProvidedAPIs().StripPlural() { providedAPIsFromCSVs[providedAPI] = csv } } return providedAPIsFromCSVs } -func (a *Operator) pruneProvidedAPIs(group *v1.OperatorGroup, groupProvidedAPIs resolver.APISet, providedAPIsFromCSVs map[opregistry.APIKey]*v1alpha1.ClusterServiceVersion, logger *logrus.Entry) { +func (a *Operator) pruneProvidedAPIs(group *v1.OperatorGroup, groupProvidedAPIs cache.APISet, providedAPIsFromCSVs map[opregistry.APIKey]*v1alpha1.ClusterServiceVersion, logger *logrus.Entry) { // Don't prune providedAPIsFromCSVs if static if group.Spec.StaticProvidedAPIs { a.logger.Debug("group has static provided apis. skipping provided api pruning") return } - intersection := make(resolver.APISet) + intersection := make(cache.APISet) for api := range providedAPIsFromCSVs { if _, ok := groupProvidedAPIs[api]; ok { intersection[api] = struct{}{} @@ -530,8 +531,14 @@ func (a *Operator) ensureSingletonRBAC(operatorNamespace string, csv *v1alpha1.C Resources: []string{"namespaces"}, }), } - if _, err := a.opClient.CreateClusterRole(clusterRole); err != nil { - return err + // TODO: this should do something smarter if the cluster role already exists + if cr, err := a.opClient.CreateClusterRole(clusterRole); err != nil { + // if the CR already exists, but the label is correct, the cache is just behind + if k8serrors.IsAlreadyExists(err) && ownerutil.IsOwnedByLabel(cr, csv) { + continue + } else { + return err + } } a.logger.Debug("created cluster role") } @@ -564,8 +571,14 @@ func (a *Operator) ensureSingletonRBAC(operatorNamespace string, csv *v1alpha1.C Name: r.RoleRef.Name, }, } - if _, err := a.opClient.CreateClusterRoleBinding(clusterRoleBinding); err != nil { - return err + // TODO: this should do something smarter if the cluster role binding already exists + if crb, err := a.opClient.CreateClusterRoleBinding(clusterRoleBinding); err != nil { + // if the CR already exists, but the label is correct, the cache is just behind + if k8serrors.IsAlreadyExists(err) && ownerutil.IsOwnedByLabel(crb, csv) { + continue + } else { + return err + } } } } @@ -966,7 +979,7 @@ func (a *Operator) updateNamespaceList(op *v1.OperatorGroup) ([]string, error) { return namespaceList, nil } -func (a *Operator) ensureOpGroupClusterRole(op *v1.OperatorGroup, suffix string, apis resolver.APISet) error { +func (a *Operator) ensureOpGroupClusterRole(op *v1.OperatorGroup, suffix string, apis cache.APISet) error { clusterRole := &rbacv1.ClusterRole{ ObjectMeta: metav1.ObjectMeta{ Name: strings.Join([]string{op.GetName(), suffix}, "-"), @@ -1017,7 +1030,7 @@ func (a *Operator) ensureOpGroupClusterRole(op *v1.OperatorGroup, suffix string, return nil } -func (a *Operator) ensureOpGroupClusterRoles(op *v1.OperatorGroup, apis resolver.APISet) error { +func (a *Operator) ensureOpGroupClusterRoles(op *v1.OperatorGroup, apis cache.APISet) error { for _, suffix := range Suffices { if err := a.ensureOpGroupClusterRole(op, suffix, apis); err != nil { return err @@ -1026,7 +1039,7 @@ func (a *Operator) ensureOpGroupClusterRoles(op *v1.OperatorGroup, apis resolver return nil } -func (a *Operator) findCSVsThatProvideAnyOf(provide resolver.APISet) ([]*v1alpha1.ClusterServiceVersion, error) { +func (a *Operator) findCSVsThatProvideAnyOf(provide cache.APISet) ([]*v1alpha1.ClusterServiceVersion, error) { csvs, err := a.lister.OperatorsV1alpha1().ClusterServiceVersionLister().ClusterServiceVersions(metav1.NamespaceAll).List(labels.Everything()) if err != nil { return nil, err @@ -1039,12 +1052,12 @@ func (a *Operator) findCSVsThatProvideAnyOf(provide resolver.APISet) ([]*v1alpha continue } - operatorSurface, err := resolver.NewOperatorFromV1Alpha1CSV(csv) + operatorSurface, err := cache.NewOperatorFromV1Alpha1CSV(csv) if err != nil { continue } - if len(operatorSurface.ProvidedAPIs().StripPlural().Intersection(provide)) > 0 { + if len(operatorSurface.GetProvidedAPIs().StripPlural().Intersection(provide)) > 0 { providers = append(providers, csv) } } diff --git a/staging/operator-lifecycle-manager/pkg/controller/registry/grpc/source_test.go b/staging/operator-lifecycle-manager/pkg/controller/registry/grpc/source_test.go index c604d77346..ed9d957f64 100644 --- a/staging/operator-lifecycle-manager/pkg/controller/registry/grpc/source_test.go +++ b/staging/operator-lifecycle-manager/pkg/controller/registry/grpc/source_test.go @@ -22,8 +22,8 @@ import ( "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry" ) -func server(store opregistry.Query, port int) (func(), func()) { - lis, err := net.Listen("tcp", fmt.Sprintf("localhost:%d", port)) +func server(store opregistry.Query) (func(), string, func()) { + lis, err := net.Listen("tcp", fmt.Sprintf("localhost:")) if err != nil { logrus.Fatalf("failed to listen: %v", err) } @@ -41,7 +41,7 @@ func server(store opregistry.Query, port int) (func(), func()) { s.Stop() } - return serve, stop + return serve, lis.Addr().String(), stop } type FakeSourceSyncer struct { @@ -82,14 +82,14 @@ func TestConnectionEvents(t *testing.T) { test := func(tt testcase) func(t *testing.T) { return func(t *testing.T) { - // start server for each catalog totalEvents := 0 - port := 50050 - for _, events := range tt.expectedHistory { + addresses := map[registry.CatalogKey]string{} + + for catalog, events := range tt.expectedHistory { totalEvents += len(events) - port += 1 - serve, stop := server(&fakes.FakeQuery{}, port) + serve, address, stop := server(&fakes.FakeQuery{}) + addresses[catalog] = address go serve() defer stop() } @@ -102,10 +102,8 @@ func TestConnectionEvents(t *testing.T) { sources.Start(ctx) // add source for each catalog - port = 50050 - for catalog := range tt.expectedHistory { - port += 1 - _, err := sources.Add(catalog, fmt.Sprintf("localhost:%d", port)) + for catalog, address := range addresses { + _, err := sources.Add(catalog, address) require.NoError(t, err) } diff --git a/staging/operator-lifecycle-manager/pkg/controller/registry/resolver/cache.go b/staging/operator-lifecycle-manager/pkg/controller/registry/resolver/cache/cache.go similarity index 78% rename from staging/operator-lifecycle-manager/pkg/controller/registry/resolver/cache.go rename to staging/operator-lifecycle-manager/pkg/controller/registry/resolver/cache/cache.go index 68a060f9c7..c26f30d434 100644 --- a/staging/operator-lifecycle-manager/pkg/controller/registry/resolver/cache.go +++ b/staging/operator-lifecycle-manager/pkg/controller/registry/resolver/cache/cache.go @@ -1,4 +1,4 @@ -package resolver +package cache import ( "context" @@ -8,6 +8,8 @@ import ( "sync" "time" + "k8s.io/apimachinery/pkg/util/errors" + "github.com/sirupsen/logrus" "github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/listers/operators/v1alpha1" @@ -74,9 +76,22 @@ func NewOperatorCache(rcp RegistryClientProvider, log logrus.FieldLogger, catsrc } type NamespacedOperatorCache struct { - namespaces []string + Namespaces []string existing *registry.CatalogKey - snapshots map[registry.CatalogKey]*CatalogSnapshot + Snapshots map[registry.CatalogKey]*CatalogSnapshot +} + +func (c *NamespacedOperatorCache) Error() error { + var errs []error + for key, snapshot := range c.Snapshots { + snapshot.m.Lock() + err := snapshot.err + snapshot.m.Unlock() + if err != nil { + errs = append(errs, fmt.Errorf("error using catalog %s (in namespace %s): %w", key.Name, key.Namespace, err)) + } + } + return errors.NewAggregate(errs) } func (c *OperatorCache) Expire(catalog registry.CatalogKey) { @@ -98,8 +113,8 @@ func (c *OperatorCache) Namespaced(namespaces ...string) MultiCatalogOperatorFin clients := c.rcp.ClientsForNamespaces(namespaces...) result := NamespacedOperatorCache{ - namespaces: namespaces, - snapshots: make(map[registry.CatalogKey]*CatalogSnapshot), + Namespaces: namespaces, + Snapshots: make(map[registry.CatalogKey]*CatalogSnapshot), } var misses []registry.CatalogKey @@ -112,8 +127,8 @@ func (c *OperatorCache) Namespaced(namespaces ...string) MultiCatalogOperatorFin func() { snapshot.m.RLock() defer snapshot.m.RUnlock() - if !snapshot.Expired(now) && snapshot.operators != nil && len(snapshot.operators) > 0 { - result.snapshots[key] = snapshot + if !snapshot.Expired(now) && snapshot.Operators != nil && len(snapshot.Operators) > 0 { + result.Snapshots[key] = snapshot } else { misses = append(misses, key) } @@ -147,8 +162,8 @@ func (c *OperatorCache) Namespaced(namespaces ...string) MultiCatalogOperatorFin // Check for any snapshots that were populated while waiting to acquire the lock. var found int for i := range misses { - if snapshot, ok := c.snapshots[misses[i]]; ok && !snapshot.Expired(now) && snapshot.operators != nil && len(snapshot.operators) > 0 { - result.snapshots[misses[i]] = snapshot + if snapshot, ok := c.snapshots[misses[i]]; ok && !snapshot.Expired(now) && snapshot.Operators != nil && len(snapshot.Operators) > 0 { + result.Snapshots[misses[i]] = snapshot misses[found], misses[i] = misses[i], misses[found] found++ } @@ -167,14 +182,14 @@ func (c *OperatorCache) Namespaced(namespaces ...string) MultiCatalogOperatorFin s := CatalogSnapshot{ logger: c.logger.WithField("catalog", miss), - key: miss, + Key: miss, expiry: now.Add(c.ttl), pop: cancel, - priority: catalogSourcePriority(catsrcPriority), + Priority: catalogSourcePriority(catsrcPriority), } s.m.Lock() c.snapshots[miss] = &s - result.snapshots[miss] = &s + result.Snapshots[miss] = &s go c.populate(ctx, &s, clients[miss]) } @@ -183,6 +198,12 @@ func (c *OperatorCache) Namespaced(namespaces ...string) MultiCatalogOperatorFin func (c *OperatorCache) populate(ctx context.Context, snapshot *CatalogSnapshot, registry client.Interface) { defer snapshot.m.Unlock() + defer func() { + // Don't cache an errorred snapshot. + if snapshot.err != nil { + snapshot.expiry = time.Time{} + } + }() c.sem <- struct{}{} defer func() { <-c.sem }() @@ -195,9 +216,10 @@ func (c *OperatorCache) populate(ctx context.Context, snapshot *CatalogSnapshot, it, err := registry.ListBundles(ctx) if err != nil { snapshot.logger.Errorf("failed to list bundles: %s", err.Error()) + snapshot.err = err return } - c.logger.WithField("catalog", snapshot.key.String()).Debug("updating cache") + c.logger.WithField("catalog", snapshot.Key.String()).Debug("updating cache") var operators []*Operator for b := it.Next(); b != nil; b = it.Next() { defaultChannel, ok := defaultChannels[b.PackageName] @@ -210,25 +232,26 @@ func (c *OperatorCache) populate(ctx context.Context, snapshot *CatalogSnapshot, defaultChannel = p.DefaultChannelName } } - o, err := NewOperatorFromBundle(b, "", snapshot.key, defaultChannel) + o, err := NewOperatorFromBundle(b, "", snapshot.Key, defaultChannel) if err != nil { snapshot.logger.Warnf("failed to construct operator from bundle, continuing: %v", err) continue } - o.providedAPIs = o.ProvidedAPIs().StripPlural() - o.requiredAPIs = o.RequiredAPIs().StripPlural() - o.replaces = b.Replaces - ensurePackageProperty(o, b.PackageName, b.Version) + o.ProvidedAPIs = o.ProvidedAPIs.StripPlural() + o.RequiredAPIs = o.RequiredAPIs.StripPlural() + o.Replaces = b.Replaces + EnsurePackageProperty(o, b.PackageName, b.Version) operators = append(operators, o) } if err := it.Error(); err != nil { snapshot.logger.Warnf("error encountered while listing bundles: %s", err.Error()) + snapshot.err = err } - snapshot.operators = operators + snapshot.Operators = operators } -func ensurePackageProperty(o *Operator, name, version string) { - for _, p := range o.Properties() { +func EnsurePackageProperty(o *Operator, name, version string) { + for _, p := range o.Properties { if p.Type == opregistry.PackageType { return } @@ -241,7 +264,7 @@ func ensurePackageProperty(o *Operator, name, version string) { if err != nil { return } - o.properties = append(o.properties, &api.Property{ + o.Properties = append(o.Properties, &api.Property{ Type: opregistry.PackageType, Value: string(bytes), }) @@ -252,7 +275,7 @@ func (c *NamespacedOperatorCache) Catalog(k registry.CatalogKey) OperatorFinder if k.Empty() { return c } - if snapshot, ok := c.snapshots[k]; ok { + if snapshot, ok := c.Snapshots[k]; ok { return snapshot } return EmptyOperatorFinder{} @@ -263,7 +286,7 @@ func (c *NamespacedOperatorCache) FindPreferred(preferred *registry.CatalogKey, if preferred != nil && preferred.Empty() { preferred = nil } - sorted := NewSortableSnapshots(c.existing, preferred, c.namespaces, c.snapshots) + sorted := NewSortableSnapshots(c.existing, preferred, c.Namespaces, c.Snapshots) sort.Sort(sorted) for _, snapshot := range sorted.snapshots { result = append(result, snapshot.Find(p...)...) @@ -273,11 +296,11 @@ func (c *NamespacedOperatorCache) FindPreferred(preferred *registry.CatalogKey, func (c *NamespacedOperatorCache) WithExistingOperators(snapshot *CatalogSnapshot) MultiCatalogOperatorFinder { o := &NamespacedOperatorCache{ - namespaces: c.namespaces, - existing: &snapshot.key, - snapshots: c.snapshots, + Namespaces: c.Namespaces, + existing: &snapshot.Key, + Snapshots: c.Snapshots, } - o.snapshots[snapshot.key] = snapshot + o.Snapshots[snapshot.Key] = snapshot return o } @@ -287,12 +310,13 @@ func (c *NamespacedOperatorCache) Find(p ...OperatorPredicate) []*Operator { type CatalogSnapshot struct { logger logrus.FieldLogger - key registry.CatalogKey + Key registry.CatalogKey expiry time.Time - operators []*Operator + Operators []*Operator m sync.RWMutex pop context.CancelFunc - priority catalogSourcePriority + Priority catalogSourcePriority + err error } func (s *CatalogSnapshot) Cancel() { @@ -308,8 +332,8 @@ func (s *CatalogSnapshot) Expired(at time.Time) bool { func NewRunningOperatorSnapshot(logger logrus.FieldLogger, key registry.CatalogKey, o []*Operator) *CatalogSnapshot { return &CatalogSnapshot{ logger: logger, - key: key, - operators: o, + Key: key, + Operators: o, } } @@ -348,37 +372,37 @@ func (s SortableSnapshots) Len() int { func (s SortableSnapshots) Less(i, j int) bool { // existing operators are preferred over catalog operators if s.existing != nil && - s.snapshots[i].key.Name == s.existing.Name && - s.snapshots[i].key.Namespace == s.existing.Namespace { + s.snapshots[i].Key.Name == s.existing.Name && + s.snapshots[i].Key.Namespace == s.existing.Namespace { return true } if s.existing != nil && - s.snapshots[j].key.Name == s.existing.Name && - s.snapshots[j].key.Namespace == s.existing.Namespace { + s.snapshots[j].Key.Name == s.existing.Name && + s.snapshots[j].Key.Namespace == s.existing.Namespace { return false } // preferred catalog is less than all other catalogs if s.preferred != nil && - s.snapshots[i].key.Name == s.preferred.Name && - s.snapshots[i].key.Namespace == s.preferred.Namespace { + s.snapshots[i].Key.Name == s.preferred.Name && + s.snapshots[i].Key.Namespace == s.preferred.Namespace { return true } if s.preferred != nil && - s.snapshots[j].key.Name == s.preferred.Name && - s.snapshots[j].key.Namespace == s.preferred.Namespace { + s.snapshots[j].Key.Name == s.preferred.Name && + s.snapshots[j].Key.Namespace == s.preferred.Namespace { return false } // the rest are sorted first on priority, namespace and then by name - if s.snapshots[i].priority != s.snapshots[j].priority { - return s.snapshots[i].priority > s.snapshots[j].priority + if s.snapshots[i].Priority != s.snapshots[j].Priority { + return s.snapshots[i].Priority > s.snapshots[j].Priority } - if s.snapshots[i].key.Namespace != s.snapshots[j].key.Namespace { - return s.namespaces[s.snapshots[i].key.Namespace] < s.namespaces[s.snapshots[j].key.Namespace] + if s.snapshots[i].Key.Namespace != s.snapshots[j].Key.Namespace { + return s.namespaces[s.snapshots[i].Key.Namespace] < s.namespaces[s.snapshots[j].Key.Namespace] } - return s.snapshots[i].key.Name < s.snapshots[j].key.Name + return s.snapshots[i].Key.Name < s.snapshots[j].Key.Name } // Swap swaps the elements with indexes i and j. @@ -389,7 +413,7 @@ func (s SortableSnapshots) Swap(i, j int) { func (s *CatalogSnapshot) Find(p ...OperatorPredicate) []*Operator { s.m.RLock() defer s.m.RUnlock() - return Filter(s.operators, p...) + return Filter(s.Operators, p...) } type OperatorFinder interface { @@ -400,6 +424,7 @@ type MultiCatalogOperatorFinder interface { Catalog(registry.CatalogKey) OperatorFinder FindPreferred(*registry.CatalogKey, ...OperatorPredicate) []*Operator WithExistingOperators(*CatalogSnapshot) MultiCatalogOperatorFinder + Error() error OperatorFinder } diff --git a/staging/operator-lifecycle-manager/pkg/controller/registry/resolver/cache_test.go b/staging/operator-lifecycle-manager/pkg/controller/registry/resolver/cache/cache_test.go similarity index 85% rename from staging/operator-lifecycle-manager/pkg/controller/registry/resolver/cache_test.go rename to staging/operator-lifecycle-manager/pkg/controller/registry/resolver/cache/cache_test.go index fccf59dd95..ecb4f14b24 100644 --- a/staging/operator-lifecycle-manager/pkg/controller/registry/resolver/cache_test.go +++ b/staging/operator-lifecycle-manager/pkg/controller/registry/resolver/cache/cache_test.go @@ -1,7 +1,8 @@ -package resolver +package cache import ( "context" + "errors" "fmt" "io" "math/rand" @@ -10,6 +11,7 @@ import ( "time" "github.com/sirupsen/logrus" + "github.com/sirupsen/logrus/hooks/test" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -35,6 +37,8 @@ func (s *BundleStreamStub) Recv() (*api.Bundle, error) { type RegistryClientStub struct { BundleIterator *client.BundleIterator + + ListBundlesError error } func (s *RegistryClientStub) Get() (client.Interface, error) { @@ -58,7 +62,7 @@ func (s *RegistryClientStub) GetBundleThatProvides(ctx context.Context, group, v } func (s *RegistryClientStub) ListBundles(ctx context.Context) (*client.BundleIterator, error) { - return s.BundleIterator, nil + return s.BundleIterator, s.ListBundlesError } func (s *RegistryClientStub) GetPackage(ctx context.Context, packageName string) (*api.Package, error) { @@ -236,9 +240,9 @@ func TestCatalogSnapshotFind(t *testing.T) { return false }), Operators: []*Operator{ - {name: "a"}, - {name: "b"}, - {name: "c"}, + {Name: "a"}, + {Name: "b"}, + {Name: "c"}, }, Expected: nil, }, @@ -256,34 +260,34 @@ func TestCatalogSnapshotFind(t *testing.T) { return true }), Operators: []*Operator{ - {name: "a"}, - {name: "b"}, - {name: "c"}, + {Name: "a"}, + {Name: "b"}, + {Name: "c"}, }, Expected: []*Operator{ - {name: "a"}, - {name: "b"}, - {name: "c"}, + {Name: "a"}, + {Name: "b"}, + {Name: "c"}, }, }, { Name: "some satisfy predicate", Predicate: OperatorPredicateTestFunc(func(o *Operator) bool { - return o.name != "a" + return o.Name != "a" }), Operators: []*Operator{ - {name: "a"}, - {name: "b"}, - {name: "c"}, + {Name: "a"}, + {Name: "b"}, + {Name: "c"}, }, Expected: []*Operator{ - {name: "b"}, - {name: "c"}, + {Name: "b"}, + {Name: "c"}, }, }, } { t.Run(tt.Name, func(t *testing.T) { - s := CatalogSnapshot{operators: tt.Operators} + s := CatalogSnapshot{Operators: tt.Operators} assert.Equal(t, tt.Expected, s.Find(tt.Predicate)) }) } @@ -310,7 +314,7 @@ func TestStripPluralRequiredAndProvidedAPIKeys(t *testing.T) { Kind: "K2", Plural: "ks2", }}, - Properties: apiSetToProperties(map[opregistry.APIKey]struct{}{ + Properties: APISetToProperties(map[opregistry.APIKey]struct{}{ { Group: "g", Version: "v1", @@ -318,7 +322,7 @@ func TestStripPluralRequiredAndProvidedAPIKeys(t *testing.T) { Plural: "ks", }: {}, }, nil, false), - Dependencies: apiSetToDependencies(map[opregistry.APIKey]struct{}{ + Dependencies: APISetToDependencies(map[opregistry.APIKey]struct{}{ { Group: "g2", Version: "v2", @@ -336,6 +340,24 @@ func TestStripPluralRequiredAndProvidedAPIKeys(t *testing.T) { result, err := AtLeast(1, nc.Find(ProvidingAPIPredicate(opregistry.APIKey{Group: "g", Version: "v1", Kind: "K"}))) assert.NoError(t, err) assert.Equal(t, 1, len(result)) - assert.Equal(t, "K.v1.g", result[0].providedAPIs.String()) - assert.Equal(t, "K2.v2.g2", result[0].requiredAPIs.String()) + assert.Equal(t, "K.v1.g", result[0].ProvidedAPIs.String()) + assert.Equal(t, "K2.v2.g2", result[0].RequiredAPIs.String()) +} + +func TestNamespaceOperatorCacheError(t *testing.T) { + rcp := RegistryClientProviderStub{} + catsrcLister := operatorlister.NewLister().OperatorsV1alpha1().CatalogSourceLister() + key := registry.CatalogKey{Namespace: "dummynamespace", Name: "dummyname"} + rcp[key] = &RegistryClientStub{ + ListBundlesError: errors.New("testing"), + } + + logger, _ := test.NewNullLogger() + c := NewOperatorCache(rcp, logger, catsrcLister) + require.EqualError(t, c.Namespaced("dummynamespace").Error(), "error using catalog dummyname (in namespace dummynamespace): testing") + if snapshot, ok := c.snapshots[key]; !ok { + t.Fatalf("cache snapshot not found") + } else { + require.Zero(t, snapshot.expiry) + } } diff --git a/vendor/github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/operators.go b/staging/operator-lifecycle-manager/pkg/controller/registry/resolver/cache/operators.go similarity index 75% rename from vendor/github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/operators.go rename to staging/operator-lifecycle-manager/pkg/controller/registry/resolver/cache/operators.go index 32c38b3f09..7b85124133 100644 --- a/vendor/github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/operators.go +++ b/staging/operator-lifecycle-manager/pkg/controller/registry/resolver/cache/operators.go @@ -1,4 +1,4 @@ -package resolver +package cache import ( "encoding/json" @@ -208,28 +208,28 @@ var ExistingOperator = OperatorSourceInfo{Package: "", Channel: "", StartingCSV: // OperatorSurface describes the API surfaces provided and required by an Operator. type OperatorSurface interface { - ProvidedAPIs() APISet - RequiredAPIs() APISet + GetProvidedAPIs() APISet + GetRequiredAPIs() APISet Identifier() string - Replaces() string - Version() *semver.Version - SourceInfo() *OperatorSourceInfo - Bundle() *api.Bundle + GetReplaces() string + GetVersion() *semver.Version + GetSourceInfo() *OperatorSourceInfo + GetBundle() *api.Bundle Inline() bool - Properties() []*api.Property - Skips() []string + GetProperties() []*api.Property + GetSkips() []string } type Operator struct { - name string - replaces string - providedAPIs APISet - requiredAPIs APISet - version *semver.Version - bundle *api.Bundle - sourceInfo *OperatorSourceInfo - properties []*api.Property - skips []string + Name string + Replaces string + ProvidedAPIs APISet + RequiredAPIs APISet + Version *semver.Version + Bundle *api.Bundle + SourceInfo *OperatorSourceInfo + Properties []*api.Property + Skips []string } var _ OperatorSurface = &Operator{} @@ -265,13 +265,13 @@ func NewOperatorFromBundle(bundle *api.Bundle, startingCSV string, sourceKey reg } } if len(bundle.Dependencies) > 0 { - ps, err := legacyDependenciesToProperties(bundle.Dependencies) + ps, err := LegacyDependenciesToProperties(bundle.Dependencies) if err != nil { return nil, fmt.Errorf("failed to translate legacy dependencies to properties: %w", err) } properties = append(properties, ps...) } else { - ps, err := requiredAPIsToProperties(required) + ps, err := RequiredAPIsToProperties(required) if err != nil { return nil, err } @@ -279,15 +279,15 @@ func NewOperatorFromBundle(bundle *api.Bundle, startingCSV string, sourceKey reg } o := &Operator{ - name: bundle.CsvName, - replaces: bundle.Replaces, - version: version, - providedAPIs: provided, - requiredAPIs: required, - bundle: bundle, - sourceInfo: sourceInfo, - properties: properties, - skips: bundle.Skips, + Name: bundle.CsvName, + Replaces: bundle.Replaces, + Version: version, + ProvidedAPIs: provided, + RequiredAPIs: required, + Bundle: bundle, + SourceInfo: sourceInfo, + Properties: properties, + Skips: bundle.Skips, } if !o.Inline() { @@ -331,91 +331,83 @@ func NewOperatorFromV1Alpha1CSV(csv *v1alpha1.ClusterServiceVersion) (*Operator, if err != nil { return nil, err } - dependencies, err := requiredAPIsToProperties(requiredAPIs) + dependencies, err := RequiredAPIsToProperties(requiredAPIs) if err != nil { return nil, err } properties = append(properties, dependencies...) return &Operator{ - name: csv.GetName(), - version: &csv.Spec.Version.Version, - providedAPIs: providedAPIs, - requiredAPIs: requiredAPIs, - sourceInfo: &ExistingOperator, - properties: properties, + Name: csv.GetName(), + Version: &csv.Spec.Version.Version, + ProvidedAPIs: providedAPIs, + RequiredAPIs: requiredAPIs, + SourceInfo: &ExistingOperator, + Properties: properties, }, nil } -func (o *Operator) Name() string { - return o.name +func (o *Operator) GetProvidedAPIs() APISet { + return o.ProvidedAPIs } -func (o *Operator) ProvidedAPIs() APISet { - return o.providedAPIs -} - -func (o *Operator) RequiredAPIs() APISet { - return o.requiredAPIs +func (o *Operator) GetRequiredAPIs() APISet { + return o.RequiredAPIs } func (o *Operator) Identifier() string { - return o.name + return o.Name } -func (o *Operator) Replaces() string { - return o.replaces +func (o *Operator) GetReplaces() string { + return o.Replaces } -func (o *Operator) Skips() []string { - return o.skips -} - -func (o *Operator) SetReplaces(replacing string) { - o.replaces = replacing +func (o *Operator) GetSkips() []string { + return o.Skips } func (o *Operator) Package() string { - if o.bundle != nil { - return o.bundle.PackageName + if o.Bundle != nil { + return o.Bundle.PackageName } return "" } func (o *Operator) Channel() string { - if o.bundle != nil { - return o.bundle.ChannelName + if o.Bundle != nil { + return o.Bundle.ChannelName } return "" } -func (o *Operator) SourceInfo() *OperatorSourceInfo { - return o.sourceInfo +func (o *Operator) GetSourceInfo() *OperatorSourceInfo { + return o.SourceInfo } -func (o *Operator) Bundle() *api.Bundle { - return o.bundle +func (o *Operator) GetBundle() *api.Bundle { + return o.Bundle } -func (o *Operator) Version() *semver.Version { - return o.version +func (o *Operator) GetVersion() *semver.Version { + return o.Version } func (o *Operator) SemverRange() (semver.Range, error) { - return semver.ParseRange(o.Bundle().SkipRange) + return semver.ParseRange(o.Bundle.SkipRange) } func (o *Operator) Inline() bool { - return o.bundle != nil && o.bundle.GetBundlePath() == "" + return o.Bundle != nil && o.Bundle.GetBundlePath() == "" } -func (o *Operator) Properties() []*api.Property { - return o.properties +func (o *Operator) GetProperties() []*api.Property { + return o.Properties } func (o *Operator) DependencyPredicates() (predicates []OperatorPredicate, err error) { predicates = make([]OperatorPredicate, 0) - for _, property := range o.Properties() { + for _, property := range o.Properties { predicate, err := PredicateForProperty(property) if err != nil { return nil, err @@ -428,7 +420,7 @@ func (o *Operator) DependencyPredicates() (predicates []OperatorPredicate, err e return } -func requiredAPIsToProperties(apis APISet) (out []*api.Property, err error) { +func RequiredAPIsToProperties(apis APISet) (out []*api.Property, err error) { if len(apis) == 0 { return } @@ -479,7 +471,7 @@ func providedAPIsToProperties(apis APISet) (out []*api.Property, err error) { return nil, nil } -func legacyDependenciesToProperties(dependencies []*api.Dependency) ([]*api.Property, error) { +func LegacyDependenciesToProperties(dependencies []*api.Dependency) ([]*api.Property, error) { var result []*api.Property for _, dependency := range dependencies { switch dependency.Type { @@ -538,3 +530,88 @@ func legacyDependenciesToProperties(dependencies []*api.Dependency) ([]*api.Prop } return result, nil } + +func APISetToProperties(crds, apis APISet, deprecated bool) (out []*api.Property) { + out = make([]*api.Property, 0) + for a := range crds { + val, err := json.Marshal(opregistry.GVKProperty{ + Group: a.Group, + Kind: a.Kind, + Version: a.Version, + }) + if err != nil { + panic(err) + } + out = append(out, &api.Property{ + Type: opregistry.GVKType, + Value: string(val), + }) + } + for a := range apis { + val, err := json.Marshal(opregistry.GVKProperty{ + Group: a.Group, + Kind: a.Kind, + Version: a.Version, + }) + if err != nil { + panic(err) + } + out = append(out, &api.Property{ + Type: opregistry.GVKType, + Value: string(val), + }) + } + if deprecated { + val, err := json.Marshal(opregistry.DeprecatedProperty{}) + if err != nil { + panic(err) + } + out = append(out, &api.Property{ + Type: opregistry.DeprecatedType, + Value: string(val), + }) + } + if len(out) == 0 { + return nil + } + return +} + +func APISetToDependencies(crds, apis APISet) (out []*api.Dependency) { + if len(crds)+len(apis) == 0 { + return nil + } + out = make([]*api.Dependency, 0) + for a := range crds { + val, err := json.Marshal(opregistry.GVKDependency{ + Group: a.Group, + Kind: a.Kind, + Version: a.Version, + }) + if err != nil { + panic(err) + } + out = append(out, &api.Dependency{ + Type: opregistry.GVKType, + Value: string(val), + }) + } + for a := range apis { + val, err := json.Marshal(opregistry.GVKDependency{ + Group: a.Group, + Kind: a.Kind, + Version: a.Version, + }) + if err != nil { + panic(err) + } + out = append(out, &api.Dependency{ + Type: opregistry.GVKType, + Value: string(val), + }) + } + if len(out) == 0 { + return nil + } + return +} diff --git a/staging/operator-lifecycle-manager/pkg/controller/registry/resolver/operators_test.go b/staging/operator-lifecycle-manager/pkg/controller/registry/resolver/cache/operators_test.go similarity index 92% rename from staging/operator-lifecycle-manager/pkg/controller/registry/resolver/operators_test.go rename to staging/operator-lifecycle-manager/pkg/controller/registry/resolver/cache/operators_test.go index eb295deec4..5e08f8b089 100644 --- a/staging/operator-lifecycle-manager/pkg/controller/registry/resolver/operators_test.go +++ b/staging/operator-lifecycle-manager/pkg/controller/registry/resolver/cache/operators_test.go @@ -1,4 +1,4 @@ -package resolver +package cache import ( "encoding/json" @@ -737,7 +737,7 @@ func TestAPIMultiOwnerSet_PopAPIKey(t *testing.T) { name: "OneApi/OneOwner", s: map[opregistry.APIKey]OperatorSet{ {Group: "g", Version: "v", Kind: "k", Plural: "p"}: map[string]OperatorSurface{ - "owner1": &Operator{name: "op1"}, + "owner1": &Operator{Name: "op1"}, }, }, }, @@ -745,8 +745,8 @@ func TestAPIMultiOwnerSet_PopAPIKey(t *testing.T) { name: "OneApi/MultiOwner", s: map[opregistry.APIKey]OperatorSet{ {Group: "g", Version: "v", Kind: "k", Plural: "p"}: map[string]OperatorSurface{ - "owner1": &Operator{name: "op1"}, - "owner2": &Operator{name: "op2"}, + "owner1": &Operator{Name: "op1"}, + "owner2": &Operator{Name: "op2"}, }, }, }, @@ -754,12 +754,12 @@ func TestAPIMultiOwnerSet_PopAPIKey(t *testing.T) { name: "MultipleApi/MultiOwner", s: map[opregistry.APIKey]OperatorSet{ {Group: "g", Version: "v", Kind: "k", Plural: "p"}: map[string]OperatorSurface{ - "owner1": &Operator{name: "op1"}, - "owner2": &Operator{name: "op2"}, + "owner1": &Operator{Name: "op1"}, + "owner2": &Operator{Name: "op2"}, }, {Group: "g2", Version: "v2", Kind: "k2", Plural: "p2"}: map[string]OperatorSurface{ - "owner1": &Operator{name: "op1"}, - "owner2": &Operator{name: "op2"}, + "owner1": &Operator{Name: "op1"}, + "owner2": &Operator{Name: "op2"}, }, }, }, @@ -797,41 +797,41 @@ func TestAPIMultiOwnerSet_PopAPIRequirers(t *testing.T) { name: "OneApi/OneOwner", s: map[opregistry.APIKey]OperatorSet{ {Group: "g", Version: "v", Kind: "k", Plural: "p"}: map[string]OperatorSurface{ - "owner1": &Operator{name: "op1"}, + "owner1": &Operator{Name: "op1"}, }, }, want: map[string]OperatorSurface{ - "owner1": &Operator{name: "op1"}, + "owner1": &Operator{Name: "op1"}, }, }, { name: "OneApi/MultiOwner", s: map[opregistry.APIKey]OperatorSet{ {Group: "g", Version: "v", Kind: "k", Plural: "p"}: map[string]OperatorSurface{ - "owner1": &Operator{name: "op1"}, - "owner2": &Operator{name: "op2"}, + "owner1": &Operator{Name: "op1"}, + "owner2": &Operator{Name: "op2"}, }, }, want: map[string]OperatorSurface{ - "owner1": &Operator{name: "op1"}, - "owner2": &Operator{name: "op2"}, + "owner1": &Operator{Name: "op1"}, + "owner2": &Operator{Name: "op2"}, }, }, { name: "MultipleApi/MultiOwner", s: map[opregistry.APIKey]OperatorSet{ {Group: "g", Version: "v", Kind: "k", Plural: "p"}: map[string]OperatorSurface{ - "owner1": &Operator{name: "op1"}, - "owner2": &Operator{name: "op2"}, + "owner1": &Operator{Name: "op1"}, + "owner2": &Operator{Name: "op2"}, }, {Group: "g2", Version: "v2", Kind: "k2", Plural: "p2"}: map[string]OperatorSurface{ - "owner1": &Operator{name: "op1"}, - "owner2": &Operator{name: "op2"}, + "owner1": &Operator{Name: "op1"}, + "owner2": &Operator{Name: "op2"}, }, }, want: map[string]OperatorSurface{ - "owner1": &Operator{name: "op1"}, - "owner2": &Operator{name: "op2"}, + "owner1": &Operator{Name: "op1"}, + "owner2": &Operator{Name: "op2"}, }, }, } @@ -1077,12 +1077,12 @@ func TestNewOperatorFromBundle(t *testing.T) { sourceKey: registry.CatalogKey{Name: "source", Namespace: "testNamespace"}, }, want: &Operator{ - name: "testBundle", - version: &version.Version, - providedAPIs: EmptyAPISet(), - requiredAPIs: EmptyAPISet(), - bundle: bundleNoAPIs, - sourceInfo: &OperatorSourceInfo{ + Name: "testBundle", + Version: &version.Version, + ProvidedAPIs: EmptyAPISet(), + RequiredAPIs: EmptyAPISet(), + Bundle: bundleNoAPIs, + SourceInfo: &OperatorSourceInfo{ Package: "testPackage", Channel: "testChannel", Catalog: registry.CatalogKey{Name: "source", Namespace: "testNamespace"}, @@ -1096,9 +1096,9 @@ func TestNewOperatorFromBundle(t *testing.T) { sourceKey: registry.CatalogKey{Name: "source", Namespace: "testNamespace"}, }, want: &Operator{ - name: "testBundle", - version: &version.Version, - providedAPIs: APISet{ + Name: "testBundle", + Version: &version.Version, + ProvidedAPIs: APISet{ opregistry.APIKey{ Group: "crd.group.com", Version: "v1", @@ -1112,7 +1112,7 @@ func TestNewOperatorFromBundle(t *testing.T) { Plural: "ownedapis", }: struct{}{}, }, - requiredAPIs: APISet{ + RequiredAPIs: APISet{ opregistry.APIKey{ Group: "crd.group.com", Version: "v1", @@ -1126,7 +1126,7 @@ func TestNewOperatorFromBundle(t *testing.T) { Plural: "requiredapis", }: struct{}{}, }, - properties: []*api.Property{ + Properties: []*api.Property{ { Type: "olm.gvk", Value: "{\"group\":\"crd.group.com\",\"kind\":\"OwnedCRD\",\"version\":\"v1\"}", @@ -1144,8 +1144,8 @@ func TestNewOperatorFromBundle(t *testing.T) { Value: "{\"group\":\"apis.group.com\",\"kind\":\"RequiredAPI\",\"version\":\"v1\"}", }, }, - bundle: bundleWithAPIs, - sourceInfo: &OperatorSourceInfo{ + Bundle: bundleWithAPIs, + SourceInfo: &OperatorSourceInfo{ Package: "testPackage", Channel: "testChannel", Catalog: registry.CatalogKey{Name: "source", Namespace: "testNamespace"}, @@ -1159,11 +1159,11 @@ func TestNewOperatorFromBundle(t *testing.T) { sourceKey: registry.CatalogKey{Name: "source", Namespace: "testNamespace"}, }, want: &Operator{ - name: "testBundle", - providedAPIs: EmptyAPISet(), - requiredAPIs: EmptyAPISet(), - bundle: bundleWithAPIsUnextracted, - sourceInfo: &OperatorSourceInfo{ + Name: "testBundle", + ProvidedAPIs: EmptyAPISet(), + RequiredAPIs: EmptyAPISet(), + Bundle: bundleWithAPIsUnextracted, + SourceInfo: &OperatorSourceInfo{ Package: "testPackage", Channel: "testChannel", Catalog: registry.CatalogKey{Name: "source", Namespace: "testNamespace"}, @@ -1178,12 +1178,12 @@ func TestNewOperatorFromBundle(t *testing.T) { defaultChannel: "testChannel", }, want: &Operator{ - name: "testBundle", - version: &version.Version, - providedAPIs: EmptyAPISet(), - requiredAPIs: EmptyAPISet(), - bundle: bundleNoAPIs, - sourceInfo: &OperatorSourceInfo{ + Name: "testBundle", + Version: &version.Version, + ProvidedAPIs: EmptyAPISet(), + RequiredAPIs: EmptyAPISet(), + Bundle: bundleNoAPIs, + SourceInfo: &OperatorSourceInfo{ Package: "testPackage", Channel: "testChannel", Catalog: registry.CatalogKey{Name: "source", Namespace: "testNamespace"}, @@ -1198,11 +1198,11 @@ func TestNewOperatorFromBundle(t *testing.T) { sourceKey: registry.CatalogKey{Name: "source", Namespace: "testNamespace"}, }, want: &Operator{ - name: "testBundle", - version: &version.Version, - providedAPIs: EmptyAPISet(), - requiredAPIs: EmptyAPISet(), - properties: []*api.Property{ + Name: "testBundle", + Version: &version.Version, + ProvidedAPIs: EmptyAPISet(), + RequiredAPIs: EmptyAPISet(), + Properties: []*api.Property{ { Type: "olm.gvk", Value: "{\"group\":\"crd.group.com\",\"kind\":\"OwnedCRD\",\"version\":\"v1\"}", @@ -1220,8 +1220,8 @@ func TestNewOperatorFromBundle(t *testing.T) { Value: "{\"group\":\"apis.group.com\",\"kind\":\"RequiredAPI\",\"version\":\"v1\"}", }, }, - bundle: bundleWithPropsAndDeps, - sourceInfo: &OperatorSourceInfo{ + Bundle: bundleWithPropsAndDeps, + SourceInfo: &OperatorSourceInfo{ Package: "testPackage", Channel: "testChannel", Catalog: registry.CatalogKey{Name: "source", Namespace: "testNamespace"}, @@ -1233,8 +1233,8 @@ func TestNewOperatorFromBundle(t *testing.T) { t.Run(tt.name, func(t *testing.T) { got, err := NewOperatorFromBundle(tt.args.bundle, "", tt.args.sourceKey, tt.args.defaultChannel) require.Equal(t, tt.wantErr, err) - requirePropertiesEqual(t, tt.want.properties, got.properties) - tt.want.properties, got.properties = nil, nil + requirePropertiesEqual(t, tt.want.Properties, got.Properties) + tt.want.Properties, got.Properties = nil, nil require.Equal(t, tt.want, got) }) } @@ -1264,11 +1264,11 @@ func TestNewOperatorFromCSV(t *testing.T) { }, }, want: &Operator{ - name: "operator.v1", - providedAPIs: EmptyAPISet(), - requiredAPIs: EmptyAPISet(), - sourceInfo: &ExistingOperator, - version: &version.Version, + Name: "operator.v1", + ProvidedAPIs: EmptyAPISet(), + RequiredAPIs: EmptyAPISet(), + SourceInfo: &ExistingOperator, + Version: &version.Version, }, }, { @@ -1303,12 +1303,12 @@ func TestNewOperatorFromCSV(t *testing.T) { }, }, want: &Operator{ - name: "operator.v1", - providedAPIs: map[opregistry.APIKey]struct{}{ + Name: "operator.v1", + ProvidedAPIs: map[opregistry.APIKey]struct{}{ {Group: "g", Version: "v1", Kind: "APIKind", Plural: "apikinds"}: {}, {Group: "g", Version: "v1", Kind: "CRDKind", Plural: "crdkinds"}: {}, }, - properties: []*api.Property{ + Properties: []*api.Property{ { Type: "olm.gvk", Value: "{\"group\":\"g\",\"kind\":\"APIKind\",\"version\":\"v1\"}", @@ -1318,9 +1318,9 @@ func TestNewOperatorFromCSV(t *testing.T) { Value: "{\"group\":\"g\",\"kind\":\"CRDKind\",\"version\":\"v1\"}", }, }, - requiredAPIs: EmptyAPISet(), - sourceInfo: &ExistingOperator, - version: &version.Version, + RequiredAPIs: EmptyAPISet(), + SourceInfo: &ExistingOperator, + Version: &version.Version, }, }, { @@ -1355,13 +1355,13 @@ func TestNewOperatorFromCSV(t *testing.T) { }, }, want: &Operator{ - name: "operator.v1", - providedAPIs: EmptyAPISet(), - requiredAPIs: map[opregistry.APIKey]struct{}{ + Name: "operator.v1", + ProvidedAPIs: EmptyAPISet(), + RequiredAPIs: map[opregistry.APIKey]struct{}{ {Group: "g", Version: "v1", Kind: "APIKind", Plural: "apikinds"}: {}, {Group: "g", Version: "v1", Kind: "CRDKind", Plural: "crdkinds"}: {}, }, - properties: []*api.Property{ + Properties: []*api.Property{ { Type: "olm.gvk.required", Value: "{\"group\":\"g\",\"kind\":\"APIKind\",\"version\":\"v1\"}", @@ -1371,8 +1371,8 @@ func TestNewOperatorFromCSV(t *testing.T) { Value: "{\"group\":\"g\",\"kind\":\"CRDKind\",\"version\":\"v1\"}", }, }, - sourceInfo: &ExistingOperator, - version: &version.Version, + SourceInfo: &ExistingOperator, + Version: &version.Version, }, }, { @@ -1422,16 +1422,16 @@ func TestNewOperatorFromCSV(t *testing.T) { }, }, want: &Operator{ - name: "operator.v1", - providedAPIs: map[opregistry.APIKey]struct{}{ + Name: "operator.v1", + ProvidedAPIs: map[opregistry.APIKey]struct{}{ {Group: "g", Version: "v1", Kind: "APIOwnedKind", Plural: "apiownedkinds"}: {}, {Group: "g", Version: "v1", Kind: "CRDOwnedKind", Plural: "crdownedkinds"}: {}, }, - requiredAPIs: map[opregistry.APIKey]struct{}{ + RequiredAPIs: map[opregistry.APIKey]struct{}{ {Group: "g2", Version: "v1", Kind: "APIReqKind", Plural: "apireqkinds"}: {}, {Group: "g2", Version: "v1", Kind: "CRDReqKind", Plural: "crdreqkinds"}: {}, }, - properties: []*api.Property{ + Properties: []*api.Property{ { Type: "olm.gvk", Value: "{\"group\":\"g\",\"kind\":\"APIOwnedKind\",\"version\":\"v1\"}", @@ -1449,8 +1449,8 @@ func TestNewOperatorFromCSV(t *testing.T) { Value: "{\"group\":\"g2\",\"kind\":\"CRDReqKind\",\"version\":\"v1\"}", }, }, - sourceInfo: &ExistingOperator, - version: &version.Version, + SourceInfo: &ExistingOperator, + Version: &version.Version, }, }, } @@ -1458,8 +1458,8 @@ func TestNewOperatorFromCSV(t *testing.T) { t.Run(tt.name, func(t *testing.T) { got, err := NewOperatorFromV1Alpha1CSV(tt.args.csv) require.Equal(t, tt.wantErr, err) - requirePropertiesEqual(t, tt.want.properties, got.properties) - tt.want.properties, got.properties = nil, nil + requirePropertiesEqual(t, tt.want.Properties, got.Properties) + tt.want.Properties, got.Properties = nil, nil require.Equal(t, tt.want, got) }) } diff --git a/staging/operator-lifecycle-manager/pkg/controller/registry/resolver/predicates.go b/staging/operator-lifecycle-manager/pkg/controller/registry/resolver/cache/predicates.go similarity index 95% rename from staging/operator-lifecycle-manager/pkg/controller/registry/resolver/predicates.go rename to staging/operator-lifecycle-manager/pkg/controller/registry/resolver/cache/predicates.go index 4e731dd412..171ee5ac91 100644 --- a/staging/operator-lifecycle-manager/pkg/controller/registry/resolver/predicates.go +++ b/staging/operator-lifecycle-manager/pkg/controller/registry/resolver/cache/predicates.go @@ -1,4 +1,4 @@ -package resolver +package cache import ( "bytes" @@ -24,7 +24,7 @@ func CSVNamePredicate(name string) OperatorPredicate { } func (c csvNamePredicate) Test(o *Operator) bool { - return o.Name() == string(c) + return o.Name == string(c) } func (c csvNamePredicate) String() string { @@ -42,10 +42,10 @@ func (ch channelPredicate) Test(o *Operator) bool { if string(ch) == "" { return true } - if o.Bundle() == nil { + if o.Bundle == nil { return false } - return o.Bundle().ChannelName == string(ch) + return o.Bundle.ChannelName == string(ch) } func (ch channelPredicate) String() string { @@ -59,7 +59,7 @@ func PkgPredicate(pkg string) OperatorPredicate { } func (pkg pkgPredicate) Test(o *Operator) bool { - for _, p := range o.Properties() { + for _, p := range o.Properties { if p.Type != opregistry.PackageType { continue } @@ -89,7 +89,7 @@ func VersionInRangePredicate(r semver.Range, version string) OperatorPredicate { } func (v versionInRangePredicate) Test(o *Operator) bool { - for _, p := range o.Properties() { + for _, p := range o.Properties { if p.Type != opregistry.PackageType { continue } @@ -106,7 +106,7 @@ func (v versionInRangePredicate) Test(o *Operator) bool { return true } } - return o.Version() != nil && v.r(*o.Version()) + return o.Version != nil && v.r(*o.Version) } func (v versionInRangePredicate) String() string { @@ -119,7 +119,7 @@ func LabelPredicate(label string) OperatorPredicate { return labelPredicate(label) } func (l labelPredicate) Test(o *Operator) bool { - for _, p := range o.Properties() { + for _, p := range o.Properties { if p.Type != opregistry.LabelType { continue } @@ -148,7 +148,7 @@ func CatalogPredicate(key registry.CatalogKey) OperatorPredicate { } func (c catalogPredicate) Test(o *Operator) bool { - return c.key.Equal(o.SourceInfo().Catalog) + return c.key.Equal(o.SourceInfo.Catalog) } func (c catalogPredicate) String() string { @@ -166,7 +166,7 @@ func ProvidingAPIPredicate(api opregistry.APIKey) OperatorPredicate { } func (g gvkPredicate) Test(o *Operator) bool { - for _, p := range o.Properties() { + for _, p := range o.Properties { if p.Type != opregistry.GVKType { continue } @@ -210,10 +210,10 @@ func ReplacesPredicate(replaces string) OperatorPredicate { } func (r replacesPredicate) Test(o *Operator) bool { - if o.Replaces() == string(r) { + if o.Replaces == string(r) { return true } - for _, s := range o.Skips() { + for _, s := range o.Skips { if s == string(r) { return true } diff --git a/staging/operator-lifecycle-manager/pkg/controller/registry/resolver/predicates_test.go b/staging/operator-lifecycle-manager/pkg/controller/registry/resolver/cache/predicates_test.go similarity index 80% rename from staging/operator-lifecycle-manager/pkg/controller/registry/resolver/predicates_test.go rename to staging/operator-lifecycle-manager/pkg/controller/registry/resolver/cache/predicates_test.go index 97947a7364..ad493b0a7d 100644 --- a/staging/operator-lifecycle-manager/pkg/controller/registry/resolver/predicates_test.go +++ b/staging/operator-lifecycle-manager/pkg/controller/registry/resolver/cache/predicates_test.go @@ -1,4 +1,4 @@ -package resolver +package cache import ( "testing" @@ -6,6 +6,16 @@ import ( "github.com/stretchr/testify/assert" ) +type OperatorPredicateTestFunc func(*Operator) bool + +func (opf OperatorPredicateTestFunc) Test(o *Operator) bool { + return opf(o) +} + +func (opf OperatorPredicateTestFunc) String() string { + return "" +} + func TestCountingPredicate(t *testing.T) { for _, tc := range []struct { Name string diff --git a/staging/operator-lifecycle-manager/pkg/controller/registry/resolver/groups.go b/staging/operator-lifecycle-manager/pkg/controller/registry/resolver/groups.go index 2dcc276e08..72f3e68ebf 100644 --- a/staging/operator-lifecycle-manager/pkg/controller/registry/resolver/groups.go +++ b/staging/operator-lifecycle-manager/pkg/controller/registry/resolver/groups.go @@ -5,6 +5,7 @@ import ( "strings" v1 "github.com/operator-framework/api/pkg/operators/v1" + "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/cache" ) type NamespaceSet map[string]struct{} @@ -93,7 +94,7 @@ type OperatorGroupSurface interface { Identifier() string Namespace() string Targets() NamespaceSet - ProvidedAPIs() APISet + ProvidedAPIs() cache.APISet GroupIntersection(groups ...OperatorGroupSurface) []OperatorGroupSurface } @@ -103,7 +104,7 @@ type OperatorGroup struct { namespace string name string targets NamespaceSet - providedAPIs APISet + providedAPIs cache.APISet } func NewOperatorGroup(group *v1.OperatorGroup) *OperatorGroup { @@ -119,7 +120,7 @@ func NewOperatorGroup(group *v1.OperatorGroup) *OperatorGroup { namespace: group.GetNamespace(), name: group.GetName(), targets: NewNamespaceSet(namespaces), - providedAPIs: GVKStringToProvidedAPISet(gvksStr), + providedAPIs: cache.GVKStringToProvidedAPISet(gvksStr), } } @@ -144,7 +145,7 @@ func (g *OperatorGroup) Targets() NamespaceSet { return g.targets } -func (g *OperatorGroup) ProvidedAPIs() APISet { +func (g *OperatorGroup) ProvidedAPIs() cache.APISet { return g.providedAPIs } @@ -174,18 +175,18 @@ const ( ) type APIIntersectionReconciler interface { - Reconcile(add APISet, group OperatorGroupSurface, otherGroups ...OperatorGroupSurface) APIReconciliationResult + Reconcile(add cache.APISet, group OperatorGroupSurface, otherGroups ...OperatorGroupSurface) APIReconciliationResult } -type APIIntersectionReconcileFunc func(add APISet, group OperatorGroupSurface, otherGroups ...OperatorGroupSurface) APIReconciliationResult +type APIIntersectionReconcileFunc func(add cache.APISet, group OperatorGroupSurface, otherGroups ...OperatorGroupSurface) APIReconciliationResult -func (a APIIntersectionReconcileFunc) Reconcile(add APISet, group OperatorGroupSurface, otherGroups ...OperatorGroupSurface) APIReconciliationResult { +func (a APIIntersectionReconcileFunc) Reconcile(add cache.APISet, group OperatorGroupSurface, otherGroups ...OperatorGroupSurface) APIReconciliationResult { return a(add, group, otherGroups...) } -func ReconcileAPIIntersection(add APISet, group OperatorGroupSurface, otherGroups ...OperatorGroupSurface) APIReconciliationResult { +func ReconcileAPIIntersection(add cache.APISet, group OperatorGroupSurface, otherGroups ...OperatorGroupSurface) APIReconciliationResult { groupIntersection := group.GroupIntersection(otherGroups...) - providedAPIIntersection := make(APISet) + providedAPIIntersection := make(cache.APISet) for _, g := range groupIntersection { providedAPIIntersection = providedAPIIntersection.Union(g.ProvidedAPIs()) } diff --git a/staging/operator-lifecycle-manager/pkg/controller/registry/resolver/groups_test.go b/staging/operator-lifecycle-manager/pkg/controller/registry/resolver/groups_test.go index 20fbc56a56..6b2be428d4 100644 --- a/staging/operator-lifecycle-manager/pkg/controller/registry/resolver/groups_test.go +++ b/staging/operator-lifecycle-manager/pkg/controller/registry/resolver/groups_test.go @@ -8,7 +8,8 @@ import ( "github.com/stretchr/testify/require" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "github.com/operator-framework/api/pkg/operators/v1" + v1 "github.com/operator-framework/api/pkg/operators/v1" + "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/cache" ) func buildAPIOperatorGroup(namespace, name string, targets []string, gvks []string) *v1.OperatorGroup { @@ -38,7 +39,7 @@ func TestNewOperatorGroup(t *testing.T) { namespace: "ns", name: "empty-group", targets: make(NamespaceSet), - providedAPIs: make(APISet), + providedAPIs: make(cache.APISet), }, }, { @@ -51,7 +52,7 @@ func TestNewOperatorGroup(t *testing.T) { "ns": {}, "ns-1": {}, }, - providedAPIs: make(APISet), + providedAPIs: make(cache.APISet), }, }, { @@ -63,7 +64,7 @@ func TestNewOperatorGroup(t *testing.T) { targets: NamespaceSet{ "ns": {}, }, - providedAPIs: make(APISet), + providedAPIs: make(cache.APISet), }, }, { @@ -77,7 +78,7 @@ func TestNewOperatorGroup(t *testing.T) { "ns-1": {}, "ns-2": {}, }, - providedAPIs: make(APISet), + providedAPIs: make(cache.APISet), }, }, { @@ -89,7 +90,7 @@ func TestNewOperatorGroup(t *testing.T) { targets: NamespaceSet{ metav1.NamespaceAll: {}, }, - providedAPIs: make(APISet), + providedAPIs: make(cache.APISet), }, }, { @@ -102,7 +103,7 @@ func TestNewOperatorGroup(t *testing.T) { "ns": {}, "ns-1": {}, }, - providedAPIs: APISet{ + providedAPIs: cache.APISet{ opregistry.APIKey{Group: "birds.com", Version: "v1alpha1", Kind: "Goose"}: {}, }, }, @@ -117,7 +118,7 @@ func TestNewOperatorGroup(t *testing.T) { "ns": {}, "ns-1": {}, }, - providedAPIs: make(APISet), + providedAPIs: make(cache.APISet), }, }, { @@ -130,7 +131,7 @@ func TestNewOperatorGroup(t *testing.T) { "ns": {}, "ns-1": {}, }, - providedAPIs: APISet{ + providedAPIs: cache.APISet{ opregistry.APIKey{Group: "mammals.com", Version: "v1alpha1", Kind: "Moose"}: {}, }, }, @@ -145,7 +146,7 @@ func TestNewOperatorGroup(t *testing.T) { "ns": {}, "ns-1": {}, }, - providedAPIs: APISet{ + providedAPIs: cache.APISet{ opregistry.APIKey{Group: "birds.com", Version: "v1alpha1", Kind: "Goose"}: {}, opregistry.APIKey{Group: "mammals.com", Version: "v1alpha1", Kind: "Moose"}: {}, }, @@ -804,21 +805,21 @@ func TestGroupIntersection(t *testing.T) { func apiIntersectionReconcilerSuite(t *testing.T, reconciler APIIntersectionReconciler) { tests := []struct { name string - add APISet + add cache.APISet group OperatorGroupSurface otherGroups []OperatorGroupSurface want APIReconciliationResult }{ { name: "Empty/NoAPIConflict", - add: make(APISet), + add: make(cache.APISet), group: buildOperatorGroup("ns", "g1", []string{"ns"}, nil), otherGroups: nil, want: NoAPIConflict, }, { name: "NoNamespaceIntersection/APIIntersection/NoAPIConflict", - add: APISet{ + add: cache.APISet{ opregistry.APIKey{Group: "birds.com", Version: "v1alpha1", Kind: "Goose"}: {}, }, group: buildOperatorGroup("ns", "g1", []string{"ns-1"}, []string{"Goose.v1alpha1.birds.com"}), @@ -829,7 +830,7 @@ func apiIntersectionReconcilerSuite(t *testing.T, reconciler APIIntersectionReco }, { name: "NamespaceIntersection/NoAPIIntersection/NoAPIConflict", - add: APISet{ + add: cache.APISet{ opregistry.APIKey{Group: "birds.com", Version: "v1alpha1", Kind: "Goose"}: {}, }, group: buildOperatorGroup("ns", "g1", []string{"ns-1"}, []string{"Goose.v1alpha1.birds.com"}), @@ -840,7 +841,7 @@ func apiIntersectionReconcilerSuite(t *testing.T, reconciler APIIntersectionReco }, { name: "MultipleNamespaceIntersections/NoAPIIntersection/NoAPIConflict", - add: APISet{ + add: cache.APISet{ opregistry.APIKey{Group: "birds.com", Version: "v1alpha1", Kind: "Goose"}: {}, }, group: buildOperatorGroup("ns", "g1", []string{"ns-1"}, []string{"Goose.v1alpha1.birds.com"}), @@ -852,7 +853,7 @@ func apiIntersectionReconcilerSuite(t *testing.T, reconciler APIIntersectionReco }, { name: "SomeNamespaceIntersection/NoAPIIntersection/NoAPIConflict", - add: APISet{ + add: cache.APISet{ opregistry.APIKey{Group: "birds.com", Version: "v1alpha1", Kind: "Goose"}: {}, opregistry.APIKey{Group: "mammals.com", Version: "v1alpha1", Kind: "Moose"}: {}, }, @@ -866,7 +867,7 @@ func apiIntersectionReconcilerSuite(t *testing.T, reconciler APIIntersectionReco }, { name: "AllNamespaceIntersection/NoAPIIntersection/NoAPIConflict", - add: APISet{ + add: cache.APISet{ opregistry.APIKey{Group: "birds.com", Version: "v1alpha1", Kind: "Goose"}: {}, }, group: buildOperatorGroup("ns", "g1", []string{""}, []string{"Goose.v1alpha1.birds.com"}), @@ -877,7 +878,7 @@ func apiIntersectionReconcilerSuite(t *testing.T, reconciler APIIntersectionReco }, { name: "AllNamespaceIntersectionOnOther/NoAPIIntersection/NoAPIConflict", - add: APISet{ + add: cache.APISet{ opregistry.APIKey{Group: "birds.com", Version: "v1alpha1", Kind: "Goose"}: {}, }, group: buildOperatorGroup("ns", "g1", []string{"ns-1"}, []string{"Goose.v1alpha1.birds.com"}), @@ -888,7 +889,7 @@ func apiIntersectionReconcilerSuite(t *testing.T, reconciler APIIntersectionReco }, { name: "AllNamespaceInstersectionOnOther/NoAPIIntersection/NoAPIConflict", - add: APISet{ + add: cache.APISet{ opregistry.APIKey{Group: "birds.com", Version: "v1alpha1", Kind: "Goose"}: {}, }, group: buildOperatorGroup("ns", "g1", []string{""}, []string{"Goose.v1alpha1.birds.com"}), @@ -899,7 +900,7 @@ func apiIntersectionReconcilerSuite(t *testing.T, reconciler APIIntersectionReco }, { name: "NamespaceIntersection/NoAPIIntersection/NoAPIConflict", - add: APISet{ + add: cache.APISet{ opregistry.APIKey{Group: "birds.com", Version: "v1alpha1", Kind: "Goose"}: {}, }, group: buildOperatorGroup("ns", "g1", []string{"ns-1"}, []string{"Goose.v1alpha1.birds.com"}), @@ -910,7 +911,7 @@ func apiIntersectionReconcilerSuite(t *testing.T, reconciler APIIntersectionReco }, { name: "NamespaceIntersection/APIIntersection/APIConflict", - add: APISet{ + add: cache.APISet{ opregistry.APIKey{Group: "birds.com", Version: "v1alpha1", Kind: "Goose"}: {}, }, group: buildOperatorGroup("ns", "g1", []string{"ns-1"}, nil), @@ -921,7 +922,7 @@ func apiIntersectionReconcilerSuite(t *testing.T, reconciler APIIntersectionReco }, { name: "AllNamespaceIntersection/APIIntersection/APIConflict", - add: APISet{ + add: cache.APISet{ opregistry.APIKey{Group: "birds.com", Version: "v1alpha1", Kind: "Goose"}: {}, }, group: buildOperatorGroup("ns", "g1", []string{""}, nil), @@ -932,7 +933,7 @@ func apiIntersectionReconcilerSuite(t *testing.T, reconciler APIIntersectionReco }, { name: "AllNamespaceIntersectionOnOther/APIIntersection/APIConflict", - add: APISet{ + add: cache.APISet{ opregistry.APIKey{Group: "birds.com", Version: "v1alpha1", Kind: "Goose"}: {}, }, group: buildOperatorGroup("ns", "g1", []string{"ns-1"}, nil), @@ -943,7 +944,7 @@ func apiIntersectionReconcilerSuite(t *testing.T, reconciler APIIntersectionReco }, { name: "AllNamespaceIntersectionOnBoth/APIIntersection/APIConflict", - add: APISet{ + add: cache.APISet{ opregistry.APIKey{Group: "birds.com", Version: "v1alpha1", Kind: "Goose"}: {}, }, group: buildOperatorGroup("ns", "g1", []string{""}, nil), @@ -954,7 +955,7 @@ func apiIntersectionReconcilerSuite(t *testing.T, reconciler APIIntersectionReco }, { name: "NamespaceIntersection/SomeAPIIntersection/APIConflict", - add: APISet{ + add: cache.APISet{ opregistry.APIKey{Group: "birds.com", Version: "v1alpha1", Kind: "Goose"}: {}, }, group: buildOperatorGroup("ns", "g1", []string{"ns-1"}, nil), @@ -966,7 +967,7 @@ func apiIntersectionReconcilerSuite(t *testing.T, reconciler APIIntersectionReco }, { name: "NamespaceIntersectionOnOperatorNamespace/SomeAPIIntersection/APIConflict", - add: APISet{ + add: cache.APISet{ opregistry.APIKey{Group: "birds.com", Version: "v1alpha1", Kind: "Goose"}: {}, }, group: buildOperatorGroup("ns", "g1", []string{"ns-1"}, nil), @@ -978,7 +979,7 @@ func apiIntersectionReconcilerSuite(t *testing.T, reconciler APIIntersectionReco { name: "NoNamespaceIntersection/NoAPIIntersection/AddAPIs", - add: APISet{ + add: cache.APISet{ opregistry.APIKey{Group: "birds.com", Version: "v1alpha1", Kind: "Goose"}: {}, }, group: buildOperatorGroup("ns", "g1", []string{"ns-1"}, nil), @@ -989,7 +990,7 @@ func apiIntersectionReconcilerSuite(t *testing.T, reconciler APIIntersectionReco }, { name: "NamespaceIntersection/NoAPIIntersection/AddAPIs", - add: APISet{ + add: cache.APISet{ opregistry.APIKey{Group: "birds.com", Version: "v1alpha1", Kind: "Goose"}: {}, }, group: buildOperatorGroup("ns", "g1", []string{"ns-1"}, nil), @@ -1000,7 +1001,7 @@ func apiIntersectionReconcilerSuite(t *testing.T, reconciler APIIntersectionReco }, { name: "OperatorNamespaceIntersection/NoAPIIntersection/AddAPIs", - add: APISet{ + add: cache.APISet{ opregistry.APIKey{Group: "birds.com", Version: "v1alpha1", Kind: "Goose"}: {}, }, group: buildOperatorGroup("ns", "g1", []string{"ns-1"}, nil), @@ -1011,7 +1012,7 @@ func apiIntersectionReconcilerSuite(t *testing.T, reconciler APIIntersectionReco }, { name: "AllNamespaceIntersection/NoAPIIntersection/AddAPIs", - add: APISet{ + add: cache.APISet{ opregistry.APIKey{Group: "birds.com", Version: "v1alpha1", Kind: "Goose"}: {}, }, group: buildOperatorGroup("ns", "g1", []string{""}, nil), @@ -1023,7 +1024,7 @@ func apiIntersectionReconcilerSuite(t *testing.T, reconciler APIIntersectionReco }, { name: "AllNamespaceIntersectionOnOthers/NoAPIIntersection/AddAPIs", - add: APISet{ + add: cache.APISet{ opregistry.APIKey{Group: "birds.com", Version: "v1alpha1", Kind: "Goose"}: {}, }, group: buildOperatorGroup("ns", "g1", []string{"ns-1"}, nil), @@ -1035,7 +1036,7 @@ func apiIntersectionReconcilerSuite(t *testing.T, reconciler APIIntersectionReco }, { name: "AllNamespaceIntersectionOnOthers/NoAPIIntersection/AddAPIs/PrexistingAddition", - add: APISet{ + add: cache.APISet{ opregistry.APIKey{Group: "birds.com", Version: "v1alpha1", Kind: "Goose"}: {}, opregistry.APIKey{Group: "mammals.com", Version: "v1alpha1", Kind: "Cow"}: {}, }, @@ -1048,7 +1049,7 @@ func apiIntersectionReconcilerSuite(t *testing.T, reconciler APIIntersectionReco }, { name: "NamespaceInstersection/APIIntersection/RemoveAPIs", - add: APISet{ + add: cache.APISet{ opregistry.APIKey{Group: "birds.com", Version: "v1alpha1", Kind: "Goose"}: {}, }, group: buildOperatorGroup("ns", "g1", []string{"ns-1"}, []string{"Goose.v1alpha1.birds.com"}), @@ -1059,7 +1060,7 @@ func apiIntersectionReconcilerSuite(t *testing.T, reconciler APIIntersectionReco }, { name: "AllNamespaceInstersection/APIIntersection/RemoveAPIs", - add: APISet{ + add: cache.APISet{ opregistry.APIKey{Group: "birds.com", Version: "v1alpha1", Kind: "Goose"}: {}, }, group: buildOperatorGroup("ns", "g1", []string{""}, []string{"Goose.v1alpha1.birds.com"}), @@ -1070,7 +1071,7 @@ func apiIntersectionReconcilerSuite(t *testing.T, reconciler APIIntersectionReco }, { name: "AllNamespaceInstersectionOnOther/APIIntersection/RemoveAPIs", - add: APISet{ + add: cache.APISet{ opregistry.APIKey{Group: "birds.com", Version: "v1alpha1", Kind: "Goose"}: {}, }, group: buildOperatorGroup("ns", "g1", []string{""}, []string{"Goose.v1alpha1.birds.com"}), @@ -1081,7 +1082,7 @@ func apiIntersectionReconcilerSuite(t *testing.T, reconciler APIIntersectionReco }, { name: "MultipleNamespaceIntersections/APIIntersection/RemoveAPIs", - add: APISet{ + add: cache.APISet{ opregistry.APIKey{Group: "birds.com", Version: "v1alpha1", Kind: "Goose"}: {}, }, group: buildOperatorGroup("ns", "g1", []string{"ns-1"}, []string{"Goose.v1alpha1.birds.com"}), @@ -1093,7 +1094,7 @@ func apiIntersectionReconcilerSuite(t *testing.T, reconciler APIIntersectionReco }, { name: "SomeNamespaceIntersection/APIIntersection/RemoveAPIs", - add: APISet{ + add: cache.APISet{ opregistry.APIKey{Group: "birds.com", Version: "v1alpha1", Kind: "Goose"}: {}, opregistry.APIKey{Group: "mammals.com", Version: "v1alpha1", Kind: "Moose"}: {}, }, diff --git a/staging/operator-lifecycle-manager/pkg/controller/registry/resolver/installabletypes.go b/staging/operator-lifecycle-manager/pkg/controller/registry/resolver/installabletypes.go index a76361d3f2..94302d53ee 100644 --- a/staging/operator-lifecycle-manager/pkg/controller/registry/resolver/installabletypes.go +++ b/staging/operator-lifecycle-manager/pkg/controller/registry/resolver/installabletypes.go @@ -5,6 +5,7 @@ import ( "strings" "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry" + "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/cache" "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/solver" operatorregistry "github.com/operator-framework/operator-registry/pkg/registry" ) @@ -55,14 +56,13 @@ func bundleId(bundle, channel string, catalog registry.CatalogKey) solver.Identi return solver.IdentifierFromString(fmt.Sprintf("%s/%s/%s", catalog.String(), channel, bundle)) } -func NewBundleInstallableFromOperator(o *Operator) (BundleInstallable, error) { - src := o.SourceInfo() - if src == nil { - return BundleInstallable{}, fmt.Errorf("unable to resolve the source of bundle %s", o.Identifier()) +func NewBundleInstallableFromOperator(o *cache.Operator) (BundleInstallable, error) { + if o.SourceInfo == nil { + return BundleInstallable{}, fmt.Errorf("unable to resolve the source of bundle %s", o.Name) } - id := bundleId(o.Identifier(), o.Channel(), src.Catalog) + id := bundleId(o.Identifier(), o.Channel(), o.SourceInfo.Catalog) var constraints []solver.Constraint - if src.Catalog.Virtual() && src.Subscription == nil { + if o.SourceInfo.Catalog.Virtual() && o.SourceInfo.Subscription == nil { // CSVs already associated with a Subscription // may be replaced, but freestanding CSVs must // appear in any solution. @@ -71,7 +71,7 @@ func NewBundleInstallableFromOperator(o *Operator) (BundleInstallable, error) { fmt.Sprintf("clusterserviceversion %s exists and is not referenced by a subscription", o.Identifier()), )) } - for _, p := range o.bundle.GetProperties() { + for _, p := range o.Properties { if p.GetType() == operatorregistry.DeprecatedType { constraints = append(constraints, PrettyConstraint( solver.Prohibited(), diff --git a/staging/operator-lifecycle-manager/pkg/controller/registry/resolver/labeler.go b/staging/operator-lifecycle-manager/pkg/controller/registry/resolver/labeler.go index cf408ef00d..9b10eb2d6d 100644 --- a/staging/operator-lifecycle-manager/pkg/controller/registry/resolver/labeler.go +++ b/staging/operator-lifecycle-manager/pkg/controller/registry/resolver/labeler.go @@ -1,6 +1,7 @@ package resolver import ( + "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/cache" "github.com/operator-framework/operator-registry/pkg/registry" extv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" "k8s.io/apimachinery/pkg/labels" @@ -15,7 +16,7 @@ const ( // Concrete types other than OperatorSurface and CustomResource definition no-op. func LabelSetsFor(obj interface{}) ([]labels.Set, error) { switch v := obj.(type) { - case OperatorSurface: + case cache.OperatorSurface: return labelSetsForOperatorSurface(v) case *extv1beta1.CustomResourceDefinition: return labelSetsForCRD(v) @@ -24,17 +25,17 @@ func LabelSetsFor(obj interface{}) ([]labels.Set, error) { } } -func labelSetsForOperatorSurface(surface OperatorSurface) ([]labels.Set, error) { +func labelSetsForOperatorSurface(surface cache.OperatorSurface) ([]labels.Set, error) { labelSet := labels.Set{} - for key := range surface.ProvidedAPIs().StripPlural() { - hash, err := APIKeyToGVKHash(key) + for key := range surface.GetProvidedAPIs().StripPlural() { + hash, err := cache.APIKeyToGVKHash(key) if err != nil { return nil, err } labelSet[APILabelKeyPrefix+hash] = "provided" } - for key := range surface.RequiredAPIs().StripPlural() { - hash, err := APIKeyToGVKHash(key) + for key := range surface.GetRequiredAPIs().StripPlural() { + hash, err := cache.APIKeyToGVKHash(key) if err != nil { return nil, err } @@ -52,7 +53,7 @@ func labelSetsForCRD(crd *extv1beta1.CustomResourceDefinition) ([]labels.Set, er // Add label sets for each version for _, version := range crd.Spec.Versions { - hash, err := APIKeyToGVKHash(registry.APIKey{ + hash, err := cache.APIKeyToGVKHash(registry.APIKey{ Group: crd.Spec.Group, Version: version.Name, Kind: crd.Spec.Names.Kind, diff --git a/staging/operator-lifecycle-manager/pkg/controller/registry/resolver/labeler_test.go b/staging/operator-lifecycle-manager/pkg/controller/registry/resolver/labeler_test.go index 2c26a7dece..caf16f972e 100644 --- a/staging/operator-lifecycle-manager/pkg/controller/registry/resolver/labeler_test.go +++ b/staging/operator-lifecycle-manager/pkg/controller/registry/resolver/labeler_test.go @@ -3,6 +3,7 @@ package resolver import ( "testing" + "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/cache" opregistry "github.com/operator-framework/operator-registry/pkg/registry" "github.com/stretchr/testify/require" "k8s.io/apimachinery/pkg/labels" @@ -43,8 +44,8 @@ func TestLabelSetsFor(t *testing.T) { }, { name: "OperatorSurface/Provided", - obj: &Operator{ - providedAPIs: map[opregistry.APIKey]struct{}{ + obj: &cache.Operator{ + ProvidedAPIs: map[opregistry.APIKey]struct{}{ opregistry.APIKey{Group: "ghouls", Version: "v1alpha1", Kind: "Ghost", Plural: "Ghosts"}: {}, }, }, @@ -56,11 +57,11 @@ func TestLabelSetsFor(t *testing.T) { }, { name: "OperatorSurface/ProvidedAndRequired", - obj: &Operator{ - providedAPIs: map[opregistry.APIKey]struct{}{ + obj: &cache.Operator{ + ProvidedAPIs: map[opregistry.APIKey]struct{}{ opregistry.APIKey{Group: "ghouls", Version: "v1alpha1", Kind: "Ghost", Plural: "Ghosts"}: {}, }, - requiredAPIs: map[opregistry.APIKey]struct{}{ + RequiredAPIs: map[opregistry.APIKey]struct{}{ opregistry.APIKey{Group: "ghouls", Version: "v1alpha1", Kind: "Goblin", Plural: "Goblins"}: {}, }, }, diff --git a/staging/operator-lifecycle-manager/pkg/controller/registry/resolver/resolver.go b/staging/operator-lifecycle-manager/pkg/controller/registry/resolver/resolver.go index b00ec8a269..a8eeaf51bf 100644 --- a/staging/operator-lifecycle-manager/pkg/controller/registry/resolver/resolver.go +++ b/staging/operator-lifecycle-manager/pkg/controller/registry/resolver/resolver.go @@ -14,6 +14,7 @@ import ( "github.com/operator-framework/api/pkg/operators/v1alpha1" v1alpha1listers "github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/listers/operators/v1alpha1" "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry" + "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/cache" "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/projection" "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/solver" "github.com/operator-framework/operator-registry/pkg/api" @@ -21,17 +22,17 @@ import ( ) type OperatorResolver interface { - SolveOperators(csvs []*v1alpha1.ClusterServiceVersion, subs []*v1alpha1.Subscription, add map[OperatorSourceInfo]struct{}) (OperatorSet, error) + SolveOperators(csvs []*v1alpha1.ClusterServiceVersion, subs []*v1alpha1.Subscription, add map[cache.OperatorSourceInfo]struct{}) (cache.OperatorSet, error) } type SatResolver struct { - cache OperatorCacheProvider + cache cache.OperatorCacheProvider log logrus.FieldLogger } -func NewDefaultSatResolver(rcp RegistryClientProvider, catsrcLister v1alpha1listers.CatalogSourceLister, log logrus.FieldLogger) *SatResolver { +func NewDefaultSatResolver(rcp cache.RegistryClientProvider, catsrcLister v1alpha1listers.CatalogSourceLister, log logrus.FieldLogger) *SatResolver { return &SatResolver{ - cache: NewOperatorCache(rcp, log, catsrcLister), + cache: cache.NewOperatorCache(rcp, log, catsrcLister), log: log, } } @@ -46,11 +47,11 @@ func (w *debugWriter) Write(b []byte) (int, error) { return n, nil } -func (r *SatResolver) SolveOperators(namespaces []string, csvs []*v1alpha1.ClusterServiceVersion, subs []*v1alpha1.Subscription) (OperatorSet, error) { +func (r *SatResolver) SolveOperators(namespaces []string, csvs []*v1alpha1.ClusterServiceVersion, subs []*v1alpha1.Subscription) (cache.OperatorSet, error) { var errs []error installables := make(map[solver.Identifier]solver.Installable, 0) - visited := make(map[OperatorSurface]*BundleInstallable, 0) + visited := make(map[cache.OperatorSurface]*BundleInstallable, 0) // TODO: better abstraction startingCSVs := make(map[string]struct{}) @@ -73,10 +74,10 @@ func (r *SatResolver) SolveOperators(namespaces []string, csvs []*v1alpha1.Clust // build constraints for each Subscription for _, sub := range subs { // find the currently installed operator (if it exists) - var current *Operator + var current *cache.Operator for _, csv := range csvs { if csv.Name == sub.Status.InstalledCSV { - op, err := NewOperatorFromV1Alpha1CSV(csv) + op, err := cache.NewOperatorFromV1Alpha1CSV(csv) if err != nil { return nil, err } @@ -103,6 +104,10 @@ func (r *SatResolver) SolveOperators(namespaces []string, csvs []*v1alpha1.Clust r.addInvariants(namespacedCache, installables) + if err := namespacedCache.Error(); err != nil { + return nil, err + } + input := make([]solver.Installable, 0) for _, i := range installables { input = append(input, i) @@ -136,7 +141,7 @@ func (r *SatResolver) SolveOperators(namespaces []string, csvs []*v1alpha1.Clust } } - operators := make(map[string]OperatorSurface, 0) + operators := make(map[string]cache.OperatorSurface, 0) for _, installableOperator := range operatorInstallables { csvName, channel, catalog, err := installableOperator.BundleSourceInfo() if err != nil { @@ -144,18 +149,18 @@ func (r *SatResolver) SolveOperators(namespaces []string, csvs []*v1alpha1.Clust continue } - op, err := ExactlyOne(namespacedCache.Catalog(catalog).Find(CSVNamePredicate(csvName), ChannelPredicate(channel))) + op, err := cache.ExactlyOne(namespacedCache.Catalog(catalog).Find(cache.CSVNamePredicate(csvName), cache.ChannelPredicate(channel))) if err != nil { errs = append(errs, err) continue } if len(installableOperator.Replaces) > 0 { - op.replaces = installableOperator.Replaces + op.Replaces = installableOperator.Replaces // TODO: Don't mutate object from cache! } // lookup if this installable came from a starting CSV if _, ok := startingCSVs[csvName]; ok { - op.sourceInfo.StartingCSV = csvName + op.SourceInfo.StartingCSV = csvName // TODO: Don't mutate object from cache! } operators[csvName] = op @@ -168,8 +173,8 @@ func (r *SatResolver) SolveOperators(namespaces []string, csvs []*v1alpha1.Clust return operators, nil } -func (r *SatResolver) getSubscriptionInstallables(sub *v1alpha1.Subscription, current *Operator, namespacedCache MultiCatalogOperatorFinder, visited map[OperatorSurface]*BundleInstallable) (map[solver.Identifier]solver.Installable, error) { - var cachePredicates, channelPredicates []OperatorPredicate +func (r *SatResolver) getSubscriptionInstallables(sub *v1alpha1.Subscription, current *cache.Operator, namespacedCache cache.MultiCatalogOperatorFinder, visited map[cache.OperatorSurface]*BundleInstallable) (map[solver.Identifier]solver.Installable, error) { + var cachePredicates, channelPredicates []cache.OperatorPredicate installables := make(map[solver.Identifier]solver.Installable, 0) catalog := registry.CatalogKey{ @@ -177,24 +182,24 @@ func (r *SatResolver) getSubscriptionInstallables(sub *v1alpha1.Subscription, cu Namespace: sub.Spec.CatalogSourceNamespace, } - var bundles []*Operator + var bundles []*cache.Operator { var nall, npkg, nch, ncsv int - csvPredicate := True() + csvPredicate := cache.True() if current != nil { // if we found an existing installed operator, we should filter the channel by operators that can replace it - channelPredicates = append(channelPredicates, Or(SkipRangeIncludesPredicate(*current.Version()), ReplacesPredicate(current.Identifier()))) + channelPredicates = append(channelPredicates, cache.Or(cache.SkipRangeIncludesPredicate(*current.Version), cache.ReplacesPredicate(current.Identifier()))) } else if sub.Spec.StartingCSV != "" { // if no operator is installed and we have a startingCSV, filter for it - csvPredicate = CSVNamePredicate(sub.Spec.StartingCSV) + csvPredicate = cache.CSVNamePredicate(sub.Spec.StartingCSV) } - cachePredicates = append(cachePredicates, And( - CountingPredicate(True(), &nall), - CountingPredicate(PkgPredicate(sub.Spec.Package), &npkg), - CountingPredicate(ChannelPredicate(sub.Spec.Channel), &nch), - CountingPredicate(csvPredicate, &ncsv), + cachePredicates = append(cachePredicates, cache.And( + cache.CountingPredicate(cache.True(), &nall), + cache.CountingPredicate(cache.PkgPredicate(sub.Spec.Package), &npkg), + cache.CountingPredicate(cache.ChannelPredicate(sub.Spec.Channel), &nch), + cache.CountingPredicate(csvPredicate, &ncsv), )) bundles = namespacedCache.Catalog(catalog).Find(cachePredicates...) @@ -219,23 +224,23 @@ func (r *SatResolver) getSubscriptionInstallables(sub *v1alpha1.Subscription, cu // bundles in the default channel appear first, then lexicographically order by channel name sort.SliceStable(bundles, func(i, j int) bool { var idef bool - if isrc := bundles[i].SourceInfo(); isrc != nil { + if isrc := bundles[i].SourceInfo; isrc != nil { idef = isrc.DefaultChannel } var jdef bool - if jsrc := bundles[j].SourceInfo(); jsrc != nil { + if jsrc := bundles[j].SourceInfo; jsrc != nil { jdef = jsrc.DefaultChannel } if idef == jdef { - return bundles[i].bundle.ChannelName < bundles[j].bundle.ChannelName + return bundles[i].Bundle.ChannelName < bundles[j].Bundle.ChannelName } return idef }) - var sortedBundles []*Operator + var sortedBundles []*cache.Operator lastChannel, lastIndex := "", 0 for i := 0; i <= len(bundles); i++ { - if i != len(bundles) && bundles[i].bundle.ChannelName == lastChannel { + if i != len(bundles) && bundles[i].Bundle.ChannelName == lastChannel { continue } channel, err := sortChannel(bundles[lastIndex:i]) @@ -245,14 +250,14 @@ func (r *SatResolver) getSubscriptionInstallables(sub *v1alpha1.Subscription, cu sortedBundles = append(sortedBundles, channel...) if i != len(bundles) { - lastChannel = bundles[i].bundle.ChannelName + lastChannel = bundles[i].Bundle.ChannelName lastIndex = i } } candidates := make([]*BundleInstallable, 0) - for _, o := range Filter(sortedBundles, channelPredicates...) { - predicates := append(cachePredicates, CSVNamePredicate(o.Identifier())) + for _, o := range cache.Filter(sortedBundles, channelPredicates...) { + predicates := append(cachePredicates, cache.CSVNamePredicate(o.Identifier())) stack := namespacedCache.Catalog(catalog).Find(predicates...) id, installable, err := r.getBundleInstallables(catalog, stack, namespacedCache, visited) if err != nil { @@ -298,12 +303,12 @@ func (r *SatResolver) getSubscriptionInstallables(sub *v1alpha1.Subscription, cu return installables, nil } -func (r *SatResolver) getBundleInstallables(catalog registry.CatalogKey, bundleStack []*Operator, namespacedCache MultiCatalogOperatorFinder, visited map[OperatorSurface]*BundleInstallable) (map[solver.Identifier]struct{}, map[solver.Identifier]*BundleInstallable, error) { +func (r *SatResolver) getBundleInstallables(catalog registry.CatalogKey, bundleStack []*cache.Operator, namespacedCache cache.MultiCatalogOperatorFinder, visited map[cache.OperatorSurface]*BundleInstallable) (map[solver.Identifier]struct{}, map[solver.Identifier]*BundleInstallable, error) { errs := make([]error, 0) installables := make(map[solver.Identifier]*BundleInstallable, 0) // all installables, including dependencies // track the first layer of installable ids - var initial = make(map[*Operator]struct{}) + var initial = make(map[*cache.Operator]struct{}) for _, o := range bundleStack { initial[o] = struct{}{} } @@ -336,15 +341,15 @@ func (r *SatResolver) getBundleInstallables(catalog registry.CatalogKey, bundleS } for _, d := range dependencyPredicates { - sourcePredicate := False() + sourcePredicate := cache.False() // Build a filter matching all (catalog, // package, channel) combinations that contain // at least one candidate bundle, even if only // a subset of those bundles actually satisfy // the dependency. - sources := map[OperatorSourceInfo]struct{}{} + sources := map[cache.OperatorSourceInfo]struct{}{} for _, b := range namespacedCache.Find(d) { - si := b.SourceInfo() + si := b.SourceInfo if _, ok := sources[*si]; ok { // Predicate already covers this source. @@ -353,19 +358,19 @@ func (r *SatResolver) getBundleInstallables(catalog registry.CatalogKey, bundleS sources[*si] = struct{}{} if si.Catalog.Virtual() { - sourcePredicate = Or(sourcePredicate, And( - CSVNamePredicate(b.Identifier()), - CatalogPredicate(si.Catalog), + sourcePredicate = cache.Or(sourcePredicate, cache.And( + cache.CSVNamePredicate(b.Identifier()), + cache.CatalogPredicate(si.Catalog), )) } else { - sourcePredicate = Or(sourcePredicate, And( - PkgPredicate(si.Package), - ChannelPredicate(si.Channel), - CatalogPredicate(si.Catalog), + sourcePredicate = cache.Or(sourcePredicate, cache.And( + cache.PkgPredicate(si.Package), + cache.ChannelPredicate(si.Channel), + cache.CatalogPredicate(si.Catalog), )) } } - sortedBundles, err := r.sortBundles(namespacedCache.FindPreferred(&bundle.sourceInfo.Catalog, sourcePredicate)) + sortedBundles, err := r.sortBundles(namespacedCache.FindPreferred(&bundle.SourceInfo.Catalog, sourcePredicate)) if err != nil { errs = append(errs, err) continue @@ -374,7 +379,7 @@ func (r *SatResolver) getBundleInstallables(catalog registry.CatalogKey, bundleS // The dependency predicate is applied here // (after sorting) to remove all bundles that // don't satisfy the dependency. - for _, b := range Filter(sortedBundles, d) { + for _, b := range cache.Filter(sortedBundles, d) { i, err := NewBundleInstallableFromOperator(b) if err != nil { errs = append(errs, err) @@ -386,7 +391,7 @@ func (r *SatResolver) getBundleInstallables(catalog registry.CatalogKey, bundleS } bundleInstallable.AddConstraint(PrettyConstraint( solver.Dependency(bundleDependencies...), - fmt.Sprintf("bundle %s requires an operator %s", bundle.name, d.String()), + fmt.Sprintf("bundle %s requires an operator %s", bundle.Name, d.String()), )) } @@ -417,7 +422,7 @@ func (r *SatResolver) inferProperties(csv *v1alpha1.ClusterServiceVersion, subs // package against catalog contents, updates to the // Subscription spec could result in a bad package // inference. - for _, entry := range r.cache.Namespaced(sub.Namespace).Catalog(registry.CatalogKey{Namespace: sub.Spec.CatalogSourceNamespace, Name: sub.Spec.CatalogSource}).Find(And(CSVNamePredicate(csv.Name), PkgPredicate(sub.Spec.Package))) { + for _, entry := range r.cache.Namespaced(sub.Namespace).Catalog(registry.CatalogKey{Namespace: sub.Spec.CatalogSourceNamespace, Name: sub.Spec.CatalogSource}).Find(cache.And(cache.CSVNamePredicate(csv.Name), cache.PkgPredicate(sub.Spec.Package))) { if pkg := entry.Package(); pkg != "" { packages[pkg] = struct{}{} } @@ -450,7 +455,7 @@ func (r *SatResolver) inferProperties(csv *v1alpha1.ClusterServiceVersion, subs return properties, nil } -func (r *SatResolver) newSnapshotForNamespace(namespace string, subs []*v1alpha1.Subscription, csvs []*v1alpha1.ClusterServiceVersion) (*CatalogSnapshot, error) { +func (r *SatResolver) newSnapshotForNamespace(namespace string, subs []*v1alpha1.Subscription, csvs []*v1alpha1.ClusterServiceVersion) (*cache.CatalogSnapshot, error) { existingOperatorCatalog := registry.NewVirtualCatalogKey(namespace) // build a catalog snapshot of CSVs without subscriptions csvSubscriptions := make(map[*v1alpha1.ClusterServiceVersion]*v1alpha1.Subscription) @@ -463,9 +468,9 @@ func (r *SatResolver) newSnapshotForNamespace(namespace string, subs []*v1alpha1 } } var csvsMissingProperties []*v1alpha1.ClusterServiceVersion - standaloneOperators := make([]*Operator, 0) + standaloneOperators := make([]*cache.Operator, 0) for _, csv := range csvs { - op, err := NewOperatorFromV1Alpha1CSV(csv) + op, err := cache.NewOperatorFromV1Alpha1CSV(csv) if err != nil { return nil, err } @@ -475,20 +480,20 @@ func (r *SatResolver) newSnapshotForNamespace(namespace string, subs []*v1alpha1 if inferred, err := r.inferProperties(csv, subs); err != nil { r.log.Warnf("unable to infer properties for csv %q: %w", csv.Name, err) } else { - op.properties = append(op.properties, inferred...) + op.Properties = append(op.Properties, inferred...) } } else if props, err := projection.PropertyListFromPropertiesAnnotation(anno); err != nil { return nil, fmt.Errorf("failed to retrieve properties of csv %q: %w", csv.GetName(), err) } else { - op.properties = props + op.Properties = props } - op.sourceInfo = &OperatorSourceInfo{ + op.SourceInfo = &cache.OperatorSourceInfo{ Catalog: existingOperatorCatalog, Subscription: csvSubscriptions[csv], } // Try to determine source package name from properties and add to SourceInfo. - for _, p := range op.properties { + for _, p := range op.Properties { if p.Type != opregistry.PackageType { continue } @@ -498,7 +503,7 @@ func (r *SatResolver) newSnapshotForNamespace(namespace string, subs []*v1alpha1 r.log.Warnf("failed to unmarshal package property of csv %q: %w", csv.Name, err) continue } - op.sourceInfo.Package = pp.PackageName + op.SourceInfo.Package = pp.PackageName } standaloneOperators = append(standaloneOperators, op) @@ -512,10 +517,10 @@ func (r *SatResolver) newSnapshotForNamespace(namespace string, subs []*v1alpha1 r.log.Infof("considered csvs without properties annotation during resolution: %v", names) } - return NewRunningOperatorSnapshot(r.log, existingOperatorCatalog, standaloneOperators), nil + return cache.NewRunningOperatorSnapshot(r.log, existingOperatorCatalog, standaloneOperators), nil } -func (r *SatResolver) addInvariants(namespacedCache MultiCatalogOperatorFinder, installables map[solver.Identifier]solver.Installable) { +func (r *SatResolver) addInvariants(namespacedCache cache.MultiCatalogOperatorFinder, installables map[solver.Identifier]solver.Installable) { // no two operators may provide the same GVK or Package in a namespace gvkConflictToInstallable := make(map[opregistry.GVKProperty][]solver.Identifier) packageConflictToInstallable := make(map[string][]solver.Identifier) @@ -529,13 +534,13 @@ func (r *SatResolver) addInvariants(namespacedCache MultiCatalogOperatorFinder, continue } - op, err := ExactlyOne(namespacedCache.Catalog(catalog).Find(CSVNamePredicate(csvName), ChannelPredicate(channel))) + op, err := cache.ExactlyOne(namespacedCache.Catalog(catalog).Find(cache.CSVNamePredicate(csvName), cache.ChannelPredicate(channel))) if err != nil { continue } // cannot provide the same GVK - for _, p := range op.Properties() { + for _, p := range op.Properties { if p.Type != opregistry.GVKType { continue } @@ -548,7 +553,7 @@ func (r *SatResolver) addInvariants(namespacedCache MultiCatalogOperatorFinder, } // cannot have the same package - for _, p := range op.Properties() { + for _, p := range op.Properties { if p.Type != opregistry.PackageType { continue } @@ -572,7 +577,7 @@ func (r *SatResolver) addInvariants(namespacedCache MultiCatalogOperatorFinder, } } -func (r *SatResolver) sortBundles(bundles []*Operator) ([]*Operator, error) { +func (r *SatResolver) sortBundles(bundles []*cache.Operator) ([]*cache.Operator, error) { // assume bundles have been passed in sorted by catalog already catalogOrder := make([]registry.CatalogKey, 0) @@ -584,22 +589,22 @@ func (r *SatResolver) sortBundles(bundles []*Operator) ([]*Operator, error) { channelOrder := make(map[registry.CatalogKey][]PackageChannel) // partition by catalog -> channel -> bundle - partitionedBundles := map[registry.CatalogKey]map[PackageChannel][]*Operator{} + partitionedBundles := map[registry.CatalogKey]map[PackageChannel][]*cache.Operator{} for _, b := range bundles { pc := PackageChannel{ Package: b.Package(), Channel: b.Channel(), - DefaultChannel: b.SourceInfo().DefaultChannel, + DefaultChannel: b.SourceInfo.DefaultChannel, } - if _, ok := partitionedBundles[b.sourceInfo.Catalog]; !ok { - catalogOrder = append(catalogOrder, b.sourceInfo.Catalog) - partitionedBundles[b.sourceInfo.Catalog] = make(map[PackageChannel][]*Operator) + if _, ok := partitionedBundles[b.SourceInfo.Catalog]; !ok { + catalogOrder = append(catalogOrder, b.SourceInfo.Catalog) + partitionedBundles[b.SourceInfo.Catalog] = make(map[PackageChannel][]*cache.Operator) } - if _, ok := partitionedBundles[b.sourceInfo.Catalog][pc]; !ok { - channelOrder[b.sourceInfo.Catalog] = append(channelOrder[b.sourceInfo.Catalog], pc) - partitionedBundles[b.sourceInfo.Catalog][pc] = make([]*Operator, 0) + if _, ok := partitionedBundles[b.SourceInfo.Catalog][pc]; !ok { + channelOrder[b.SourceInfo.Catalog] = append(channelOrder[b.SourceInfo.Catalog], pc) + partitionedBundles[b.SourceInfo.Catalog][pc] = make([]*cache.Operator, 0) } - partitionedBundles[b.sourceInfo.Catalog][pc] = append(partitionedBundles[b.sourceInfo.Catalog][pc], b) + partitionedBundles[b.SourceInfo.Catalog][pc] = append(partitionedBundles[b.SourceInfo.Catalog][pc], b) } for catalog := range partitionedBundles { @@ -621,7 +626,7 @@ func (r *SatResolver) sortBundles(bundles []*Operator) ([]*Operator, error) { partitionedBundles[catalog][channel] = sorted } } - all := make([]*Operator, 0) + all := make([]*cache.Operator, 0) for _, catalog := range catalogOrder { for _, channel := range channelOrder[catalog] { all = append(all, partitionedBundles[catalog][channel]...) @@ -632,7 +637,7 @@ func (r *SatResolver) sortBundles(bundles []*Operator) ([]*Operator, error) { // Sorts bundle in a channel by replaces. All entries in the argument // are assumed to have the same Package and Channel. -func sortChannel(bundles []*Operator) ([]*Operator, error) { +func sortChannel(bundles []*cache.Operator) ([]*cache.Operator, error) { if len(bundles) < 1 { return bundles, nil } @@ -640,25 +645,25 @@ func sortChannel(bundles []*Operator) ([]*Operator, error) { packageName := bundles[0].Package() channelName := bundles[0].Channel() - bundleLookup := map[string]*Operator{} + bundleLookup := map[string]*cache.Operator{} // if a replaces b, then replacedBy[b] = a - replacedBy := map[*Operator]*Operator{} - replaces := map[*Operator]*Operator{} - skipped := map[string]*Operator{} + replacedBy := map[*cache.Operator]*cache.Operator{} + replaces := map[*cache.Operator]*cache.Operator{} + skipped := map[string]*cache.Operator{} for _, b := range bundles { bundleLookup[b.Identifier()] = b } for _, b := range bundles { - if b.replaces != "" { - if r, ok := bundleLookup[b.replaces]; ok { + if b.Replaces != "" { + if r, ok := bundleLookup[b.Replaces]; ok { replacedBy[r] = b replaces[b] = r } } - for _, skip := range b.skips { + for _, skip := range b.Skips { if r, ok := bundleLookup[skip]; ok { replacedBy[r] = b skipped[skip] = r @@ -668,7 +673,7 @@ func sortChannel(bundles []*Operator) ([]*Operator, error) { // a bundle without a replacement is a channel head, but if we // find more than one of those something is weird - headCandidates := []*Operator{} + headCandidates := []*cache.Operator{} for _, b := range bundles { if _, ok := replacedBy[b]; !ok { headCandidates = append(headCandidates, b) @@ -678,10 +683,10 @@ func sortChannel(bundles []*Operator) ([]*Operator, error) { return nil, fmt.Errorf("no channel heads (entries not replaced by another entry) found in channel %q of package %q", channelName, packageName) } - var chains [][]*Operator + var chains [][]*cache.Operator for _, head := range headCandidates { - var chain []*Operator - visited := make(map[*Operator]struct{}) + var chain []*cache.Operator + visited := make(map[*cache.Operator]struct{}) current := head for { visited[current] = struct{}{} diff --git a/staging/operator-lifecycle-manager/pkg/controller/registry/resolver/resolver_test.go b/staging/operator-lifecycle-manager/pkg/controller/registry/resolver/resolver_test.go index b954425930..fc9b3eb336 100644 --- a/staging/operator-lifecycle-manager/pkg/controller/registry/resolver/resolver_test.go +++ b/staging/operator-lifecycle-manager/pkg/controller/registry/resolver/resolver_test.go @@ -16,13 +16,14 @@ import ( "github.com/operator-framework/api/pkg/lib/version" "github.com/operator-framework/api/pkg/operators/v1alpha1" "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry" + "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/cache" "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/solver" "github.com/operator-framework/operator-registry/pkg/api" opregistry "github.com/operator-framework/operator-registry/pkg/registry" ) func TestSolveOperators(t *testing.T) { - APISet := APISet{opregistry.APIKey{Group: "g", Version: "v", Kind: "k", Plural: "ks"}: struct{}{}} + APISet := cache.APISet{opregistry.APIKey{Group: "g", Version: "v", Kind: "k", Plural: "ks"}: struct{}{}} Provides := APISet const namespace = "test-namespace" @@ -34,11 +35,11 @@ func TestSolveOperators(t *testing.T) { newSub := newSub(namespace, "packageB", "alpha", catalog) subs := []*v1alpha1.Subscription{sub, newSub} - fakeNamespacedOperatorCache := NamespacedOperatorCache{ - snapshots: map[registry.CatalogKey]*CatalogSnapshot{ + fakeNamespacedOperatorCache := cache.NamespacedOperatorCache{ + Snapshots: map[registry.CatalogKey]*cache.CatalogSnapshot{ catalog: { - key: catalog, - operators: []*Operator{ + Key: catalog, + Operators: []*cache.Operator{ genOperator("packageA.v1", "0.0.1", "", "packageA", "alpha", catalog.Name, catalog.Namespace, nil, nil, nil, "", false), genOperator("packageB.v1", "1.0.1", "", "packageB", "alpha", catalog.Name, catalog.Namespace, nil, nil, nil, "", false), }, @@ -53,7 +54,7 @@ func TestSolveOperators(t *testing.T) { operators, err := satResolver.SolveOperators([]string{namespace}, csvs, subs) assert.NoError(t, err) - expected := OperatorSet{ + expected := cache.OperatorSet{ "packageB.v1": genOperator("packageB.v1", "1.0.1", "", "packageB", "alpha", catalog.Name, catalog.Namespace, nil, nil, nil, "", false), } require.EqualValues(t, expected, operators) @@ -66,11 +67,11 @@ func TestDisjointChannelGraph(t *testing.T) { newSub := newSub(namespace, "packageA", "alpha", catalog) subs := []*v1alpha1.Subscription{newSub} - fakeNamespacedOperatorCache := NamespacedOperatorCache{ - snapshots: map[registry.CatalogKey]*CatalogSnapshot{ + fakeNamespacedOperatorCache := cache.NamespacedOperatorCache{ + Snapshots: map[registry.CatalogKey]*cache.CatalogSnapshot{ catalog: { - key: catalog, - operators: []*Operator{ + Key: catalog, + Operators: []*cache.Operator{ genOperator("packageA.side1.v1", "0.0.1", "", "packageA", "alpha", catalog.Name, catalog.Namespace, nil, nil, nil, "", false), genOperator("packageA.side1.v2", "0.0.2", "packageA.side1.v1", "packageA", "alpha", catalog.Name, catalog.Namespace, nil, nil, nil, "", false), genOperator("packageA.side2.v1", "1.0.0", "", "packageA", "alpha", catalog.Name, catalog.Namespace, nil, nil, nil, "", false), @@ -103,11 +104,11 @@ func TestPropertiesAnnotationHonored(t *testing.T) { b := genOperator("packageB.v1", "1.0.1", "", "packageB", "alpha", "community", "olm", nil, nil, []*api.Dependency{{Type: "olm.package", Value: `{"packageName":"packageA","version":"1.0.0"}`}}, "", false) - fakeNamespacedOperatorCache := NamespacedOperatorCache{ - snapshots: map[registry.CatalogKey]*CatalogSnapshot{ + fakeNamespacedOperatorCache := cache.NamespacedOperatorCache{ + Snapshots: map[registry.CatalogKey]*cache.CatalogSnapshot{ community: { - key: community, - operators: []*Operator{b}, + Key: community, + Operators: []*cache.Operator{b}, }, }, } @@ -119,14 +120,14 @@ func TestPropertiesAnnotationHonored(t *testing.T) { operators, err := satResolver.SolveOperators([]string{"olm"}, csvs, subs) assert.NoError(t, err) - expected := OperatorSet{ + expected := cache.OperatorSet{ "packageB.v1": b, } require.EqualValues(t, expected, operators) } func TestSolveOperators_MultipleChannels(t *testing.T) { - APISet := APISet{opregistry.APIKey{"g", "v", "k", "ks"}: struct{}{}} + APISet := cache.APISet{opregistry.APIKey{"g", "v", "k", "ks"}: struct{}{}} Provides := APISet namespace := "olm" @@ -138,11 +139,11 @@ func TestSolveOperators_MultipleChannels(t *testing.T) { newSub := newSub(namespace, "packageB", "alpha", catalog) subs := []*v1alpha1.Subscription{sub, newSub} - fakeNamespacedOperatorCache := NamespacedOperatorCache{ - snapshots: map[registry.CatalogKey]*CatalogSnapshot{ + fakeNamespacedOperatorCache := cache.NamespacedOperatorCache{ + Snapshots: map[registry.CatalogKey]*cache.CatalogSnapshot{ catalog: { - key: catalog, - operators: []*Operator{ + Key: catalog, + Operators: []*cache.Operator{ genOperator("packageA.v1", "0.0.1", "", "packageA", "alpha", "community", "olm", nil, nil, nil, "", false), genOperator("packageB.v1", "1.0.0", "", "packageB", "alpha", "community", "olm", nil, nil, nil, "", false), genOperator("packageB.v1", "1.0.0", "", "packageB", "beta", "community", "olm", nil, nil, nil, "", false), @@ -157,7 +158,7 @@ func TestSolveOperators_MultipleChannels(t *testing.T) { operators, err := satResolver.SolveOperators([]string{"olm"}, csvs, subs) assert.NoError(t, err) - expected := OperatorSet{ + expected := cache.OperatorSet{ "packageB.v1": genOperator("packageB.v1", "1.0.0", "", "packageB", "alpha", "community", "olm", nil, nil, nil, "", false), } assert.Len(t, operators, 1) @@ -167,7 +168,7 @@ func TestSolveOperators_MultipleChannels(t *testing.T) { } func TestSolveOperators_FindLatestVersion(t *testing.T) { - APISet := APISet{opregistry.APIKey{"g", "v", "k", "ks"}: struct{}{}} + APISet := cache.APISet{opregistry.APIKey{"g", "v", "k", "ks"}: struct{}{}} Provides := APISet namespace := "olm" @@ -179,17 +180,17 @@ func TestSolveOperators_FindLatestVersion(t *testing.T) { newSub := newSub(namespace, "packageB", "alpha", catalog) subs := []*v1alpha1.Subscription{sub, newSub} - fakeNamespacedOperatorCache := NamespacedOperatorCache{ - snapshots: map[registry.CatalogKey]*CatalogSnapshot{ + fakeNamespacedOperatorCache := cache.NamespacedOperatorCache{ + Snapshots: map[registry.CatalogKey]*cache.CatalogSnapshot{ registry.CatalogKey{ Namespace: "olm", Name: "community", }: { - key: registry.CatalogKey{ + Key: registry.CatalogKey{ Namespace: "olm", Name: "community", }, - operators: []*Operator{ + Operators: []*cache.Operator{ genOperator("packageA.v1.0.1", "1.0.1", "packageA.v1", "packageA", "alpha", "community", "olm", nil, nil, nil, "", false), genOperator("packageB.v0.9.0", "0.9.0", "", "packageB", "alpha", "community", "olm", nil, nil, nil, "", false), genOperator("packageB.v1.0.0", "1.0.0", "packageB.v0.9.0", "packageB", "alpha", "community", "olm", nil, nil, nil, "", false), @@ -207,10 +208,10 @@ func TestSolveOperators_FindLatestVersion(t *testing.T) { assert.NoError(t, err) assert.Equal(t, 2, len(operators)) for _, op := range operators { - assert.Equal(t, "1.0.1", op.Version().String()) + assert.Equal(t, "1.0.1", op.GetVersion().String()) } - expected := OperatorSet{ + expected := cache.OperatorSet{ "packageA.v1.0.1": genOperator("packageA.v1.0.1", "1.0.1", "packageA.v1", "packageA", "alpha", "community", "olm", nil, nil, nil, "", false), "packageB.v1.0.1": genOperator("packageB.v1.0.1", "1.0.1", "packageB.v1.0.0", "packageB", "alpha", "community", "olm", nil, nil, nil, "", false), } @@ -221,7 +222,7 @@ func TestSolveOperators_FindLatestVersion(t *testing.T) { } func TestSolveOperators_FindLatestVersionWithDependencies(t *testing.T) { - APISet := APISet{opregistry.APIKey{"g", "v", "k", "ks"}: struct{}{}} + APISet := cache.APISet{opregistry.APIKey{"g", "v", "k", "ks"}: struct{}{}} Provides := APISet namespace := "olm" @@ -244,17 +245,17 @@ func TestSolveOperators_FindLatestVersionWithDependencies(t *testing.T) { }, } - fakeNamespacedOperatorCache := NamespacedOperatorCache{ - snapshots: map[registry.CatalogKey]*CatalogSnapshot{ + fakeNamespacedOperatorCache := cache.NamespacedOperatorCache{ + Snapshots: map[registry.CatalogKey]*cache.CatalogSnapshot{ registry.CatalogKey{ Namespace: "olm", Name: "community", }: { - key: registry.CatalogKey{ + Key: registry.CatalogKey{ Namespace: "olm", Name: "community", }, - operators: []*Operator{ + Operators: []*cache.Operator{ genOperator("packageA.v1.0.1", "1.0.1", "packageA.v1", "packageA", "alpha", "community", "olm", nil, nil, nil, "", false), genOperator("packageB.v0.9.0", "0.9.0", "", "packageB", "alpha", "community", "olm", nil, nil, nil, "", false), genOperator("packageB.v1.0.0", "1.0.0", "packageB.v0.9.0", "packageB", "alpha", "community", "olm", nil, nil, nil, "", false), @@ -277,7 +278,7 @@ func TestSolveOperators_FindLatestVersionWithDependencies(t *testing.T) { assert.NoError(t, err) assert.Equal(t, 4, len(operators)) - expected := OperatorSet{ + expected := cache.OperatorSet{ "packageA.v1.0.1": genOperator("packageA.v1.0.1", "1.0.1", "packageA.v1", "packageA", "alpha", "community", "olm", nil, nil, nil, "", false), "packageB.v1.0.1": genOperator("packageB.v1.0.1", "1.0.1", "packageB.v1.0.0", "packageB", "alpha", "community", "olm", nil, nil, opToAddVersionDeps, "", false), "packageC.v1.0.1": genOperator("packageC.v1.0.1", "1.0.1", "packageC.v1.0.0", "packageC", "alpha", "community", "olm", nil, nil, nil, "", false), @@ -290,7 +291,7 @@ func TestSolveOperators_FindLatestVersionWithDependencies(t *testing.T) { } func TestSolveOperators_FindLatestVersionWithNestedDependencies(t *testing.T) { - APISet := APISet{opregistry.APIKey{"g", "v", "k", "ks"}: struct{}{}} + APISet := cache.APISet{opregistry.APIKey{"g", "v", "k", "ks"}: struct{}{}} Provides := APISet namespace := "olm" @@ -319,17 +320,17 @@ func TestSolveOperators_FindLatestVersionWithNestedDependencies(t *testing.T) { }, } - fakeNamespacedOperatorCache := NamespacedOperatorCache{ - snapshots: map[registry.CatalogKey]*CatalogSnapshot{ + fakeNamespacedOperatorCache := cache.NamespacedOperatorCache{ + Snapshots: map[registry.CatalogKey]*cache.CatalogSnapshot{ registry.CatalogKey{ Namespace: "olm", Name: "community", }: { - key: registry.CatalogKey{ + Key: registry.CatalogKey{ Namespace: "olm", Name: "community", }, - operators: []*Operator{ + Operators: []*cache.Operator{ genOperator("packageA.v1.0.1", "1.0.1", "packageA.v1", "packageA", "alpha", "community", "olm", nil, nil, nil, "", false), genOperator("packageB.v0.9.0", "0.9.0", "", "packageB", "alpha", "community", "olm", nil, nil, nil, "", false), genOperator("packageB.v1.0.0", "1.0.0", "packageB.v0.9.0", "packageB", "alpha", "community", "olm", nil, nil, nil, "", false), @@ -352,7 +353,7 @@ func TestSolveOperators_FindLatestVersionWithNestedDependencies(t *testing.T) { assert.NoError(t, err) assert.Equal(t, 5, len(operators)) - expected := OperatorSet{ + expected := cache.OperatorSet{ "packageA.v1.0.1": genOperator("packageA.v1.0.1", "1.0.1", "packageA.v1", "packageA", "alpha", "community", "olm", nil, nil, nil, "", false), "packageB.v1.0.1": genOperator("packageB.v1.0.1", "1.0.1", "packageB.v1.0.0", "packageB", "alpha", "community", "olm", nil, nil, opToAddVersionDeps, "", false), "packageC.v1.0.1": genOperator("packageC.v1.0.1", "1.0.1", "packageC.v1.0.0", "packageC", "alpha", "community", "olm", nil, nil, nil, "", false), @@ -378,17 +379,17 @@ func TestSolveOperators_CatsrcPrioritySorting(t *testing.T) { newSub := newSub(namespace, "packageA", "alpha", customCatalog) subs := []*v1alpha1.Subscription{newSub} - fakeNamespacedOperatorCache := NamespacedOperatorCache{ - snapshots: map[registry.CatalogKey]*CatalogSnapshot{ + fakeNamespacedOperatorCache := cache.NamespacedOperatorCache{ + Snapshots: map[registry.CatalogKey]*cache.CatalogSnapshot{ registry.CatalogKey{ Namespace: "olm", Name: "community", }: { - key: registry.CatalogKey{ + Key: registry.CatalogKey{ Namespace: "olm", Name: "community", }, - operators: []*Operator{ + Operators: []*cache.Operator{ genOperator("packageA.v1", "0.0.1", "", "packageA", "alpha", "community", namespace, nil, nil, opToAddVersionDeps, "", false), }, @@ -397,11 +398,11 @@ func TestSolveOperators_CatsrcPrioritySorting(t *testing.T) { Namespace: "olm", Name: "community-operator", }: { - key: registry.CatalogKey{ + Key: registry.CatalogKey{ Namespace: "olm", Name: "community-operator", }, - operators: []*Operator{ + Operators: []*cache.Operator{ genOperator("packageB.v1", "0.0.1", "", "packageB", "alpha", "community-operator", namespace, nil, nil, nil, "", false), }, @@ -410,12 +411,12 @@ func TestSolveOperators_CatsrcPrioritySorting(t *testing.T) { Namespace: "olm", Name: "high-priority-operator", }: { - key: registry.CatalogKey{ + Key: registry.CatalogKey{ Namespace: "olm", Name: "high-priority-operator", }, - priority: catalogSourcePriority(100), - operators: []*Operator{ + Priority: 100, + Operators: []*cache.Operator{ genOperator("packageB.v1", "0.0.1", "", "packageB", "alpha", "high-priority-operator", namespace, nil, nil, nil, "", false), }, @@ -430,7 +431,7 @@ func TestSolveOperators_CatsrcPrioritySorting(t *testing.T) { operators, err := satResolver.SolveOperators([]string{"olm"}, []*v1alpha1.ClusterServiceVersion{}, subs) assert.NoError(t, err) - expected := OperatorSet{ + expected := cache.OperatorSet{ "packageA.v1": genOperator("packageA.v1", "0.0.1", "", "packageA", "alpha", "community", "olm", nil, nil, opToAddVersionDeps, "", false), "packageB.v1": genOperator("packageB.v1", "0.0.1", "", "packageB", "alpha", "high-priority-operator", "olm", @@ -442,16 +443,16 @@ func TestSolveOperators_CatsrcPrioritySorting(t *testing.T) { } // Catsrc with the same priority, ns, different name - fakeNamespacedOperatorCache.snapshots[registry.CatalogKey{ + fakeNamespacedOperatorCache.Snapshots[registry.CatalogKey{ Namespace: "olm", Name: "community-operator", - }] = &CatalogSnapshot{ - key: registry.CatalogKey{ + }] = &cache.CatalogSnapshot{ + Key: registry.CatalogKey{ Namespace: "olm", Name: "community-operator", }, - priority: catalogSourcePriority(100), - operators: []*Operator{ + Priority: 100, + Operators: []*cache.Operator{ genOperator("packageB.v1", "0.0.1", "", "packageB", "alpha", "community-operator", namespace, nil, nil, nil, "", false), }, @@ -463,7 +464,7 @@ func TestSolveOperators_CatsrcPrioritySorting(t *testing.T) { operators, err = satResolver.SolveOperators([]string{"olm"}, []*v1alpha1.ClusterServiceVersion{}, subs) assert.NoError(t, err) - expected = OperatorSet{ + expected = cache.OperatorSet{ "packageA.v1": genOperator("packageA.v1", "0.0.1", "", "packageA", "alpha", "community", "olm", nil, nil, opToAddVersionDeps, "", false), "packageB.v1": genOperator("packageB.v1", "0.0.1", "", "packageB", "alpha", "community-operator", "olm", @@ -475,15 +476,15 @@ func TestSolveOperators_CatsrcPrioritySorting(t *testing.T) { } // operators from the same catalogs source should be prioritized. - fakeNamespacedOperatorCache.snapshots[registry.CatalogKey{ + fakeNamespacedOperatorCache.Snapshots[registry.CatalogKey{ Namespace: "olm", Name: "community", - }] = &CatalogSnapshot{ - key: registry.CatalogKey{ + }] = &cache.CatalogSnapshot{ + Key: registry.CatalogKey{ Namespace: "olm", Name: "community", }, - operators: []*Operator{ + Operators: []*cache.Operator{ genOperator("packageA.v1", "0.0.1", "", "packageA", "alpha", "community", namespace, nil, nil, opToAddVersionDeps, "", false), genOperator("packageB.v1", "0.0.1", "", "packageB", "alpha", "community", @@ -497,7 +498,7 @@ func TestSolveOperators_CatsrcPrioritySorting(t *testing.T) { operators, err = satResolver.SolveOperators([]string{"olm"}, []*v1alpha1.ClusterServiceVersion{}, subs) assert.NoError(t, err) - expected = OperatorSet{ + expected = cache.OperatorSet{ "packageA.v1": genOperator("packageA.v1", "0.0.1", "", "packageA", "alpha", "community", "olm", nil, nil, opToAddVersionDeps, "", false), "packageB.v1": genOperator("packageB.v1", "0.0.1", "", "packageB", "alpha", "community", "olm", @@ -511,7 +512,7 @@ func TestSolveOperators_CatsrcPrioritySorting(t *testing.T) { } func TestSolveOperators_WithDependencies(t *testing.T) { - APISet := APISet{opregistry.APIKey{"g", "v", "k", "ks"}: struct{}{}} + APISet := cache.APISet{opregistry.APIKey{"g", "v", "k", "ks"}: struct{}{}} Provides := APISet namespace := "olm" @@ -530,17 +531,17 @@ func TestSolveOperators_WithDependencies(t *testing.T) { }, } - fakeNamespacedOperatorCache := NamespacedOperatorCache{ - snapshots: map[registry.CatalogKey]*CatalogSnapshot{ + fakeNamespacedOperatorCache := cache.NamespacedOperatorCache{ + Snapshots: map[registry.CatalogKey]*cache.CatalogSnapshot{ registry.CatalogKey{ Namespace: "olm", Name: "community", }: { - key: registry.CatalogKey{ + Key: registry.CatalogKey{ Namespace: "olm", Name: "community", }, - operators: []*Operator{ + Operators: []*cache.Operator{ genOperator("packageA.v1.0.1", "1.0.1", "packageA.v1", "packageA", "alpha", "community", "olm", nil, nil, nil, "", false), genOperator("packageB.v1", "1.0.0", "", "packageB", "alpha", "community", "olm", nil, nil, opToAddVersionDeps, "", false), genOperator("packageC.v1", "0.1.0", "", "packageC", "alpha", "community", "olm", nil, nil, nil, "", false), @@ -557,7 +558,7 @@ func TestSolveOperators_WithDependencies(t *testing.T) { assert.NoError(t, err) assert.Equal(t, 3, len(operators)) - expected := OperatorSet{ + expected := cache.OperatorSet{ "packageA.v1.0.1": genOperator("packageA.v1.0.1", "1.0.1", "packageA.v1", "packageA", "alpha", "community", "olm", nil, nil, nil, "", false), "packageB.v1": genOperator("packageB.v1", "1.0.0", "", "packageB", "alpha", "community", "olm", nil, nil, opToAddVersionDeps, "", false), "packageC.v1": genOperator("packageC.v1", "0.1.0", "", "packageC", "alpha", "community", "olm", nil, nil, nil, "", false), @@ -569,7 +570,7 @@ func TestSolveOperators_WithDependencies(t *testing.T) { } func TestSolveOperators_WithGVKDependencies(t *testing.T) { - APISet := APISet{opregistry.APIKey{"g", "v", "k", "ks"}: struct{}{}} + APISet := cache.APISet{opregistry.APIKey{"g", "v", "k", "ks"}: struct{}{}} Provides := APISet namespace := "olm" @@ -590,11 +591,11 @@ func TestSolveOperators_WithGVKDependencies(t *testing.T) { }, } - fakeNamespacedOperatorCache := NamespacedOperatorCache{ - snapshots: map[registry.CatalogKey]*CatalogSnapshot{ + fakeNamespacedOperatorCache := cache.NamespacedOperatorCache{ + Snapshots: map[registry.CatalogKey]*cache.CatalogSnapshot{ community: { - key: community, - operators: []*Operator{ + Key: community, + Operators: []*cache.Operator{ genOperator("packageA.v1", "0.0.1", "", "packageA", "alpha", "community", "olm", nil, nil, nil, "", false), genOperator("packageB.v1", "1.0.0", "", "packageB", "alpha", "community", "olm", Provides, nil, deps, "", false), genOperator("packageC.v1", "0.1.0", "", "packageC", "alpha", "community", "olm", nil, Provides, nil, "", false), @@ -610,7 +611,7 @@ func TestSolveOperators_WithGVKDependencies(t *testing.T) { operators, err := satResolver.SolveOperators([]string{"olm"}, csvs, subs) assert.NoError(t, err) - expected := OperatorSet{ + expected := cache.OperatorSet{ "packageB.v1": genOperator("packageB.v1", "1.0.0", "", "packageB", "alpha", "community", "olm", Provides, nil, deps, "", false), "packageC.v1": genOperator("packageC.v1", "0.1.0", "", "packageC", "alpha", "community", "olm", nil, Provides, nil, "", false), } @@ -644,20 +645,20 @@ func TestSolveOperators_WithLabelDependencies(t *testing.T) { operatorBv1 := genOperator("packageB.v1", "1.0.0", "", "packageB", "beta", "community", "olm", nil, nil, nil, "", false) for _, p := range props { - operatorBv1.properties = append(operatorBv1.properties, p) + operatorBv1.Properties = append(operatorBv1.Properties, p) } - fakeNamespacedOperatorCache := NamespacedOperatorCache{ - snapshots: map[registry.CatalogKey]*CatalogSnapshot{ + fakeNamespacedOperatorCache := cache.NamespacedOperatorCache{ + Snapshots: map[registry.CatalogKey]*cache.CatalogSnapshot{ registry.CatalogKey{ Namespace: "olm", Name: "community", }: { - key: registry.CatalogKey{ + Key: registry.CatalogKey{ Namespace: "olm", Name: "community", }, - operators: []*Operator{ + Operators: []*cache.Operator{ genOperator("packageA", "0.0.1", "", "packageA", "alpha", "community", "olm", nil, nil, deps, "", false), operatorBv1, }, @@ -672,7 +673,7 @@ func TestSolveOperators_WithLabelDependencies(t *testing.T) { assert.NoError(t, err) assert.Equal(t, 2, len(operators)) - expected := OperatorSet{ + expected := cache.OperatorSet{ "packageA": genOperator("packageA", "0.0.1", "", "packageA", "alpha", "community", "olm", nil, nil, deps, "", false), "packageB.v1": operatorBv1, } @@ -696,17 +697,17 @@ func TestSolveOperators_WithUnsatisfiableLabelDependencies(t *testing.T) { }, } - fakeNamespacedOperatorCache := NamespacedOperatorCache{ - snapshots: map[registry.CatalogKey]*CatalogSnapshot{ + fakeNamespacedOperatorCache := cache.NamespacedOperatorCache{ + Snapshots: map[registry.CatalogKey]*cache.CatalogSnapshot{ registry.CatalogKey{ Namespace: "olm", Name: "community", }: { - key: registry.CatalogKey{ + Key: registry.CatalogKey{ Namespace: "olm", Name: "community", }, - operators: []*Operator{ + Operators: []*cache.Operator{ genOperator("packageA", "0.0.1", "", "packageA", "alpha", "community", "olm", nil, nil, deps, "", false), genOperator("packageB.v1", "1.0.0", "", "packageB", "alpha", "community", "olm", nil, nil, nil, "", false), }, @@ -723,7 +724,7 @@ func TestSolveOperators_WithUnsatisfiableLabelDependencies(t *testing.T) { } func TestSolveOperators_WithNestedGVKDependencies(t *testing.T) { - APISet := APISet{opregistry.APIKey{"g", "v", "k", "ks"}: struct{}{}} + APISet := cache.APISet{opregistry.APIKey{"g", "v", "k", "ks"}: struct{}{}} Provides := APISet namespace := "olm" @@ -749,17 +750,17 @@ func TestSolveOperators_WithNestedGVKDependencies(t *testing.T) { }, } - fakeNamespacedOperatorCache := NamespacedOperatorCache{ - snapshots: map[registry.CatalogKey]*CatalogSnapshot{ + fakeNamespacedOperatorCache := cache.NamespacedOperatorCache{ + Snapshots: map[registry.CatalogKey]*cache.CatalogSnapshot{ registry.CatalogKey{ Namespace: "olm", Name: "community", }: { - key: registry.CatalogKey{ + Key: registry.CatalogKey{ Namespace: "olm", Name: "community", }, - operators: []*Operator{ + Operators: []*cache.Operator{ genOperator("packageA.v1.0.1", "1.0.1", "packageA.v1", "packageA", "alpha", "community", "olm", nil, nil, nil, "", false), genOperator("packageB.v1.0.0", "1.0.0", "", "packageB", "alpha", "community", "olm", Provides, nil, deps, "", false), genOperator("packageB.v1.0.1", "1.0.1", "packageB.v1.0.0", "packageB", "alpha", "community", "olm", Provides, nil, deps, "", false), @@ -772,11 +773,11 @@ func TestSolveOperators_WithNestedGVKDependencies(t *testing.T) { Namespace: "olm", Name: "certified", }: { - key: registry.CatalogKey{ + Key: registry.CatalogKey{ Namespace: "olm", Name: "certified", }, - operators: []*Operator{ + Operators: []*cache.Operator{ genOperator("packageC.v1.0.0", "1.0.0", "", "packageC", "alpha", "certified", "olm", Provides2, Provides, deps2, "", false), genOperator("packageC.v1.0.1", "1.0.1", "packageC.v1.0.0", "packageC", "alpha", "certified", "olm", Provides2, Provides, deps2, "", false), genOperator("packageD.v1.0.1", "1.0.1", "", "packageD", "alpha", "certified", "olm", nil, Provides2, nil, "", false), @@ -792,7 +793,7 @@ func TestSolveOperators_WithNestedGVKDependencies(t *testing.T) { operators, err := satResolver.SolveOperators([]string{"olm"}, csvs, subs) assert.NoError(t, err) assert.Equal(t, 4, len(operators)) - expected := OperatorSet{ + expected := cache.OperatorSet{ "packageA.v1.0.1": genOperator("packageA.v1.0.1", "1.0.1", "packageA.v1", "packageA", "alpha", "community", "olm", nil, nil, nil, "", false), "packageB.v1.0.1": genOperator("packageB.v1.0.1", "1.0.1", "packageB.v1.0.0", "packageB", "alpha", "community", "olm", Provides, nil, deps, "", false), "packageC.v1.0.1": genOperator("packageC.v1.0.1", "1.0.1", "packageC.v1.0.0", "packageC", "alpha", "community", "olm", Provides2, Provides, deps2, "", false), @@ -813,7 +814,7 @@ func TestSolveOperators_WithNestedGVKDependencies(t *testing.T) { func TestSolveOperators_IgnoreUnsatisfiableDependencies(t *testing.T) { const namespace = "olm" - Provides := APISet{opregistry.APIKey{Group: "g", Version: "v", Kind: "k", Plural: "ks"}: struct{}{}} + Provides := cache.APISet{opregistry.APIKey{Group: "g", Version: "v", Kind: "k", Plural: "ks"}: struct{}{}} community := registry.CatalogKey{Name: "community", Namespace: namespace} csvs := []*v1alpha1.ClusterServiceVersion{ existingOperator(namespace, "packageA.v1", "packageA", "alpha", "", Provides, nil, nil, nil), @@ -836,11 +837,11 @@ func TestSolveOperators_IgnoreUnsatisfiableDependencies(t *testing.T) { }, } - fakeNamespacedOperatorCache := NamespacedOperatorCache{ - snapshots: map[registry.CatalogKey]*CatalogSnapshot{ + fakeNamespacedOperatorCache := cache.NamespacedOperatorCache{ + Snapshots: map[registry.CatalogKey]*cache.CatalogSnapshot{ community: { - key: community, - operators: []*Operator{ + Key: community, + Operators: []*cache.Operator{ genOperator("packageA.v1", "0.0.1", "", "packageA", "alpha", "community", "olm", nil, nil, nil, "", false), genOperator("packageB.v1", "1.0.0", "", "packageB", "alpha", "community", "olm", nil, nil, opToAddVersionDeps, "", false), genOperator("packageC.v1", "0.1.0", "", "packageC", "alpha", "community", "olm", nil, nil, unsatisfiableVersionDeps, "", false), @@ -850,11 +851,11 @@ func TestSolveOperators_IgnoreUnsatisfiableDependencies(t *testing.T) { Namespace: "olm", Name: "certified", }: { - key: registry.CatalogKey{ + Key: registry.CatalogKey{ Namespace: "olm", Name: "certified", }, - operators: []*Operator{ + Operators: []*cache.Operator{ genOperator("packageA.v1", "0.0.1", "", "packageA", "alpha", "certified", "olm", nil, nil, nil, "", false), genOperator("packageB.v1", "1.0.0", "", "packageB", "alpha", "certified", "olm", nil, nil, opToAddVersionDeps, "", false), genOperator("packageC.v1", "0.1.0", "", "packageC", "alpha", "certified", "olm", nil, nil, nil, "", false), @@ -869,7 +870,7 @@ func TestSolveOperators_IgnoreUnsatisfiableDependencies(t *testing.T) { operators, err := satResolver.SolveOperators([]string{"olm"}, csvs, subs) assert.NoError(t, err) - expected := OperatorSet{ + expected := cache.OperatorSet{ "packageB.v1": genOperator("packageB.v1", "1.0.0", "", "packageB", "alpha", "community", "olm", nil, nil, opToAddVersionDeps, "", false), "packageC.v1": genOperator("packageC.v1", "0.1.0", "", "packageC", "alpha", "certified", "olm", nil, nil, nil, "", false), } @@ -883,7 +884,7 @@ func TestSolveOperators_IgnoreUnsatisfiableDependencies(t *testing.T) { // Behavior: The resolver should prefer catalogs in the same namespace as the subscription. // It should also prefer the same catalog over global catalogs in terms of the operator cache. func TestSolveOperators_PreferCatalogInSameNamespace(t *testing.T) { - APISet := APISet{opregistry.APIKey{"g", "v", "k", "ks"}: struct{}{}} + APISet := cache.APISet{opregistry.APIKey{"g", "v", "k", "ks"}: struct{}{}} Provides := APISet namespace := "olm" @@ -896,20 +897,20 @@ func TestSolveOperators_PreferCatalogInSameNamespace(t *testing.T) { sub := existingSub(namespace, "packageA.v1", "packageA", "alpha", catalog) subs := []*v1alpha1.Subscription{sub} - fakeNamespacedOperatorCache := NamespacedOperatorCache{ - snapshots: map[registry.CatalogKey]*CatalogSnapshot{ + fakeNamespacedOperatorCache := cache.NamespacedOperatorCache{ + Snapshots: map[registry.CatalogKey]*cache.CatalogSnapshot{ catalog: { - operators: []*Operator{ + Operators: []*cache.Operator{ genOperator("packageA.v0.0.1", "0.0.1", "packageA.v1", "packageA", "alpha", catalog.Name, catalog.Namespace, nil, Provides, nil, "", false), }, }, altnsCatalog: { - operators: []*Operator{ + Operators: []*cache.Operator{ genOperator("packageA.v0.0.1", "0.0.1", "packageA.v1", "packageA", "alpha", altnsCatalog.Name, altnsCatalog.Namespace, nil, Provides, nil, "", false), }, }, }, - namespaces: []string{namespace, altNamespace}, + Namespaces: []string{namespace, altNamespace}, } satResolver := SatResolver{ cache: getFakeOperatorCache(fakeNamespacedOperatorCache), @@ -919,7 +920,7 @@ func TestSolveOperators_PreferCatalogInSameNamespace(t *testing.T) { operators, err := satResolver.SolveOperators([]string{namespace}, csvs, subs) assert.NoError(t, err) - expected := OperatorSet{ + expected := cache.OperatorSet{ "packageA.v0.0.1": genOperator("packageA.v0.0.1", "0.0.1", "packageA.v1", "packageA", "alpha", catalog.Name, catalog.Namespace, nil, Provides, nil, "", false), } require.EqualValues(t, expected, operators) @@ -928,7 +929,7 @@ func TestSolveOperators_PreferCatalogInSameNamespace(t *testing.T) { // Behavior: The resolver should not look in catalogs not in the same namespace or the global catalog namespace when resolving the subscription. // This test should not result in a successful resolution because the catalog fulfilling the subscription is not in the operator cache. func TestSolveOperators_ResolveOnlyInCachedNamespaces(t *testing.T) { - APISet := APISet{opregistry.APIKey{"g", "v", "k", "ks"}: struct{}{}} + APISet := cache.APISet{opregistry.APIKey{"g", "v", "k", "ks"}: struct{}{}} Provides := APISet namespace := "olm" @@ -940,15 +941,15 @@ func TestSolveOperators_ResolveOnlyInCachedNamespaces(t *testing.T) { newSub := newSub(namespace, "packageA", "alpha", catalog) subs := []*v1alpha1.Subscription{newSub} - fakeNamespacedOperatorCache := NamespacedOperatorCache{ - snapshots: map[registry.CatalogKey]*CatalogSnapshot{ + fakeNamespacedOperatorCache := cache.NamespacedOperatorCache{ + Snapshots: map[registry.CatalogKey]*cache.CatalogSnapshot{ catalog: { - operators: []*Operator{ + Operators: []*cache.Operator{ genOperator("packageA.v0.0.1", "0.0.1", "packageA.v1", "packageA", "alpha", otherCatalog.Name, otherCatalog.Namespace, nil, Provides, nil, "", false), }, }, }, - namespaces: []string{otherCatalog.Namespace}, + Namespaces: []string{otherCatalog.Namespace}, } satResolver := SatResolver{ cache: getFakeOperatorCache(fakeNamespacedOperatorCache), @@ -963,7 +964,7 @@ func TestSolveOperators_ResolveOnlyInCachedNamespaces(t *testing.T) { // Behavior: the resolver should always prefer the default channel for the subscribed bundle (unless we implement ordering for channels) func TestSolveOperators_PreferDefaultChannelInResolution(t *testing.T) { - APISet := APISet{opregistry.APIKey{"g", "v", "k", "ks"}: struct{}{}} + APISet := cache.APISet{opregistry.APIKey{"g", "v", "k", "ks"}: struct{}{}} Provides := APISet namespace := "olm" @@ -976,10 +977,10 @@ func TestSolveOperators_PreferDefaultChannelInResolution(t *testing.T) { newSub := newSub(namespace, "packageA", "", catalog) subs := []*v1alpha1.Subscription{newSub} - fakeNamespacedOperatorCache := NamespacedOperatorCache{ - snapshots: map[registry.CatalogKey]*CatalogSnapshot{ + fakeNamespacedOperatorCache := cache.NamespacedOperatorCache{ + Snapshots: map[registry.CatalogKey]*cache.CatalogSnapshot{ catalog: { - operators: []*Operator{ + Operators: []*cache.Operator{ // Default channel is stable in this case genOperator("packageA.v0.0.2", "0.0.2", "packageA.v1", "packageA", "alpha", catalog.Name, catalog.Namespace, nil, Provides, nil, defaultChannel, false), genOperator("packageA.v0.0.1", "0.0.1", "packageA.v1", "packageA", "stable", catalog.Name, catalog.Namespace, nil, Provides, nil, defaultChannel, false), @@ -997,7 +998,7 @@ func TestSolveOperators_PreferDefaultChannelInResolution(t *testing.T) { assert.NoError(t, err) // operator should be from the default stable channel - expected := OperatorSet{ + expected := cache.OperatorSet{ "packageA.v0.0.1": genOperator("packageA.v0.0.1", "0.0.1", "packageA.v1", "packageA", "stable", catalog.Name, catalog.Namespace, nil, Provides, nil, defaultChannel, false), } require.EqualValues(t, expected, operators) @@ -1005,7 +1006,7 @@ func TestSolveOperators_PreferDefaultChannelInResolution(t *testing.T) { // Behavior: the resolver should always prefer the default channel for bundles satisfying transitive dependencies func TestSolveOperators_PreferDefaultChannelInResolutionForTransitiveDependencies(t *testing.T) { - APISet := APISet{opregistry.APIKey{"g", "v", "k", "ks"}: struct{}{}} + APISet := cache.APISet{opregistry.APIKey{"g", "v", "k", "ks"}: struct{}{}} Provides := APISet namespace := "olm" @@ -1017,11 +1018,11 @@ func TestSolveOperators_PreferDefaultChannelInResolutionForTransitiveDependencie subs := []*v1alpha1.Subscription{newSub} const defaultChannel = "stable" - fakeNamespacedOperatorCache := NamespacedOperatorCache{ - snapshots: map[registry.CatalogKey]*CatalogSnapshot{ + fakeNamespacedOperatorCache := cache.NamespacedOperatorCache{ + Snapshots: map[registry.CatalogKey]*cache.CatalogSnapshot{ catalog: { - operators: []*Operator{ - genOperator("packageA.v0.0.1", "0.0.1", "packageA.v1", "packageA", "alpha", catalog.Name, catalog.Namespace, Provides, nil, apiSetToDependencies(nil, Provides), defaultChannel, false), + Operators: []*cache.Operator{ + genOperator("packageA.v0.0.1", "0.0.1", "packageA.v1", "packageA", "alpha", catalog.Name, catalog.Namespace, Provides, nil, cache.APISetToDependencies(nil, Provides), defaultChannel, false), genOperator("packageB.v0.0.1", "0.0.1", "packageB.v1", "packageB", defaultChannel, catalog.Name, catalog.Namespace, nil, Provides, nil, defaultChannel, false), genOperator("packageB.v0.0.2", "0.0.2", "packageB.v0.0.1", "packageB", "alpha", catalog.Name, catalog.Namespace, nil, Provides, nil, defaultChannel, false), }, @@ -1038,15 +1039,15 @@ func TestSolveOperators_PreferDefaultChannelInResolutionForTransitiveDependencie assert.NoError(t, err) // operator should be from the default stable channel - expected := OperatorSet{ - "packageA.v0.0.1": genOperator("packageA.v0.0.1", "0.0.1", "packageA.v1", "packageA", "alpha", catalog.Name, catalog.Namespace, Provides, nil, apiSetToDependencies(nil, Provides), defaultChannel, false), + expected := cache.OperatorSet{ + "packageA.v0.0.1": genOperator("packageA.v0.0.1", "0.0.1", "packageA.v1", "packageA", "alpha", catalog.Name, catalog.Namespace, Provides, nil, cache.APISetToDependencies(nil, Provides), defaultChannel, false), "packageB.v0.0.1": genOperator("packageB.v0.0.1", "0.0.1", "packageB.v1", "packageB", defaultChannel, catalog.Name, catalog.Namespace, nil, Provides, nil, defaultChannel, false), } require.EqualValues(t, expected, operators) } func TestSolveOperators_SubscriptionlessOperatorsSatisfyDependencies(t *testing.T) { - APISet := APISet{opregistry.APIKey{"g", "v", "k", "ks"}: struct{}{}} + APISet := cache.APISet{opregistry.APIKey{"g", "v", "k", "ks"}: struct{}{}} Provides := APISet namespace := "olm" @@ -1064,17 +1065,17 @@ func TestSolveOperators_SubscriptionlessOperatorsSatisfyDependencies(t *testing. }, } - fakeNamespacedOperatorCache := NamespacedOperatorCache{ - snapshots: map[registry.CatalogKey]*CatalogSnapshot{ + fakeNamespacedOperatorCache := cache.NamespacedOperatorCache{ + Snapshots: map[registry.CatalogKey]*cache.CatalogSnapshot{ registry.CatalogKey{ Namespace: "olm", Name: "community", }: { - key: registry.CatalogKey{ + Key: registry.CatalogKey{ Namespace: "olm", Name: "community", }, - operators: []*Operator{ + Operators: []*cache.Operator{ genOperator("packageB.v1.0.0", "1.0.0", "", "packageB", "alpha", "community", "olm", Provides, nil, deps, "", false), genOperator("packageB.v1.0.1", "1.0.1", "packageB.v1.0.0", "packageB", "alpha", "community", "olm", Provides, nil, deps, "", false), }, @@ -1088,8 +1089,8 @@ func TestSolveOperators_SubscriptionlessOperatorsSatisfyDependencies(t *testing. operators, err := satResolver.SolveOperators([]string{"olm"}, csvs, subs) assert.NoError(t, err) - expected := OperatorSet{ - "packageB.v1.0.1": genOperator("packageB.v1.0.1", "1.0.1", "packageB.v1.0.0", "packageB", "alpha", catalog.Name, catalog.Namespace, Provides, nil, apiSetToDependencies(Provides, nil), "", false), + expected := cache.OperatorSet{ + "packageB.v1.0.1": genOperator("packageB.v1.0.1", "1.0.1", "packageB.v1.0.0", "packageB", "alpha", catalog.Name, catalog.Namespace, Provides, nil, cache.APISetToDependencies(Provides, nil), "", false), } assert.Equal(t, len(expected), len(operators)) for k := range expected { @@ -1099,7 +1100,7 @@ func TestSolveOperators_SubscriptionlessOperatorsSatisfyDependencies(t *testing. } func TestSolveOperators_SubscriptionlessOperatorsCanConflict(t *testing.T) { - APISet := APISet{opregistry.APIKey{"g", "v", "k", "ks"}: struct{}{}} + APISet := cache.APISet{opregistry.APIKey{"g", "v", "k", "ks"}: struct{}{}} Provides := APISet namespace := "olm" @@ -1110,17 +1111,17 @@ func TestSolveOperators_SubscriptionlessOperatorsCanConflict(t *testing.T) { newSub := newSub(namespace, "packageB", "alpha", catalog) subs := []*v1alpha1.Subscription{newSub} - fakeNamespacedOperatorCache := NamespacedOperatorCache{ - snapshots: map[registry.CatalogKey]*CatalogSnapshot{ + fakeNamespacedOperatorCache := cache.NamespacedOperatorCache{ + Snapshots: map[registry.CatalogKey]*cache.CatalogSnapshot{ registry.CatalogKey{ Namespace: "olm", Name: "community", }: { - key: registry.CatalogKey{ + Key: registry.CatalogKey{ Namespace: "olm", Name: "community", }, - operators: []*Operator{ + Operators: []*cache.Operator{ genOperator("packageB.v1.0.0", "1.0.0", "", "packageB", "alpha", "community", "olm", nil, Provides, nil, "", false), genOperator("packageB.v1.0.1", "1.0.1", "packageB.v1.0.0", "packageB", "alpha", "community", "olm", nil, Provides, nil, "", false), }, @@ -1137,10 +1138,10 @@ func TestSolveOperators_SubscriptionlessOperatorsCanConflict(t *testing.T) { } func TestSolveOperators_PackageCannotSelfSatisfy(t *testing.T) { - Provides1 := APISet{opregistry.APIKey{"g", "v", "k", "ks"}: struct{}{}} - Requires1 := APISet{opregistry.APIKey{"g", "v", "k", "ks"}: struct{}{}} - Provides2 := APISet{opregistry.APIKey{"g2", "v", "k", "ks"}: struct{}{}} - Requires2 := APISet{opregistry.APIKey{"g2", "v", "k", "ks"}: struct{}{}} + Provides1 := cache.APISet{opregistry.APIKey{"g", "v", "k", "ks"}: struct{}{}} + Requires1 := cache.APISet{opregistry.APIKey{"g", "v", "k", "ks"}: struct{}{}} + Provides2 := cache.APISet{opregistry.APIKey{"g2", "v", "k", "ks"}: struct{}{}} + Requires2 := cache.APISet{opregistry.APIKey{"g2", "v", "k", "ks"}: struct{}{}} ProvidesBoth := Provides1.Union(Provides2) RequiresBoth := Requires1.Union(Requires2) @@ -1151,11 +1152,11 @@ func TestSolveOperators_PackageCannotSelfSatisfy(t *testing.T) { newSub := newSub(namespace, "packageA", "stable", catalog) subs := []*v1alpha1.Subscription{newSub} - fakeNamespacedOperatorCache := NamespacedOperatorCache{ - snapshots: map[registry.CatalogKey]*CatalogSnapshot{ + fakeNamespacedOperatorCache := cache.NamespacedOperatorCache{ + Snapshots: map[registry.CatalogKey]*cache.CatalogSnapshot{ catalog: { - key: catalog, - operators: []*Operator{ + Key: catalog, + Operators: []*cache.Operator{ genOperator("opA.v1.0.0", "1.0.0", "", "packageA", "stable", catalog.Name, catalog.Namespace, RequiresBoth, nil, nil, "", false), // Despite satisfying dependencies of opA, this is not chosen because it is in the same package genOperator("opABC.v1.0.0", "1.0.0", "", "packageA", "alpha", catalog.Name, catalog.Namespace, nil, ProvidesBoth, nil, "", false), @@ -1165,8 +1166,8 @@ func TestSolveOperators_PackageCannotSelfSatisfy(t *testing.T) { }, }, secondaryCatalog: { - key: secondaryCatalog, - operators: []*Operator{ + Key: secondaryCatalog, + Operators: []*cache.Operator{ genOperator("opC.v1.0.0", "1.0.0", "", "packageB", "stable", secondaryCatalog.Name, secondaryCatalog.Namespace, nil, Provides2, nil, "stable", false), genOperator("opE.v1.0.0", "1.0.0", "", "packageC", "stable", secondaryCatalog.Name, secondaryCatalog.Namespace, nil, Provides2, nil, "", false), @@ -1181,7 +1182,7 @@ func TestSolveOperators_PackageCannotSelfSatisfy(t *testing.T) { operators, err := satResolver.SolveOperators([]string{"olm"}, nil, subs) assert.NoError(t, err) - expected := OperatorSet{ + expected := cache.OperatorSet{ "opA.v1.0.0": genOperator("opA.v1.0.0", "1.0.0", "", "packageA", "stable", catalog.Name, catalog.Namespace, RequiresBoth, nil, nil, "", false), "opB.v1.0.0": genOperator("opB.v1.0.0", "1.0.0", "", "packageB", "stable", catalog.Name, catalog.Namespace, nil, Provides1, nil, "stable", false), "opE.v1.0.0": genOperator("opE.v1.0.0", "1.0.0", "", "packageC", "stable", secondaryCatalog.Name, secondaryCatalog.Namespace, nil, Provides2, nil, "", false), @@ -1194,9 +1195,9 @@ func TestSolveOperators_PackageCannotSelfSatisfy(t *testing.T) { } func TestSolveOperators_TransferApiOwnership(t *testing.T) { - Provides1 := APISet{opregistry.APIKey{"g", "v", "k", "ks"}: struct{}{}} - Requires1 := APISet{opregistry.APIKey{"g", "v", "k", "ks"}: struct{}{}} - Provides2 := APISet{opregistry.APIKey{"g2", "v", "k", "ks"}: struct{}{}} + Provides1 := cache.APISet{opregistry.APIKey{"g", "v", "k", "ks"}: struct{}{}} + Requires1 := cache.APISet{opregistry.APIKey{"g", "v", "k", "ks"}: struct{}{}} + Provides2 := cache.APISet{opregistry.APIKey{"g2", "v", "k", "ks"}: struct{}{}} ProvidesBoth := Provides1.Union(Provides2) namespace := "olm" @@ -1204,19 +1205,19 @@ func TestSolveOperators_TransferApiOwnership(t *testing.T) { phases := []struct { subs []*v1alpha1.Subscription - catalog *CatalogSnapshot - expected OperatorSet + catalog *cache.CatalogSnapshot + expected cache.OperatorSet }{ { subs: []*v1alpha1.Subscription{newSub(namespace, "packageB", "stable", catalog)}, - catalog: &CatalogSnapshot{ - key: catalog, - operators: []*Operator{ + catalog: &cache.CatalogSnapshot{ + Key: catalog, + Operators: []*cache.Operator{ genOperator("opA.v1.0.0", "1.0.0", "", "packageA", "stable", catalog.Name, catalog.Namespace, nil, Provides1, nil, "", false), genOperator("opB.v1.0.0", "1.0.0", "", "packageB", "stable", catalog.Name, catalog.Namespace, Requires1, Provides2, nil, "stable", false), }, }, - expected: OperatorSet{ + expected: cache.OperatorSet{ "opA.v1.0.0": genOperator("opA.v1.0.0", "1.0.0", "", "packageA", "stable", catalog.Name, catalog.Namespace, nil, Provides1, nil, "", false), "opB.v1.0.0": genOperator("opB.v1.0.0", "1.0.0", "", "packageB", "stable", catalog.Name, catalog.Namespace, Requires1, Provides2, nil, "stable", false), }, @@ -1227,16 +1228,16 @@ func TestSolveOperators_TransferApiOwnership(t *testing.T) { existingSub(namespace, "opA.v1.0.0", "packageA", "stable", catalog), existingSub(namespace, "opB.v1.0.0", "packageB", "stable", catalog), }, - catalog: &CatalogSnapshot{ - key: catalog, - operators: []*Operator{ + catalog: &cache.CatalogSnapshot{ + Key: catalog, + Operators: []*cache.Operator{ genOperator("opA.v1.0.0", "1.0.0", "", "packageA", "stable", catalog.Name, catalog.Namespace, nil, Provides1, nil, "", false), genOperator("opA.v1.0.1", "1.0.1", "opA.v1.0.0", "packageA", "stable", catalog.Name, catalog.Namespace, Requires1, nil, nil, "", false), genOperator("opB.v1.0.0", "1.0.0", "", "packageB", "stable", catalog.Name, catalog.Namespace, Requires1, Provides2, nil, "stable", false), }, }, // nothing new to do here - expected: OperatorSet{}, + expected: cache.OperatorSet{}, }, { // will have two existing subs after resolving once @@ -1244,27 +1245,27 @@ func TestSolveOperators_TransferApiOwnership(t *testing.T) { existingSub(namespace, "opA.v1.0.0", "packageA", "stable", catalog), existingSub(namespace, "opB.v1.0.0", "packageB", "stable", catalog), }, - catalog: &CatalogSnapshot{ - key: catalog, - operators: []*Operator{ + catalog: &cache.CatalogSnapshot{ + Key: catalog, + Operators: []*cache.Operator{ genOperator("opA.v1.0.0", "1.0.0", "", "packageA", "stable", catalog.Name, catalog.Namespace, nil, Provides1, nil, "", false), genOperator("opA.v1.0.1", "1.0.1", "opA.v1.0.0", "packageA", "stable", catalog.Name, catalog.Namespace, Requires1, nil, nil, "", false), genOperator("opB.v1.0.0", "1.0.0", "", "packageB", "stable", catalog.Name, catalog.Namespace, Requires1, Provides2, nil, "stable", false), genOperator("opB.v1.0.1", "1.0.1", "opB.v1.0.0", "packageB", "stable", catalog.Name, catalog.Namespace, nil, ProvidesBoth, nil, "stable", false), }, }, - expected: OperatorSet{ + expected: cache.OperatorSet{ "opA.v1.0.1": genOperator("opA.v1.0.1", "1.0.1", "opA.v1.0.0", "packageA", "stable", catalog.Name, catalog.Namespace, Requires1, nil, nil, "", false), "opB.v1.0.1": genOperator("opB.v1.0.1", "1.0.1", "opB.v1.0.0", "packageB", "stable", catalog.Name, catalog.Namespace, nil, ProvidesBoth, nil, "stable", false), }, }, } - var operators OperatorSet + var operators cache.OperatorSet for i, p := range phases { t.Run(fmt.Sprintf("phase %d", i+1), func(t *testing.T) { - fakeNamespacedOperatorCache := NamespacedOperatorCache{ - snapshots: map[registry.CatalogKey]*CatalogSnapshot{ + fakeNamespacedOperatorCache := cache.NamespacedOperatorCache{ + Snapshots: map[registry.CatalogKey]*cache.CatalogSnapshot{ catalog: p.catalog, }, } @@ -1275,11 +1276,11 @@ func TestSolveOperators_TransferApiOwnership(t *testing.T) { csvs := make([]*v1alpha1.ClusterServiceVersion, 0) for _, o := range operators { var pkg, channel string - if b := o.Bundle(); b != nil { + if b := o.GetBundle(); b != nil { pkg = b.PackageName channel = b.ChannelName } - csvs = append(csvs, existingOperator(namespace, o.Identifier(), pkg, channel, o.Replaces(), o.ProvidedAPIs(), o.RequiredAPIs(), nil, nil)) + csvs = append(csvs, existingOperator(namespace, o.Identifier(), pkg, channel, o.GetReplaces(), o.GetProvidedAPIs(), o.GetRequiredAPIs(), nil, nil)) } var err error @@ -1295,10 +1296,10 @@ func TestSolveOperators_TransferApiOwnership(t *testing.T) { } type FakeOperatorCache struct { - fakedNamespacedOperatorCache NamespacedOperatorCache + fakedNamespacedOperatorCache cache.NamespacedOperatorCache } -func (f *FakeOperatorCache) Namespaced(namespaces ...string) MultiCatalogOperatorFinder { +func (f *FakeOperatorCache) Namespaced(namespaces ...string) cache.MultiCatalogOperatorFinder { return &f.fakedNamespacedOperatorCache } @@ -1306,40 +1307,40 @@ func (f *FakeOperatorCache) Expire(key registry.CatalogKey) { return } -func getFakeOperatorCache(fakedNamespacedOperatorCache NamespacedOperatorCache) OperatorCacheProvider { +func getFakeOperatorCache(fakedNamespacedOperatorCache cache.NamespacedOperatorCache) cache.OperatorCacheProvider { return &FakeOperatorCache{ fakedNamespacedOperatorCache: fakedNamespacedOperatorCache, } } -func genOperator(name, version, replaces, pkg, channel, catalogName, catalogNamespace string, requiredAPIs, providedAPIs APISet, dependencies []*api.Dependency, defaultChannel string, deprecated bool) *Operator { +func genOperator(name, version, replaces, pkg, channel, catalogName, catalogNamespace string, requiredAPIs, providedAPIs cache.APISet, dependencies []*api.Dependency, defaultChannel string, deprecated bool) *cache.Operator { semversion, _ := semver.Make(version) - properties := apiSetToProperties(providedAPIs, nil, deprecated) + properties := cache.APISetToProperties(providedAPIs, nil, deprecated) if len(dependencies) == 0 { - ps, err := requiredAPIsToProperties(requiredAPIs) + ps, err := cache.RequiredAPIsToProperties(requiredAPIs) if err != nil { panic(err) } properties = append(properties, ps...) } else { - ps, err := legacyDependenciesToProperties(dependencies) + ps, err := cache.LegacyDependenciesToProperties(dependencies) if err != nil { panic(err) } properties = append(properties, ps...) } - o := &Operator{ - name: name, - version: &semversion, - replaces: replaces, - bundle: &api.Bundle{ + o := &cache.Operator{ + Name: name, + Version: &semversion, + Replaces: replaces, + Bundle: &api.Bundle{ PackageName: pkg, ChannelName: channel, Dependencies: dependencies, Properties: properties, }, - properties: properties, - sourceInfo: &OperatorSourceInfo{ + Properties: properties, + SourceInfo: &cache.OperatorSourceInfo{ Catalog: registry.CatalogKey{ Name: catalogName, Namespace: catalogNamespace, @@ -1348,15 +1349,15 @@ func genOperator(name, version, replaces, pkg, channel, catalogName, catalogName Package: pkg, Channel: channel, }, - providedAPIs: providedAPIs, - requiredAPIs: requiredAPIs, + ProvidedAPIs: providedAPIs, + RequiredAPIs: requiredAPIs, } - ensurePackageProperty(o, pkg, version) + cache.EnsurePackageProperty(o, pkg, version) return o } -func stripBundle(o *Operator) *Operator { - o.bundle = nil +func stripBundle(o *cache.Operator) *cache.Operator { + o.Bundle = nil return o } @@ -1367,11 +1368,11 @@ func TestSolveOperators_WithoutDeprecated(t *testing.T) { newSub(catalog.Namespace, "packageA", "alpha", catalog), } - fakeNamespacedOperatorCache := NamespacedOperatorCache{ - snapshots: map[registry.CatalogKey]*CatalogSnapshot{ + fakeNamespacedOperatorCache := cache.NamespacedOperatorCache{ + Snapshots: map[registry.CatalogKey]*cache.CatalogSnapshot{ catalog: { - key: catalog, - operators: []*Operator{ + Key: catalog, + Operators: []*cache.Operator{ genOperator("packageA.v1", "0.0.1", "", "packageA", "alpha", catalog.Name, catalog.Namespace, nil, nil, nil, "", true), }, }, @@ -1395,11 +1396,11 @@ func TestSolveOperatorsWithDeprecatedInnerChannelEntry(t *testing.T) { } logger, _ := test.NewNullLogger() resolver := SatResolver{ - cache: getFakeOperatorCache(NamespacedOperatorCache{ - snapshots: map[registry.CatalogKey]*CatalogSnapshot{ + cache: getFakeOperatorCache(cache.NamespacedOperatorCache{ + Snapshots: map[registry.CatalogKey]*cache.CatalogSnapshot{ catalog: { - key: catalog, - operators: []*Operator{ + Key: catalog, + Operators: []*cache.Operator{ genOperator("a-1", "1.0.0", "", "a", "c", catalog.Name, catalog.Namespace, nil, nil, nil, "", false), genOperator("a-2", "2.0.0", "a-1", "a", "c", catalog.Name, catalog.Namespace, nil, nil, nil, "", true), genOperator("a-3", "3.0.0", "a-2", "a", "c", catalog.Name, catalog.Namespace, nil, nil, nil, "", false), @@ -1417,7 +1418,7 @@ func TestSolveOperatorsWithDeprecatedInnerChannelEntry(t *testing.T) { } func TestSolveOperators_WithSkipsAndStartingCSV(t *testing.T) { - APISet := APISet{opregistry.APIKey{"g", "v", "k", "ks"}: struct{}{}} + APISet := cache.APISet{opregistry.APIKey{"g", "v", "k", "ks"}: struct{}{}} Provides := APISet namespace := "olm" @@ -1435,27 +1436,27 @@ func TestSolveOperators_WithSkipsAndStartingCSV(t *testing.T) { opB := genOperator("packageB.v1", "1.0.0", "", "packageB", "alpha", "community", "olm", nil, nil, opToAddVersionDeps, "", false) opB2 := genOperator("packageB.v2", "2.0.0", "", "packageB", "alpha", "community", "olm", nil, nil, opToAddVersionDeps, "", false) - opB2.skips = []string{"packageB.v1"} + opB2.Skips = []string{"packageB.v1"} op1 := genOperator("packageA.v1", "1.0.0", "", "packageA", "alpha", "community", "olm", nil, Provides, nil, "", false) op2 := genOperator("packageA.v2", "2.0.0", "packageA.v1", "packageA", "alpha", "community", "olm", nil, Provides, nil, "", false) op3 := genOperator("packageA.v3", "3.0.0", "packageA.v2", "packageA", "alpha", "community", "olm", nil, Provides, nil, "", false) op4 := genOperator("packageA.v4", "4.0.0", "packageA.v3", "packageA", "alpha", "community", "olm", nil, Provides, nil, "", false) - op4.skips = []string{"packageA.v3"} + op4.Skips = []string{"packageA.v3"} op5 := genOperator("packageA.v5", "5.0.0", "packageA.v4", "packageA", "alpha", "community", "olm", nil, Provides, nil, "", false) - op5.skips = []string{"packageA.v2", "packageA.v3", "packageA.v4"} + op5.Skips = []string{"packageA.v2", "packageA.v3", "packageA.v4"} op6 := genOperator("packageA.v6", "6.0.0", "packageA.v5", "packageA", "alpha", "community", "olm", nil, Provides, nil, "", false) - fakeNamespacedOperatorCache := NamespacedOperatorCache{ - snapshots: map[registry.CatalogKey]*CatalogSnapshot{ + fakeNamespacedOperatorCache := cache.NamespacedOperatorCache{ + Snapshots: map[registry.CatalogKey]*cache.CatalogSnapshot{ registry.CatalogKey{ Namespace: "olm", Name: "community", }: { - key: registry.CatalogKey{ + Key: registry.CatalogKey{ Namespace: "olm", Name: "community", }, - operators: []*Operator{ + Operators: []*cache.Operator{ opB, opB2, op1, op2, op3, op4, op5, op6, }, }, @@ -1468,8 +1469,8 @@ func TestSolveOperators_WithSkipsAndStartingCSV(t *testing.T) { operators, err := satResolver.SolveOperators([]string{"olm"}, nil, subs) assert.NoError(t, err) - opB.SourceInfo().StartingCSV = "packageB.v1" - expected := OperatorSet{ + opB.SourceInfo.StartingCSV = "packageB.v1" + expected := cache.OperatorSet{ "packageB.v1": opB, "packageA.v6": op6, } @@ -1485,13 +1486,13 @@ func TestSolveOperators_WithSkips(t *testing.T) { opB := genOperator("packageB.v1", "1.0.0", "", "packageB", "alpha", catalog.Name, catalog.Namespace, nil, nil, nil, "", false) opB2 := genOperator("packageB.v2", "2.0.0", "", "packageB", "alpha", catalog.Name, catalog.Namespace, nil, nil, nil, "", false) - opB2.skips = []string{"packageB.v1"} + opB2.Skips = []string{"packageB.v1"} - fakeNamespacedOperatorCache := NamespacedOperatorCache{ - snapshots: map[registry.CatalogKey]*CatalogSnapshot{ + fakeNamespacedOperatorCache := cache.NamespacedOperatorCache{ + Snapshots: map[registry.CatalogKey]*cache.CatalogSnapshot{ catalog: { - key: catalog, - operators: []*Operator{ + Key: catalog, + Operators: []*cache.Operator{ opB, opB2, }, }, @@ -1504,7 +1505,7 @@ func TestSolveOperators_WithSkips(t *testing.T) { operators, err := satResolver.SolveOperators([]string{namespace}, nil, subs) assert.NoError(t, err) - expected := OperatorSet{ + expected := cache.OperatorSet{ "packageB.v2": opB2, } require.EqualValues(t, expected, operators) @@ -1513,7 +1514,7 @@ func TestSolveOperators_WithSkips(t *testing.T) { func TestSolveOperatorsWithSkipsPreventingSelection(t *testing.T) { const namespace = "test-namespace" catalog := registry.CatalogKey{Name: "test-catalog", Namespace: namespace} - gvks := APISet{opregistry.APIKey{Group: "g", Version: "v", Kind: "k", Plural: "ks"}: struct{}{}} + gvks := cache.APISet{opregistry.APIKey{Group: "g", Version: "v", Kind: "k", Plural: "ks"}: struct{}{}} // Subscription candidate a-1 requires a GVK provided // exclusively by b-1, but b-1 is skipped by b-3 and can't be @@ -1521,17 +1522,17 @@ func TestSolveOperatorsWithSkipsPreventingSelection(t *testing.T) { subs := []*v1alpha1.Subscription{newSub(namespace, "a", "channel", catalog)} a1 := genOperator("a-1", "1.0.0", "", "a", "channel", catalog.Name, catalog.Namespace, gvks, nil, nil, "", false) b3 := genOperator("b-3", "3.0.0", "b-2", "b", "channel", catalog.Name, catalog.Namespace, nil, nil, nil, "", false) - b3.skips = []string{"b-1"} + b3.Skips = []string{"b-1"} b2 := genOperator("b-2", "2.0.0", "b-1", "b", "channel", catalog.Name, catalog.Namespace, nil, nil, nil, "", false) b1 := genOperator("b-1", "1.0.0", "", "b", "channel", catalog.Name, catalog.Namespace, nil, gvks, nil, "", false) logger, _ := test.NewNullLogger() satResolver := SatResolver{ - cache: getFakeOperatorCache(NamespacedOperatorCache{ - snapshots: map[registry.CatalogKey]*CatalogSnapshot{ + cache: getFakeOperatorCache(cache.NamespacedOperatorCache{ + Snapshots: map[registry.CatalogKey]*cache.CatalogSnapshot{ catalog: { - key: catalog, - operators: []*Operator{a1, b3, b2, b1}, + Key: catalog, + Operators: []*cache.Operator{a1, b3, b2, b1}, }, }, }), @@ -1563,11 +1564,11 @@ func TestSolveOperatorsWithClusterServiceVersionHavingDependency(t *testing.T) { log, _ := test.NewNullLogger() r := SatResolver{ - cache: getFakeOperatorCache(NamespacedOperatorCache{ - snapshots: map[registry.CatalogKey]*CatalogSnapshot{ + cache: getFakeOperatorCache(cache.NamespacedOperatorCache{ + Snapshots: map[registry.CatalogKey]*cache.CatalogSnapshot{ catalog: { - key: catalog, - operators: []*Operator{ + Key: catalog, + Operators: []*cache.Operator{ genOperator("b-2", "2.0.0", "b-1", "b", "default", catalog.Name, catalog.Namespace, nil, nil, nil, "", false), }, }, @@ -1586,7 +1587,7 @@ func TestInferProperties(t *testing.T) { for _, tc := range []struct { Name string - Cache NamespacedOperatorCache + Cache cache.NamespacedOperatorCache CSV *v1alpha1.ClusterServiceVersion Subscriptions []*v1alpha1.Subscription Expected []*api.Property @@ -1663,14 +1664,14 @@ func TestInferProperties(t *testing.T) { }, { Name: "one matching subscription infers package property", - Cache: NamespacedOperatorCache{ - snapshots: map[registry.CatalogKey]*CatalogSnapshot{ + Cache: cache.NamespacedOperatorCache{ + Snapshots: map[registry.CatalogKey]*cache.CatalogSnapshot{ catalog: { - key: catalog, - operators: []*Operator{ + Key: catalog, + Operators: []*cache.Operator{ { - name: "a", - bundle: &api.Bundle{ + Name: "a", + Bundle: &api.Bundle{ PackageName: "x", }, }, @@ -1728,14 +1729,14 @@ func TestInferProperties(t *testing.T) { }, { Name: "one matching subscription infers package property without csv version", - Cache: NamespacedOperatorCache{ - snapshots: map[registry.CatalogKey]*CatalogSnapshot{ + Cache: cache.NamespacedOperatorCache{ + Snapshots: map[registry.CatalogKey]*cache.CatalogSnapshot{ catalog: { - key: catalog, - operators: []*Operator{ + Key: catalog, + Operators: []*cache.Operator{ { - name: "a", - bundle: &api.Bundle{ + Name: "a", + Bundle: &api.Bundle{ PackageName: "x", }, }, @@ -1787,41 +1788,41 @@ func TestInferProperties(t *testing.T) { func TestSortChannel(t *testing.T) { for _, tc := range []struct { Name string - In []*Operator - Out []*Operator + In []*cache.Operator + Out []*cache.Operator Err error }{ { Name: "wrinkle-free", - In: []*Operator{ + In: []*cache.Operator{ { - name: "b", - bundle: &api.Bundle{ + Name: "b", + Bundle: &api.Bundle{ PackageName: "package", ChannelName: "channel", }, }, { - name: "a", - replaces: "b", - bundle: &api.Bundle{ + Name: "a", + Replaces: "b", + Bundle: &api.Bundle{ PackageName: "package", ChannelName: "channel", }, }, }, - Out: []*Operator{ + Out: []*cache.Operator{ { - name: "a", - replaces: "b", - bundle: &api.Bundle{ + Name: "a", + Replaces: "b", + Bundle: &api.Bundle{ PackageName: "package", ChannelName: "channel", }, }, { - name: "b", - bundle: &api.Bundle{ + Name: "b", + Bundle: &api.Bundle{ PackageName: "package", ChannelName: "channel", }, @@ -1835,19 +1836,19 @@ func TestSortChannel(t *testing.T) { }, { Name: "replacement cycle", - In: []*Operator{ + In: []*cache.Operator{ { - name: "a", - replaces: "b", - bundle: &api.Bundle{ + Name: "a", + Replaces: "b", + Bundle: &api.Bundle{ PackageName: "package", ChannelName: "channel", }, }, { - name: "b", - replaces: "a", - bundle: &api.Bundle{ + Name: "b", + Replaces: "a", + Bundle: &api.Bundle{ PackageName: "package", ChannelName: "channel", }, @@ -1857,27 +1858,27 @@ func TestSortChannel(t *testing.T) { }, { Name: "replacement cycle", - In: []*Operator{ + In: []*cache.Operator{ { - name: "a", - replaces: "b", - bundle: &api.Bundle{ + Name: "a", + Replaces: "b", + Bundle: &api.Bundle{ PackageName: "package", ChannelName: "channel", }, }, { - name: "b", - replaces: "c", - bundle: &api.Bundle{ + Name: "b", + Replaces: "c", + Bundle: &api.Bundle{ PackageName: "package", ChannelName: "channel", }, }, { - name: "c", - replaces: "b", - bundle: &api.Bundle{ + Name: "c", + Replaces: "b", + Bundle: &api.Bundle{ PackageName: "package", ChannelName: "channel", }, @@ -1887,73 +1888,73 @@ func TestSortChannel(t *testing.T) { }, { Name: "skipped and replaced entry omitted", - In: []*Operator{ + In: []*cache.Operator{ { - name: "a", - replaces: "b", - skips: []string{"b"}, + Name: "a", + Replaces: "b", + Skips: []string{"b"}, }, { - name: "b", + Name: "b", }, }, - Out: []*Operator{ + Out: []*cache.Operator{ { - name: "a", - replaces: "b", - skips: []string{"b"}, + Name: "a", + Replaces: "b", + Skips: []string{"b"}, }, }, }, { Name: "skipped entry omitted", - In: []*Operator{ + In: []*cache.Operator{ { - name: "a", - replaces: "b", - skips: []string{"c"}, + Name: "a", + Replaces: "b", + Skips: []string{"c"}, }, { - name: "b", - replaces: "c", + Name: "b", + Replaces: "c", }, { - name: "c", + Name: "c", }, }, - Out: []*Operator{ + Out: []*cache.Operator{ { - name: "a", - replaces: "b", - skips: []string{"c"}, + Name: "a", + Replaces: "b", + Skips: []string{"c"}, }, { - name: "b", - replaces: "c", + Name: "b", + Replaces: "c", }, }, }, { Name: "two replaces chains", - In: []*Operator{ + In: []*cache.Operator{ { - name: "a", - bundle: &api.Bundle{ + Name: "a", + Bundle: &api.Bundle{ PackageName: "package", ChannelName: "channel", }, }, { - name: "b", - replaces: "c", - bundle: &api.Bundle{ + Name: "b", + Replaces: "c", + Bundle: &api.Bundle{ PackageName: "package", ChannelName: "channel", }, }, { - name: "c", - bundle: &api.Bundle{ + Name: "c", + Bundle: &api.Bundle{ PackageName: "package", ChannelName: "channel", }, diff --git a/staging/operator-lifecycle-manager/pkg/controller/registry/resolver/step_resolver.go b/staging/operator-lifecycle-manager/pkg/controller/registry/resolver/step_resolver.go index 6f211e1bd4..9fb369ca32 100644 --- a/staging/operator-lifecycle-manager/pkg/controller/registry/resolver/step_resolver.go +++ b/staging/operator-lifecycle-manager/pkg/controller/registry/resolver/step_resolver.go @@ -19,6 +19,7 @@ import ( v1alpha1listers "github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/listers/operators/v1alpha1" controllerbundle "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/bundle" "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry" + "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/cache" "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/projection" "github.com/operator-framework/operator-lifecycle-manager/pkg/lib/operatorlister" ) @@ -48,7 +49,7 @@ type OperatorStepResolver struct { var _ StepResolver = &OperatorStepResolver{} func NewOperatorStepResolver(lister operatorlister.OperatorLister, client versioned.Interface, kubeclient kubernetes.Interface, - globalCatalogNamespace string, provider RegistryClientProvider, log logrus.FieldLogger) *OperatorStepResolver { + globalCatalogNamespace string, provider cache.RegistryClientProvider, log logrus.FieldLogger) *OperatorStepResolver { return &OperatorStepResolver{ subLister: lister.OperatorsV1alpha1().SubscriptionLister(), csvLister: lister.OperatorsV1alpha1().ClusterServiceVersionLister(), @@ -56,7 +57,7 @@ func NewOperatorStepResolver(lister operatorlister.OperatorLister, client versio client: client, kubeclient: kubeclient, globalCatalogNamespace: globalCatalogNamespace, - satResolver: NewDefaultSatResolver(NewDefaultRegistryClientProvider(log, provider), lister.OperatorsV1alpha1().CatalogSourceLister(), log), + satResolver: NewDefaultSatResolver(cache.NewDefaultRegistryClientProvider(log, provider), lister.OperatorsV1alpha1().CatalogSourceLister(), log), log: log, } } @@ -86,7 +87,7 @@ func (r *OperatorStepResolver) ResolveSteps(namespace string, _ SourceQuerier) ( return nil, nil, nil, err } - var operators OperatorSet + var operators cache.OperatorSet namespaces := []string{namespace, r.globalCatalogNamespace} operators, err = r.satResolver.SolveOperators(namespaces, csvs, subs) if err != nil { @@ -101,7 +102,7 @@ func (r *OperatorStepResolver) ResolveSteps(namespace string, _ SourceQuerier) ( for name, op := range operators { // Find any existing subscriptions that resolve to this operator. existingSubscriptions := make(map[*v1alpha1.Subscription]bool) - sourceInfo := *op.SourceInfo() + sourceInfo := *op.GetSourceInfo() for _, sub := range subs { if sub.Spec.Package != sourceInfo.Package { continue @@ -137,21 +138,21 @@ func (r *OperatorStepResolver) ResolveSteps(namespace string, _ SourceQuerier) ( } // add steps for any new bundle - if op.Bundle() != nil { + if op.GetBundle() != nil { if op.Inline() { - bundleSteps, err := NewStepsFromBundle(op.Bundle(), namespace, op.Replaces(), op.SourceInfo().Catalog.Name, op.SourceInfo().Catalog.Namespace) + bundleSteps, err := NewStepsFromBundle(op.GetBundle(), namespace, op.GetReplaces(), op.GetSourceInfo().Catalog.Name, op.GetSourceInfo().Catalog.Namespace) if err != nil { return nil, nil, nil, fmt.Errorf("failed to turn bundle into steps: %s", err.Error()) } steps = append(steps, bundleSteps...) } else { lookup := v1alpha1.BundleLookup{ - Path: op.Bundle().GetBundlePath(), + Path: op.GetBundle().GetBundlePath(), Identifier: op.Identifier(), - Replaces: op.Replaces(), + Replaces: op.GetReplaces(), CatalogSourceRef: &corev1.ObjectReference{ - Namespace: op.SourceInfo().Catalog.Namespace, - Name: op.SourceInfo().Catalog.Name, + Namespace: op.GetSourceInfo().Catalog.Namespace, + Name: op.GetSourceInfo().Catalog.Name, }, Conditions: []v1alpha1.BundleLookupCondition{ { @@ -168,7 +169,7 @@ func (r *OperatorStepResolver) ResolveSteps(namespace string, _ SourceQuerier) ( }, }, } - if anno, err := projection.PropertiesAnnotationFromPropertyList(op.Properties()); err != nil { + if anno, err := projection.PropertiesAnnotationFromPropertyList(op.GetProperties()); err != nil { return nil, nil, nil, fmt.Errorf("failed to serialize operator properties for %q: %w", op.Identifier(), err) } else { lookup.Properties = anno @@ -178,8 +179,8 @@ func (r *OperatorStepResolver) ResolveSteps(namespace string, _ SourceQuerier) ( if len(existingSubscriptions) == 0 { // explicitly track the resolved CSV as the starting CSV on the resolved subscriptions - op.SourceInfo().StartingCSV = op.Identifier() - subStep, err := NewSubscriptionStepResource(namespace, *op.SourceInfo()) + op.GetSourceInfo().StartingCSV = op.Identifier() + subStep, err := NewSubscriptionStepResource(namespace, *op.GetSourceInfo()) if err != nil { return nil, nil, nil, err } diff --git a/staging/operator-lifecycle-manager/pkg/controller/registry/resolver/step_resolver_test.go b/staging/operator-lifecycle-manager/pkg/controller/registry/resolver/step_resolver_test.go index 1aa6af0780..27225f556b 100644 --- a/staging/operator-lifecycle-manager/pkg/controller/registry/resolver/step_resolver_test.go +++ b/staging/operator-lifecycle-manager/pkg/controller/registry/resolver/step_resolver_test.go @@ -14,7 +14,6 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" k8sfake "k8s.io/client-go/kubernetes/fake" - "k8s.io/client-go/tools/cache" "github.com/operator-framework/api/pkg/operators/v1alpha1" "github.com/operator-framework/operator-registry/pkg/api" @@ -25,24 +24,26 @@ import ( "github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/informers/externalversions" controllerbundle "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/bundle" "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry" + resolvercache "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/cache" "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/solver" "github.com/operator-framework/operator-lifecycle-manager/pkg/lib/operatorlister" + "k8s.io/client-go/tools/cache" ) var ( // conventions for tests: packages are letters (a,b,c) and apis are numbers (1,2,3) // APISets used for tests - APISet1 = APISet{opregistry.APIKey{"g", "v", "k", "ks"}: struct{}{}} + APISet1 = resolvercache.APISet{opregistry.APIKey{"g", "v", "k", "ks"}: struct{}{}} Provides1 = APISet1 Requires1 = APISet1 - APISet2 = APISet{opregistry.APIKey{"g2", "v2", "k2", "k2s"}: struct{}{}} + APISet2 = resolvercache.APISet{opregistry.APIKey{"g2", "v2", "k2", "k2s"}: struct{}{}} Provides2 = APISet2 Requires2 = APISet2 - APISet3 = APISet{opregistry.APIKey{"g3", "v3", "k3", "k3s"}: struct{}{}} + APISet3 = resolvercache.APISet{opregistry.APIKey{"g3", "v3", "k3", "k3s"}: struct{}{}} Provides3 = APISet3 Requires3 = APISet3 - APISet4 = APISet{opregistry.APIKey{"g4", "v4", "k4", "k4s"}: struct{}{}} + APISet4 = resolvercache.APISet{opregistry.APIKey{"g4", "v4", "k4", "k4s"}: struct{}{}} Provides4 = APISet4 Requires4 = APISet4 ) @@ -826,20 +827,19 @@ func TestResolver(t *testing.T) { lister.OperatorsV1alpha1().RegisterClusterServiceVersionLister(namespace, informerFactory.Operators().V1alpha1().ClusterServiceVersions().Lister()) kClientFake := k8sfake.NewSimpleClientset() - stubSnapshot := &CatalogSnapshot{} + stubSnapshot := &resolvercache.CatalogSnapshot{} for _, bundles := range tt.bundlesByCatalog { for _, bundle := range bundles { - op, err := NewOperatorFromBundle(bundle, "", catalog, "") + op, err := resolvercache.NewOperatorFromBundle(bundle, "", catalog, "") if err != nil { t.Fatalf("unexpected error: %v", err) } - op.replaces = bundle.Replaces - stubSnapshot.operators = append(stubSnapshot.operators, op) + stubSnapshot.Operators = append(stubSnapshot.Operators, op) } } stubCache := &stubOperatorCacheProvider{ - noc: &NamespacedOperatorCache{ - snapshots: map[registry.CatalogKey]*CatalogSnapshot{ + noc: &resolvercache.NamespacedOperatorCache{ + Snapshots: map[registry.CatalogKey]*resolvercache.CatalogSnapshot{ catalog: stubSnapshot, }, }, @@ -880,10 +880,10 @@ func TestResolver(t *testing.T) { } type stubOperatorCacheProvider struct { - noc *NamespacedOperatorCache + noc *resolvercache.NamespacedOperatorCache } -func (stub *stubOperatorCacheProvider) Namespaced(namespaces ...string) MultiCatalogOperatorFinder { +func (stub *stubOperatorCacheProvider) Namespaced(namespaces ...string) resolvercache.MultiCatalogOperatorFinder { return stub.noc } @@ -979,18 +979,17 @@ func TestNamespaceResolverRBAC(t *testing.T) { lister.OperatorsV1alpha1().RegisterSubscriptionLister(namespace, informerFactory.Operators().V1alpha1().Subscriptions().Lister()) lister.OperatorsV1alpha1().RegisterClusterServiceVersionLister(namespace, informerFactory.Operators().V1alpha1().ClusterServiceVersions().Lister()) - stubSnapshot := &CatalogSnapshot{} + stubSnapshot := &resolvercache.CatalogSnapshot{} for _, bundle := range tt.bundlesInCatalog { - op, err := NewOperatorFromBundle(bundle, "", catalog, "") + op, err := resolvercache.NewOperatorFromBundle(bundle, "", catalog, "") if err != nil { t.Fatalf("unexpected error: %v", err) } - op.replaces = bundle.Replaces - stubSnapshot.operators = append(stubSnapshot.operators, op) + stubSnapshot.Operators = append(stubSnapshot.Operators, op) } stubCache := &stubOperatorCacheProvider{ - noc: &NamespacedOperatorCache{ - snapshots: map[registry.CatalogKey]*CatalogSnapshot{ + noc: &resolvercache.NamespacedOperatorCache{ + Snapshots: map[registry.CatalogKey]*resolvercache.CatalogSnapshot{ catalog: stubSnapshot, }, }, @@ -1102,7 +1101,7 @@ func existingSub(namespace, operatorName, pkg, channel string, catalog registry. } } -func existingOperator(namespace, operatorName, pkg, channel, replaces string, providedCRDs, requiredCRDs, providedAPIs, requiredAPIs APISet) *v1alpha1.ClusterServiceVersion { +func existingOperator(namespace, operatorName, pkg, channel, replaces string, providedCRDs, requiredCRDs, providedAPIs, requiredAPIs resolvercache.APISet) *v1alpha1.ClusterServiceVersion { bundleForOperator := bundle(operatorName, pkg, channel, replaces, providedCRDs, requiredCRDs, providedAPIs, requiredAPIs) csv, err := V1alpha1CSVFromBundle(bundleForOperator) if err != nil { diff --git a/staging/operator-lifecycle-manager/pkg/controller/registry/resolver/steps.go b/staging/operator-lifecycle-manager/pkg/controller/registry/resolver/steps.go index d800dff869..7f2270881b 100644 --- a/staging/operator-lifecycle-manager/pkg/controller/registry/resolver/steps.go +++ b/staging/operator-lifecycle-manager/pkg/controller/registry/resolver/steps.go @@ -17,6 +17,7 @@ import ( k8sscheme "k8s.io/client-go/kubernetes/scheme" "github.com/operator-framework/api/pkg/operators/v1alpha1" + "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/cache" "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/projection" "github.com/operator-framework/operator-lifecycle-manager/pkg/lib/ownerutil" ) @@ -86,7 +87,7 @@ func NewStepResourceFromObject(obj runtime.Object, catalogSourceName, catalogSou return resource, nil } -func NewSubscriptionStepResource(namespace string, info OperatorSourceInfo) (v1alpha1.StepResource, error) { +func NewSubscriptionStepResource(namespace string, info cache.OperatorSourceInfo) (v1alpha1.StepResource, error) { return NewStepResourceFromObject(&v1alpha1.Subscription{ ObjectMeta: metav1.ObjectMeta{ Namespace: namespace, diff --git a/staging/operator-lifecycle-manager/pkg/controller/registry/resolver/util_test.go b/staging/operator-lifecycle-manager/pkg/controller/registry/resolver/util_test.go index 3580a3f32d..533013a38d 100644 --- a/staging/operator-lifecycle-manager/pkg/controller/registry/resolver/util_test.go +++ b/staging/operator-lifecycle-manager/pkg/controller/registry/resolver/util_test.go @@ -19,6 +19,7 @@ import ( "github.com/operator-framework/api/pkg/operators/v1alpha1" "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry" + "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/cache" "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/fakes" ) @@ -32,7 +33,7 @@ func RequireStepsEqual(t *testing.T, expectedSteps, steps []*v1alpha1.Step) { } } -func csv(name, replaces string, ownedCRDs, requiredCRDs, ownedAPIServices, requiredAPIServices APISet, permissions, clusterPermissions []v1alpha1.StrategyDeploymentPermissions) *v1alpha1.ClusterServiceVersion { +func csv(name, replaces string, ownedCRDs, requiredCRDs, ownedAPIServices, requiredAPIServices cache.APISet, permissions, clusterPermissions []v1alpha1.StrategyDeploymentPermissions) *v1alpha1.ClusterServiceVersion { var singleInstance = int32(1) strategy := v1alpha1.StrategyDetailsDeployment{ Permissions: permissions, @@ -155,7 +156,7 @@ func u(object runtime.Object) *unstructured.Unstructured { return &unstructured.Unstructured{Object: unst} } -func apiSetToGVK(crds, apis APISet) (out []*api.GroupVersionKind) { +func apiSetToGVK(crds, apis cache.APISet) (out []*api.GroupVersionKind) { out = make([]*api.GroupVersionKind, 0) for a := range crds { out = append(out, &api.GroupVersionKind{ @@ -176,91 +177,6 @@ func apiSetToGVK(crds, apis APISet) (out []*api.GroupVersionKind) { return } -func apiSetToDependencies(crds, apis APISet) (out []*api.Dependency) { - if len(crds)+len(apis) == 0 { - return nil - } - out = make([]*api.Dependency, 0) - for a := range crds { - val, err := json.Marshal(opregistry.GVKDependency{ - Group: a.Group, - Kind: a.Kind, - Version: a.Version, - }) - if err != nil { - panic(err) - } - out = append(out, &api.Dependency{ - Type: opregistry.GVKType, - Value: string(val), - }) - } - for a := range apis { - val, err := json.Marshal(opregistry.GVKDependency{ - Group: a.Group, - Kind: a.Kind, - Version: a.Version, - }) - if err != nil { - panic(err) - } - out = append(out, &api.Dependency{ - Type: opregistry.GVKType, - Value: string(val), - }) - } - if len(out) == 0 { - return nil - } - return -} - -func apiSetToProperties(crds, apis APISet, deprecated bool) (out []*api.Property) { - out = make([]*api.Property, 0) - for a := range crds { - val, err := json.Marshal(opregistry.GVKProperty{ - Group: a.Group, - Kind: a.Kind, - Version: a.Version, - }) - if err != nil { - panic(err) - } - out = append(out, &api.Property{ - Type: opregistry.GVKType, - Value: string(val), - }) - } - for a := range apis { - val, err := json.Marshal(opregistry.GVKProperty{ - Group: a.Group, - Kind: a.Kind, - Version: a.Version, - }) - if err != nil { - panic(err) - } - out = append(out, &api.Property{ - Type: opregistry.GVKType, - Value: string(val), - }) - } - if deprecated { - val, err := json.Marshal(opregistry.DeprecatedProperty{}) - if err != nil { - panic(err) - } - out = append(out, &api.Property{ - Type: opregistry.DeprecatedType, - Value: string(val), - }) - } - if len(out) == 0 { - return nil - } - return -} - func packageNameToProperty(packageName, version string) (out *api.Property) { val, err := json.Marshal(opregistry.PackageProperty{ PackageName: packageName, @@ -303,7 +219,7 @@ func withVersion(version string) bundleOpt { } } -func bundle(name, pkg, channel, replaces string, providedCRDs, requiredCRDs, providedAPIServices, requiredAPIServices APISet, opts ...bundleOpt) *api.Bundle { +func bundle(name, pkg, channel, replaces string, providedCRDs, requiredCRDs, providedAPIServices, requiredAPIServices cache.APISet, opts ...bundleOpt) *api.Bundle { csvJson, err := json.Marshal(csv(name, replaces, providedCRDs, requiredCRDs, providedAPIServices, requiredAPIServices, nil, nil)) if err != nil { panic(err) @@ -328,8 +244,8 @@ func bundle(name, pkg, channel, replaces string, providedCRDs, requiredCRDs, pro ProvidedApis: apiSetToGVK(providedCRDs, providedAPIServices), RequiredApis: apiSetToGVK(requiredCRDs, requiredAPIServices), Replaces: replaces, - Dependencies: apiSetToDependencies(requiredCRDs, requiredAPIServices), - Properties: append(apiSetToProperties(providedCRDs, providedAPIServices, false), + Dependencies: cache.APISetToDependencies(requiredCRDs, requiredAPIServices), + Properties: append(cache.APISetToProperties(providedCRDs, providedAPIServices, false), packageNameToProperty(pkg, "0.0.0"), ), } @@ -359,7 +275,7 @@ func withBundlePath(bundle *api.Bundle, path string) *api.Bundle { return bundle } -func bundleWithPermissions(name, pkg, channel, replaces string, providedCRDs, requiredCRDs, providedAPIServices, requiredAPIServices APISet, permissions, clusterPermissions []v1alpha1.StrategyDeploymentPermissions) *api.Bundle { +func bundleWithPermissions(name, pkg, channel, replaces string, providedCRDs, requiredCRDs, providedAPIServices, requiredAPIServices cache.APISet, permissions, clusterPermissions []v1alpha1.StrategyDeploymentPermissions) *api.Bundle { csvJson, err := json.Marshal(csv(name, replaces, providedCRDs, requiredCRDs, providedAPIServices, requiredAPIServices, permissions, clusterPermissions)) if err != nil { panic(err) @@ -385,8 +301,8 @@ func bundleWithPermissions(name, pkg, channel, replaces string, providedCRDs, re } } -func withReplaces(operator *Operator, replaces string) *Operator { - operator.replaces = replaces +func withReplaces(operator *cache.Operator, replaces string) *cache.Operator { + operator.Replaces = replaces return operator } @@ -518,13 +434,3 @@ func getPkgName(pkgChan string) string { s := strings.Split(pkgChan, "/") return s[0] } - -type OperatorPredicateTestFunc func(*Operator) bool - -func (opf OperatorPredicateTestFunc) Test(o *Operator) bool { - return opf(o) -} - -func (opf OperatorPredicateTestFunc) String() string { - return "" -} diff --git a/staging/operator-lifecycle-manager/pkg/controller/registry/types.go b/staging/operator-lifecycle-manager/pkg/controller/registry/types.go index 11c3f99fa8..55e2c9f40c 100644 --- a/staging/operator-lifecycle-manager/pkg/controller/registry/types.go +++ b/staging/operator-lifecycle-manager/pkg/controller/registry/types.go @@ -33,7 +33,7 @@ func (k *CatalogKey) Virtual() bool { func NewVirtualCatalogKey(namespace string) CatalogKey { return CatalogKey{ - Name: ExistingOperatorKey, + Name: ExistingOperatorKey, Namespace: namespace, } } diff --git a/staging/operator-lifecycle-manager/pkg/fakes/fake_api_intersection_reconciler.go b/staging/operator-lifecycle-manager/pkg/fakes/fake_api_intersection_reconciler.go index 7ce13e21a8..1cfbf3d137 100644 --- a/staging/operator-lifecycle-manager/pkg/fakes/fake_api_intersection_reconciler.go +++ b/staging/operator-lifecycle-manager/pkg/fakes/fake_api_intersection_reconciler.go @@ -5,13 +5,14 @@ import ( "sync" "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver" + "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/cache" ) type FakeAPIIntersectionReconciler struct { - ReconcileStub func(resolver.APISet, resolver.OperatorGroupSurface, ...resolver.OperatorGroupSurface) resolver.APIReconciliationResult + ReconcileStub func(cache.APISet, resolver.OperatorGroupSurface, ...resolver.OperatorGroupSurface) resolver.APIReconciliationResult reconcileMutex sync.RWMutex reconcileArgsForCall []struct { - arg1 resolver.APISet + arg1 cache.APISet arg2 resolver.OperatorGroupSurface arg3 []resolver.OperatorGroupSurface } @@ -25,11 +26,11 @@ type FakeAPIIntersectionReconciler struct { invocationsMutex sync.RWMutex } -func (fake *FakeAPIIntersectionReconciler) Reconcile(arg1 resolver.APISet, arg2 resolver.OperatorGroupSurface, arg3 ...resolver.OperatorGroupSurface) resolver.APIReconciliationResult { +func (fake *FakeAPIIntersectionReconciler) Reconcile(arg1 cache.APISet, arg2 resolver.OperatorGroupSurface, arg3 ...resolver.OperatorGroupSurface) resolver.APIReconciliationResult { fake.reconcileMutex.Lock() ret, specificReturn := fake.reconcileReturnsOnCall[len(fake.reconcileArgsForCall)] fake.reconcileArgsForCall = append(fake.reconcileArgsForCall, struct { - arg1 resolver.APISet + arg1 cache.APISet arg2 resolver.OperatorGroupSurface arg3 []resolver.OperatorGroupSurface }{arg1, arg2, arg3}) @@ -51,13 +52,13 @@ func (fake *FakeAPIIntersectionReconciler) ReconcileCallCount() int { return len(fake.reconcileArgsForCall) } -func (fake *FakeAPIIntersectionReconciler) ReconcileCalls(stub func(resolver.APISet, resolver.OperatorGroupSurface, ...resolver.OperatorGroupSurface) resolver.APIReconciliationResult) { +func (fake *FakeAPIIntersectionReconciler) ReconcileCalls(stub func(cache.APISet, resolver.OperatorGroupSurface, ...resolver.OperatorGroupSurface) resolver.APIReconciliationResult) { fake.reconcileMutex.Lock() defer fake.reconcileMutex.Unlock() fake.ReconcileStub = stub } -func (fake *FakeAPIIntersectionReconciler) ReconcileArgsForCall(i int) (resolver.APISet, resolver.OperatorGroupSurface, []resolver.OperatorGroupSurface) { +func (fake *FakeAPIIntersectionReconciler) ReconcileArgsForCall(i int) (cache.APISet, resolver.OperatorGroupSurface, []resolver.OperatorGroupSurface) { fake.reconcileMutex.RLock() defer fake.reconcileMutex.RUnlock() argsForCall := fake.reconcileArgsForCall[i] diff --git a/staging/operator-registry/.github/workflows/goreleaser.yaml b/staging/operator-registry/.github/workflows/goreleaser.yaml new file mode 100644 index 0000000000..9edf58a161 --- /dev/null +++ b/staging/operator-registry/.github/workflows/goreleaser.yaml @@ -0,0 +1,96 @@ +name: goreleaser +on: + push: + branches: + - 'master' + tags: + - 'v[0-9]+.[0-9]+.[0-9]+' + pull_request: {} +defaults: + run: + shell: bash +jobs: + goreleaser: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + with: + # GoReleaser requires fetch-depth: 0 for + # changelog generation to work correctly. + fetch-depth: 0 + + - uses: actions/setup-go@v2 + with: + go-version: '~1.16' + + - name: "Download osxcross cross-compiler for macOS builds" + run: | + git clone https://github.com/tpoechtrager/osxcross /tmp/osxcross + cd /tmp/osxcross + wget -P tarballs '' + echo "/tmp/osxcross/target/bin" >> $GITHUB_PATH + + - name: "Cache the osxcross + tarball build output" + id: cache-osxcross + uses: actions/cache@v2 + with: + path: /tmp/osxcross/target + key: ${{ runner.os }}-osxcross-${{ hashFiles('/tmp/osxcross/**/*') }} + restore-keys: | + ${{ runner.os }}-osxcross- + + - name: "Build osxcross" + if: steps.cache-osxcross.outputs.cache-hit != 'true' + run: | + cd /tmp/osxcross + sudo ./tools/get_dependencies.sh + UNATTENDED=1 ./build.sh + + - name: "Install linux cross-compilers" + run: sudo apt-get install -y gcc-aarch64-linux-gnu gcc-s390x-linux-gnu gcc-powerpc64le-linux-gnu gcc-mingw-w64-x86-64 + + - name: "Install yq" + run: | + sudo wget https://github.com/mikefarah/yq/releases/download/v4.10.0/yq_linux_amd64 -O /usr/local/bin/yq + sudo chmod +x /usr/local/bin/yq + + - name: "Disable image pushes for pull requests" + if: github.event_name == 'pull_request' + run: | + yq eval '.dockers[].skip_push=true' -i .goreleaser.yaml + yq eval '.docker_manifests[].skip_push=true' -i .goreleaser.yaml + + - name: "Disable the Github release upload for non-tag builds" + if: startsWith(github.ref, 'refs/tags') != true + run: | + yq eval '.release.disable=true' -i .goreleaser.yaml + + - name: "Set the image tag" + run: | + if [[ $GITHUB_REF == refs/tags/* ]]; then + # Release tags. + echo IMAGE_TAG="${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV + elif [[ $GITHUB_REF == refs/heads/* ]]; then + # Branch build. + echo IMAGE_TAG="$(echo "${GITHUB_REF#refs/heads/}" | sed -r 's|/+|-|g')" >> $GITHUB_ENV + elif [[ $GITHUB_REF == refs/pull/* ]]; then + # PR build. + echo IMAGE_TAG="pr-$(echo "${GITHUB_REF}" | sed -E 's|refs/pull/([^/]+)/?.*|\1|')" >> $GITHUB_ENV + else + echo IMAGE_TAG="$(git describe --tags --always)" >> $GITHUB_ENV + fi + + - name: "Login to Quay" + if: github.event_name != 'pull_request' + uses: docker/login-action@v1 + with: + username: ${{ secrets.QUAY_USERNAME }} + password: ${{ secrets.QUAY_PASSWORD }} + registry: quay.io + + + - name: "Run GoReleaser" + run: make release + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + RELEASE_ARGS: release --rm-dist ${{ !startsWith(github.ref, 'refs/tags') && '--skip-validate' || '' }} diff --git a/staging/operator-registry/.github/workflows/release.yaml b/staging/operator-registry/.github/workflows/release.yaml deleted file mode 100644 index 6d05f2f7d5..0000000000 --- a/staging/operator-registry/.github/workflows/release.yaml +++ /dev/null @@ -1,48 +0,0 @@ -name: release -on: - push: - tags: - - 'v[0-9]+.[0-9]+.[0-9]+' -defaults: - run: - shell: bash -jobs: - create: - runs-on: ubuntu-latest - outputs: - upload_url: ${{ steps.release.outputs.upload_url }} - steps: - - uses: actions/create-release@v1 - id: release - env: - GITHUB_TOKEN: ${{ github.token }} - with: - draft: true - tag_name: ${{ github.ref }} - release_name: ${{ github.ref }} - assets: - needs: create - strategy: - matrix: - os: - - ubuntu-latest - - macos-latest - - windows-latest - runs-on: ${{ matrix.os }} - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-go@v2 - with: - go-version: '~1.16' - - run: | - echo "asset_path=bin/opm" >> $GITHUB_ENV - echo "asset_name=$(go env GOOS)-$(go env GOARCH)-opm$(go env GOEXE)" >> $GITHUB_ENV - - run: make ${{ env.asset_path }} - - uses: actions/upload-release-asset@v1 - env: - GITHUB_TOKEN: ${{ github.token }} - with: - upload_url: ${{ needs.create.outputs.upload_url }} - asset_path: ${{ env.asset_path }} - asset_name: ${{ env.asset_name }} - asset_content_type: application/octet-stream diff --git a/staging/operator-registry/.github/workflows/test.yml b/staging/operator-registry/.github/workflows/test.yml index ae2f59afc2..1890895861 100644 --- a/staging/operator-registry/.github/workflows/test.yml +++ b/staging/operator-registry/.github/workflows/test.yml @@ -8,22 +8,25 @@ on: - '**' - '!doc/**' jobs: - e2e-kind: + e2e: runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v2 - uses: actions/setup-go@v2 with: go-version: '~1.16' - - run: | + - name: Install podman + run: | . /etc/os-release echo "deb https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable/xUbuntu_${VERSION_ID}/ /" | sudo tee /etc/apt/sources.list.d/devel:kubic:libcontainers:stable.list curl -L https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable/xUbuntu_${VERSION_ID}/Release.key | sudo apt-key add - sudo apt-get update sudo apt-get -y install conntrack podman podman version - - run: | + - name: Create kind cluster and setup local docker registry + run: | "${GITHUB_WORKSPACE}/scripts/start_registry.sh" kind-registry export DOCKER_REGISTRY_HOST=localhost:443 - - run: | + - name: Run e2e tests + run: | KUBECONFIG="$HOME/.kube/config" DOCKER_REGISTRY_HOST=localhost:443 make build e2e CLUSTER=kind diff --git a/staging/operator-registry/.gitignore b/staging/operator-registry/.gitignore index 0dc911b823..fe176ef932 100644 --- a/staging/operator-registry/.gitignore +++ b/staging/operator-registry/.gitignore @@ -181,8 +181,6 @@ tags # Build results [Dd]ebug/ [Dd]ebugPublic/ -[Rr]elease/ -[Rr]eleases/ x64/ x86/ bld/ diff --git a/staging/operator-registry/.goreleaser.yaml b/staging/operator-registry/.goreleaser.yaml new file mode 100644 index 0000000000..38f0d5b876 --- /dev/null +++ b/staging/operator-registry/.goreleaser.yaml @@ -0,0 +1,195 @@ +builds: + - id: linux-amd64 + main: ./cmd/opm + binary: opm + goos: + - linux + goarch: + - amd64 + env: + - CC=gcc + mod_timestamp: "{{ .CommitTimestamp }}" + flags: &build-flags + - -tags=json1 + asmflags: &build-asmflags + - all=-trimpath={{ .Env.PWD }} + gcflags: &build-gcflags + - all=-trimpath={{ .Env.PWD }} + ldflags: &build-ldflags + - -s -w + - -X {{ .Env.PKG }}/cmd/opm/version.gitCommit={{ .Env.GIT_COMMIT }} + - -X {{ .Env.PKG }}/cmd/opm/version.opmVersion={{ .Env.OPM_VERSION }} + - -X {{ .Env.PKG }}/cmd/opm/version.buildDate={{ .Env.BUILD_DATE }} + - id: linux-arm64 + main: ./cmd/opm + binary: opm + goos: + - linux + goarch: + - arm64 + env: + - CC=aarch64-linux-gnu-gcc + mod_timestamp: "{{ .CommitTimestamp }}" + flags: *build-flags + asmflags: *build-asmflags + gcflags: *build-gcflags + ldflags: *build-ldflags + - id: linux-ppc64le + main: ./cmd/opm + binary: opm + goos: + - linux + goarch: + - ppc64le + env: + - CC=powerpc64le-linux-gnu-gcc + mod_timestamp: "{{ .CommitTimestamp }}" + flags: *build-flags + asmflags: *build-asmflags + gcflags: *build-gcflags + ldflags: *build-ldflags + - id: linux-s390x + main: ./cmd/opm + binary: opm + goos: + - linux + goarch: + - s390x + env: + - CC=s390x-linux-gnu-gcc + mod_timestamp: "{{ .CommitTimestamp }}" + flags: *build-flags + asmflags: *build-asmflags + gcflags: *build-gcflags + ldflags: *build-ldflags + - id: windows-amd64 + main: ./cmd/opm + binary: opm + goos: + - windows + goarch: + - amd64 + env: + - CC=x86_64-w64-mingw32-gcc-posix + mod_timestamp: "{{ .CommitTimestamp }}" + flags: *build-flags + asmflags: *build-asmflags + gcflags: *build-gcflags + ldflags: *build-ldflags + - id: darwin-amd64 + main: ./cmd/opm + binary: opm + goos: + - darwin + goarch: + - amd64 + env: + - CC=o64-clang + mod_timestamp: "{{ .CommitTimestamp }}" + flags: *build-flags + asmflags: *build-asmflags + gcflags: *build-gcflags + ldflags: *build-ldflags + - id: darwin-arm64 + main: ./cmd/opm + binary: opm + goos: + - darwin + goarch: + - arm64 + env: + - CC=oa64e-clang + mod_timestamp: "{{ .CommitTimestamp }}" + flags: *build-flags + asmflags: *build-asmflags + gcflags: *build-gcflags + ldflags: *build-ldflags +archives: + - id: opm + builds: + - linux-amd64 + - linux-arm64 + - linux-ppc64le + - linux-s390x + - windows-amd64 + - darwin-amd64 + - darwin-arm64 + name_template: "{{ .Binary }}_{{ .Env.OPM_VERSION }}_{{ .Os }}_{{ .Arch }}" + format: binary +dockers: + - image_templates: + - "{{ .Env.OPM_IMAGE_REPO }}:{{ .Env.IMAGE_TAG }}-amd64" + ids: ["linux-amd64"] + goos: linux + goarch: amd64 + dockerfile: release/goreleaser.opm.Dockerfile + extra_files: ["nsswitch.conf"] + use: buildx + build_flag_templates: + - --platform=linux/amd64 + - image_templates: + - "{{ .Env.OPM_IMAGE_REPO }}:{{ .Env.IMAGE_TAG }}-arm64" + ids: ["linux-arm64"] + goos: linux + goarch: arm64 + dockerfile: release/goreleaser.opm.Dockerfile + extra_files: ["nsswitch.conf"] + use: buildx + build_flag_templates: + - --platform=linux/arm64 + - image_templates: + - "{{ .Env.OPM_IMAGE_REPO }}:{{ .Env.IMAGE_TAG }}-ppc64le" + ids: ["linux-ppc64le"] + goos: linux + goarch: ppc64le + dockerfile: release/goreleaser.opm.Dockerfile + extra_files: ["nsswitch.conf"] + use: buildx + build_flag_templates: + - --platform=linux/ppc64le + - image_templates: + - "{{ .Env.OPM_IMAGE_REPO }}:{{ .Env.IMAGE_TAG }}-s390x" + ids: ["linux-s390x"] + goos: linux + goarch: s390x + dockerfile: release/goreleaser.opm.Dockerfile + extra_files: ["nsswitch.conf"] + use: buildx + build_flag_templates: + - --platform=linux/s390x +docker_manifests: + # IMAGE_TAG is either set by the Makefile or the goreleaser action workflow, + # This image is intended to be tagged/pushed on all trunk (master, release branch) commits and tags. + - name_template: "{{ .Env.OPM_IMAGE_REPO }}:{{ .Env.IMAGE_TAG }}" + image_templates: + - "{{ .Env.OPM_IMAGE_REPO }}:{{ .Env.IMAGE_TAG }}-amd64" + - "{{ .Env.OPM_IMAGE_REPO }}:{{ .Env.IMAGE_TAG }}-arm64" + - "{{ .Env.OPM_IMAGE_REPO }}:{{ .Env.IMAGE_TAG }}-ppc64le" + - "{{ .Env.OPM_IMAGE_REPO }}:{{ .Env.IMAGE_TAG }}-s390x" + # Release image builds will be skipped if *_IMAGE_OR_EMPTY variables are empty. + # https://github.com/goreleaser/goreleaser/blob/9ed3c0c/internal/pipe/docker/manifest.go#L105 + - name_template: "{{ .Env.MAJ_MIN_IMAGE_OR_EMPTY }}" + image_templates: + - "{{ .Env.OPM_IMAGE_REPO }}:{{ .Env.IMAGE_TAG }}-amd64" + - "{{ .Env.OPM_IMAGE_REPO }}:{{ .Env.IMAGE_TAG }}-arm64" + - "{{ .Env.OPM_IMAGE_REPO }}:{{ .Env.IMAGE_TAG }}-ppc64le" + - "{{ .Env.OPM_IMAGE_REPO }}:{{ .Env.IMAGE_TAG }}-s390x" + - name_template: "{{ .Env.MAJ_IMAGE_OR_EMPTY }}" + image_templates: + - "{{ .Env.OPM_IMAGE_REPO }}:{{ .Env.IMAGE_TAG }}-amd64" + - "{{ .Env.OPM_IMAGE_REPO }}:{{ .Env.IMAGE_TAG }}-arm64" + - "{{ .Env.OPM_IMAGE_REPO }}:{{ .Env.IMAGE_TAG }}-ppc64le" + - "{{ .Env.OPM_IMAGE_REPO }}:{{ .Env.IMAGE_TAG }}-s390x" + - name_template: "{{ .Env.LATEST_IMAGE_OR_EMPTY }}" + image_templates: + - "{{ .Env.OPM_IMAGE_REPO }}:{{ .Env.IMAGE_TAG }}-amd64" + - "{{ .Env.OPM_IMAGE_REPO }}:{{ .Env.IMAGE_TAG }}-arm64" + - "{{ .Env.OPM_IMAGE_REPO }}:{{ .Env.IMAGE_TAG }}-ppc64le" + - "{{ .Env.OPM_IMAGE_REPO }}:{{ .Env.IMAGE_TAG }}-s390x" +checksum: + name_template: 'checksums.txt' +snapshot: + name_template: "{{ .Env.OPM_VERSION }}" +release: + name_template: "{{ .Env.OPM_VERSION }}" + draft: true diff --git a/staging/operator-registry/Makefile b/staging/operator-registry/Makefile index bd586974e9..9cc53655f6 100644 --- a/staging/operator-registry/Makefile +++ b/staging/operator-registry/Makefile @@ -2,10 +2,10 @@ GO := GOFLAGS="-mod=vendor" go CMDS := $(addprefix bin/, $(shell ls ./cmd | grep -v opm)) OPM := $(addprefix bin/, opm) SPECIFIC_UNIT_TEST := $(if $(TEST),-run $(TEST),) -PKG := github.com/operator-framework/operator-registry -GIT_COMMIT := $(or $(SOURCE_GIT_COMMIT),$(shell git rev-parse --short HEAD)) -OPM_VERSION := $(or $(SOURCE_GIT_TAG),$(shell git describe --always --tags HEAD)) -BUILD_DATE := $(shell date -u +'%Y-%m-%dT%H:%M:%SZ') +export PKG := github.com/operator-framework/operator-registry +export GIT_COMMIT := $(or $(SOURCE_GIT_COMMIT),$(shell git rev-parse --short HEAD)) +export OPM_VERSION := $(or $(SOURCE_GIT_TAG),$(shell git describe --always --tags HEAD)) +export BUILD_DATE := $(shell date -u +'%Y-%m-%dT%H:%M:%SZ') # define characters null := @@ -14,7 +14,7 @@ comma := , # default to json1 for sqlite3 TAGS := -tags=json1 -# Cluster to use for e2e testing +# Cluster to use for e2e testing CLUSTER ?= "" ifeq ($(CLUSTER), kind) # add kind to the list of tags @@ -113,4 +113,38 @@ clean: .PHONY: e2e e2e: - $(GO) run github.com/onsi/ginkgo/ginkgo --v --randomizeAllSpecs --randomizeSuites --race $(if $(TEST),-focus '$(TEST)') $(TAGS) ./test/e2e -- $(if $(SKIPTLS),-skip-tls true) + $(GO) run github.com/onsi/ginkgo/ginkgo --v --randomizeAllSpecs --randomizeSuites --race $(if $(TEST),-focus '$(TEST)') $(TAGS) ./test/e2e -- $(if $(SKIPTLS),-skip-tls true) + + +.PHONY: release +export OPM_IMAGE_REPO ?= quay.io/operator-framework/opm +export IMAGE_TAG ?= $(OPM_VERSION) +export MAJ_MIN_IMAGE_OR_EMPTY ?= $(call tagged-or-empty,$(shell echo $(OPM_VERSION) | grep -Eo 'v[0-9]+\.[0-9]+')) +export MAJ_IMAGE_OR_EMPTY ?= $(call tagged-or-empty,$(shell echo $(OPM_VERSION) | grep -Eo 'v[0-9]+')) +# LATEST_TAG is the latest semver tag in HEAD. Used to deduce whether +# OPM_VERSION is the new latest tag, or a prior minor/patch tag, below. +# NOTE: this can only be relied upon if full git history is present. +# An actions/checkout step must use "fetch-depth: 0", for example. +LATEST_TAG := $(shell git tag -l | tr - \~ | sort -V | tr \~ - | tail -n1) +# LATEST_IMAGE_OR_EMPTY is set to OPM_IMAGE_REPO:latest when OPM_VERSION +# is not a prerelase tag and == LATEST_TAG, otherwise the empty string. +# An empty string causes goreleaser to skip building the manifest image for latest, +# which we do not want when cutting a non-latest release (old minor/patch tag). +export LATEST_IMAGE_OR_EMPTY ?= $(shell \ + echo $(OPM_VERSION) | grep -Eq '^v[0-9]+\.[0-9]+\.[0-9]+$$' \ + && [ "$(shell echo -e "$(OPM_VERSION)\n$(LATEST_TAG)" | sort -rV | head -n1)" == "$(OPM_VERSION)" ] \ + && echo "$(OPM_IMAGE_REPO):latest" || echo "") +release: RELEASE_ARGS ?= release --rm-dist --snapshot +release: + ./scripts/fetch goreleaser 0.173.2 && ./bin/goreleaser $(RELEASE_ARGS) + +# tagged-or-empty returns $(OPM_IMAGE_REPO):$(1) when HEAD is assigned a non-prerelease semver tag, +# otherwise the empty string. An empty string causes goreleaser to skip building +# the manifest image for a trunk commit when it is not a release commit. +# In other words, this function will return "" if the tag is not in vX.Y.Z format. +define tagged-or-empty +$(shell \ + echo $(OPM_VERSION) | grep -Eq '^v[0-9]+\.[0-9]+\.[0-9]+$$' \ + && git describe --exact-match HEAD >/dev/null 2>&1 \ + && echo "$(OPM_IMAGE_REPO):$(1)" || echo "" ) +endef diff --git a/staging/operator-registry/cmd/opm/index/deprecatetruncate.go b/staging/operator-registry/cmd/opm/index/deprecatetruncate.go index 4413440a6f..dc76e9ab9c 100644 --- a/staging/operator-registry/cmd/opm/index/deprecatetruncate.go +++ b/staging/operator-registry/cmd/opm/index/deprecatetruncate.go @@ -26,7 +26,9 @@ var deprecateLong = templates.LongDesc(` Produces the following update graph in quay.io/my/index:v2 1.4.0 -- replaces -> 1.3.0 [deprecated] - Deprecating a bundle that removes the default channel is not allowed. Changing the default channel prior to deprecation is possible by publishing a new bundle to the index. + Deprecating a bundle that removes the default channel is not allowed unless the head(s) of all channels are being deprecated (the package is subsequently removed from the index). + This behavior can be enabled via the allow-package-removal flag. + Changing the default channel prior to deprecation is possible by publishing a new bundle to the index. `) + "\n\n" + sqlite.DeprecationMessage func newIndexDeprecateTruncateCmd() *cobra.Command { @@ -61,6 +63,7 @@ func newIndexDeprecateTruncateCmd() *cobra.Command { if err := indexCmd.Flags().MarkHidden("debug"); err != nil { logrus.Panic(err.Error()) } + indexCmd.Flags().Bool("allow-package-removal", false, "removes the entire package if the heads of all channels in the package are deprecated") return indexCmd } @@ -111,6 +114,11 @@ func runIndexDeprecateTruncateCmdFunc(cmd *cobra.Command, args []string) error { return err } + allowPackageRemoval, err := cmd.Flags().GetBool("allow-package-removal") + if err != nil { + return err + } + logger := logrus.WithFields(logrus.Fields{"bundles": bundles}) logger.Info("deprecating bundles from the index") @@ -121,14 +129,15 @@ func runIndexDeprecateTruncateCmdFunc(cmd *cobra.Command, args []string) error { logger) request := indexer.DeprecateFromIndexRequest{ - Generate: generate, - FromIndex: fromIndex, - BinarySourceImage: binaryImage, - OutDockerfile: outDockerfile, - Tag: tag, - Bundles: bundles, - Permissive: permissive, - SkipTLS: skipTLS, + Generate: generate, + FromIndex: fromIndex, + BinarySourceImage: binaryImage, + OutDockerfile: outDockerfile, + Tag: tag, + Bundles: bundles, + Permissive: permissive, + SkipTLS: skipTLS, + AllowPackageRemoval: allowPackageRemoval, } err = indexDeprecator.DeprecateFromIndex(request) diff --git a/staging/operator-registry/go.mod b/staging/operator-registry/go.mod index f4e40875e0..94d1d1bbc6 100644 --- a/staging/operator-registry/go.mod +++ b/staging/operator-registry/go.mod @@ -7,7 +7,7 @@ require ( github.com/blang/semver v3.5.1+incompatible github.com/bugsnag/bugsnag-go v1.5.3 // indirect github.com/bugsnag/panicwrap v1.2.0 // indirect - github.com/containerd/containerd v1.3.2 + github.com/containerd/containerd v1.4.8 github.com/containerd/continuity v0.0.0-20200413184840-d3ef23f19fbb // indirect github.com/containerd/ttrpc v1.0.1 // indirect github.com/docker/cli v0.0.0-20200130152716-5d0cf8839492 @@ -36,7 +36,6 @@ require ( github.com/onsi/gomega v1.13.0 github.com/opencontainers/go-digest v1.0.0 github.com/opencontainers/image-spec v1.0.2-0.20190823105129-775207bd45b6 - github.com/opencontainers/runc v0.1.1 // indirect github.com/operator-framework/api v0.7.1 github.com/otiai10/copy v1.2.0 github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2 @@ -66,13 +65,5 @@ require ( sigs.k8s.io/yaml v1.2.0 ) -replace ( - // Currently on a fork for two issues: - // 1. stage registry proxy didn't like requests with no scopes, see https://github.com/containerd/containerd/pull/4223 - // 2. prod registry proxy returns a 403 on post, see https://github.com/containerd/containerd/pull/3913 - // The fork can be removed when both issues are resolved in a release, which should be 1.4.0 - github.com/containerd/containerd => github.com/ecordell/containerd v1.3.1-0.20200629153125-0ff1a1be2fa5 - - // latest tag resolves to a very old version. this is only used for spinning up local test registries - github.com/docker/distribution => github.com/docker/distribution v0.0.0-20191216044856-a8371794149d -) +// latest tag resolves to a very old version. this is only used for spinning up local test registries +replace github.com/docker/distribution => github.com/docker/distribution v0.0.0-20191216044856-a8371794149d diff --git a/staging/operator-registry/go.sum b/staging/operator-registry/go.sum index 5ee86f2706..79e8263309 100644 --- a/staging/operator-registry/go.sum +++ b/staging/operator-registry/go.sum @@ -150,6 +150,10 @@ github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f/go.mod h1:i/u9 github.com/containerd/cgroups v0.0.0-20190919134610-bf292b21730f h1:tSNMc+rJDfmYntojat8lljbt1mgKNpTxUZJsSzJ9Y1s= github.com/containerd/cgroups v0.0.0-20190919134610-bf292b21730f/go.mod h1:OApqhQ4XNSNC13gXIwDjhOQxjWa/NxkwZXJ1EvqT0ko= github.com/containerd/console v0.0.0-20180822173158-c12b1e7919c1/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw= +github.com/containerd/containerd v1.2.7/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.3.2/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.4.8 h1:H0wkS4AbVKTg9vyvBdCBrxoax8AMObKbNz9Fl2N0i4Y= +github.com/containerd/containerd v1.4.8/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= github.com/containerd/continuity v0.0.0-20200413184840-d3ef23f19fbb h1:nXPkFq8X1a9ycY3GYQpFNxHh3j2JgY7zDZfq2EXMIzk= github.com/containerd/continuity v0.0.0-20200413184840-d3ef23f19fbb/go.mod h1:Dq467ZllaHgAtVp4p1xUQWBrFXR9s/wyoTpG8zOJGkY= @@ -231,8 +235,6 @@ github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25Kn github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= -github.com/ecordell/containerd v1.3.1-0.20200629153125-0ff1a1be2fa5 h1:Jaqyo6dNoi2RlsZuZIXtFL6PZ+2X9vK+iwRtzG2/zYE= -github.com/ecordell/containerd v1.3.1-0.20200629153125-0ff1a1be2fa5/go.mod h1:bzKmwSHFIW6VylTyruXdGKbiAE3GhZV13uRlfLxy75s= github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153 h1:yUdfgN0XgIJw7foRItutHYUIhlcKzcSf5vDpdhQAKTc= github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= @@ -671,8 +673,6 @@ github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zM github.com/opencontainers/image-spec v1.0.2-0.20190823105129-775207bd45b6 h1:yN8BPXVwMBAm3Cuvh1L5XE8XpvYRMdsVLd82ILprhUU= github.com/opencontainers/image-spec v1.0.2-0.20190823105129-775207bd45b6/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= github.com/opencontainers/runc v0.0.0-20190115041553-12f6a991201f/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= -github.com/opencontainers/runc v0.1.1 h1:GlxAyO6x8rfZYN9Tt0Kti5a/cP41iuiO2yYT0IJGY8Y= -github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= github.com/opencontainers/runtime-spec v0.1.2-0.20190507144316-5b71a03e2700 h1:eNUVfm/RFLIi1G7flU5/ZRTHvd4kcVuzfRnL6OFlzCI= github.com/opencontainers/runtime-spec v0.1.2-0.20190507144316-5b71a03e2700/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= diff --git a/staging/operator-registry/internal/declcfg/load.go b/staging/operator-registry/internal/declcfg/load.go index bc9c23e2cd..c53c61d7e9 100644 --- a/staging/operator-registry/internal/declcfg/load.go +++ b/staging/operator-registry/internal/declcfg/load.go @@ -17,6 +17,10 @@ import ( "github.com/operator-framework/operator-registry/internal/property" ) +const ( + indexIgnoreFilename = ".indexignore" +) + type WalkFunc func(path string, cfg *DeclarativeConfig, err error) error // WalkFS walks root using a gitignore-style filename matcher to skip files @@ -27,7 +31,7 @@ func WalkFS(root fs.FS, walkFn WalkFunc) error { if root == nil { return fmt.Errorf("no declarative config filesystem provided") } - matcher, err := ignore.NewMatcher(root, ".indexignore") + matcher, err := ignore.NewMatcher(root, indexIgnoreFilename) if err != nil { return err } @@ -36,7 +40,9 @@ func WalkFS(root fs.FS, walkFn WalkFunc) error { if err != nil { return walkFn(path, nil, err) } - if info.IsDir() || matcher.Match(path, false) { + // avoid validating a directory, an .indexignore file, or any file that matches + // an ignore pattern outlined in a .indexignore file. + if info.IsDir() || info.Name() == indexIgnoreFilename || matcher.Match(path, false) { return nil } file, err := root.Open(path) diff --git a/staging/operator-registry/pkg/lib/bundle/supported_resources.go b/staging/operator-registry/pkg/lib/bundle/supported_resources.go index 534e6568c1..20d7e5402e 100644 --- a/staging/operator-registry/pkg/lib/bundle/supported_resources.go +++ b/staging/operator-registry/pkg/lib/bundle/supported_resources.go @@ -17,6 +17,9 @@ const ( PriorityClassKind = "PriorityClass" VerticalPodAutoscalerKind = "VerticalPodAutoscaler" ConsoleYamlSampleKind = "ConsoleYamlSample" + ConsoleQuickStartKind = "ConsoleQuickStart" + ConsoleCLIDownloadKind = "ConsoleCLIDownload" + ConsoleLinkKind = "ConsoleLink" ) // Namespaced indicates whether the resource is namespace scoped (true) or cluster-scoped (false). @@ -41,6 +44,9 @@ var supportedResources = map[string]Namespaced{ PriorityClassKind: false, VerticalPodAutoscalerKind: false, ConsoleYamlSampleKind: false, + ConsoleQuickStartKind: false, + ConsoleCLIDownloadKind: false, + ConsoleLinkKind: false, } // IsSupported checks if the object kind is OLM-supported and if it is namespaced diff --git a/staging/operator-registry/pkg/lib/indexer/indexer.go b/staging/operator-registry/pkg/lib/indexer/indexer.go index d15e3472d2..54eae4db1a 100644 --- a/staging/operator-registry/pkg/lib/indexer/indexer.go +++ b/staging/operator-registry/pkg/lib/indexer/indexer.go @@ -643,15 +643,16 @@ func generatePackageYaml(dbQuerier pregistry.Query, packageName, downloadPath st // DeprecateFromIndexRequest defines the parameters to send to the PruneFromIndex API type DeprecateFromIndexRequest struct { - Generate bool - Permissive bool - BinarySourceImage string - FromIndex string - OutDockerfile string - Bundles []string - Tag string - CaFile string - SkipTLS bool + Generate bool + Permissive bool + BinarySourceImage string + FromIndex string + OutDockerfile string + Bundles []string + Tag string + CaFile string + SkipTLS bool + AllowPackageRemoval bool } // DeprecateFromIndex takes a DeprecateFromIndexRequest and deprecates the requested @@ -668,14 +669,14 @@ func (i ImageIndexer) DeprecateFromIndex(request DeprecateFromIndexRequest) erro return err } - // Run opm registry prune on the database deprecateFromRegistryReq := registry.DeprecateFromRegistryRequest{ - Bundles: request.Bundles, - InputDatabase: databasePath, - Permissive: request.Permissive, + Bundles: request.Bundles, + InputDatabase: databasePath, + Permissive: request.Permissive, + AllowPackageRemoval: request.AllowPackageRemoval, } - // Prune the bundles from the registry + // Deprecate the bundles from the registry err = i.RegistryDeprecator.DeprecateFromRegistry(deprecateFromRegistryReq) if err != nil { return err diff --git a/staging/operator-registry/pkg/lib/registry/registry.go b/staging/operator-registry/pkg/lib/registry/registry.go index 34dc493e2c..e8ec05cce2 100644 --- a/staging/operator-registry/pkg/lib/registry/registry.go +++ b/staging/operator-registry/pkg/lib/registry/registry.go @@ -331,9 +331,10 @@ func (r RegistryUpdater) PruneFromRegistry(request PruneFromRegistryRequest) err } type DeprecateFromRegistryRequest struct { - Permissive bool - InputDatabase string - Bundles []string + Permissive bool + InputDatabase string + Bundles []string + AllowPackageRemoval bool } func (r RegistryUpdater) DeprecateFromRegistry(request DeprecateFromRegistryRequest) error { @@ -366,6 +367,23 @@ func (r RegistryUpdater) DeprecateFromRegistry(request DeprecateFromRegistryRequ } deprecator := sqlite.NewSQLDeprecatorForBundles(dbLoader, toDeprecate) + + // Check for deprecation of head of default channel. If deprecation request includes heads of all other channels, + // then remove the package entirely. Otherwise, deprecate provided bundles. This enables deprecating an entire package. + // By default deprecating the head of default channel is not permitted. + if request.AllowPackageRemoval { + packageDeprecator := sqlite.NewSQLDeprecatorForBundlesAndPackages(deprecator, dbQuerier) + if err := packageDeprecator.MaybeRemovePackages(); err != nil { + r.Logger.Debugf("unable to deprecate package from database: %s", err) + if !request.Permissive { + r.Logger.WithError(err).Error("permissive mode disabled") + return err + } + r.Logger.WithError(err).Warn("permissive mode enabled") + } + } + + // Any bundles associated with removed packages are now removed from the list of bundles to deprecate. if err := deprecator.Deprecate(); err != nil { r.Logger.Debugf("unable to deprecate bundles from database: %s", err) if !request.Permissive { diff --git a/staging/operator-registry/pkg/registry/populator_test.go b/staging/operator-registry/pkg/registry/populator_test.go index dc22a5f759..280907964c 100644 --- a/staging/operator-registry/pkg/registry/populator_test.go +++ b/staging/operator-registry/pkg/registry/populator_test.go @@ -943,6 +943,179 @@ func TestDeprecateBundle(t *testing.T) { } } +func TestDeprecatePackage(t *testing.T) { + type args struct { + bundles []string + } + type pkgChannel map[string][]string + type expected struct { + err error + remainingBundles []string + deprecatedBundles []string + remainingPkgChannels pkgChannel + } + tests := []struct { + description string + args args + expected expected + }{ + { + description: "RemoveEntirePackage/BundlesAreAllHeadsOfChannels", + args: args{ + bundles: []string{ + "quay.io/test/etcd.0.9.2", + "quay.io/test/etcd.0.9.0", + }, + }, + expected: expected{ + err: nil, + remainingBundles: []string{ + "quay.io/test/prometheus.0.22.2/preview", + "quay.io/test/prometheus.0.15.0/preview", + "quay.io/test/prometheus.0.15.0/stable", + "quay.io/test/prometheus.0.14.0/preview", + "quay.io/test/prometheus.0.14.0/stable", + }, + deprecatedBundles: []string{}, + remainingPkgChannels: pkgChannel{ + "prometheus": []string{ + "preview", + "stable", + }, + }, + }, + }, + { + description: "RemoveHeadOfDefaultChannelWithoutAllChannelHeads/Error", + args: args{ + bundles: []string{ + "quay.io/test/etcd.0.9.2", + }, + }, + expected: expected{ + err: utilerrors.NewAggregate([]error{fmt.Errorf("cannot deprecate default channel head from package without removing all other channel heads in package %s: must deprecate %s, head of channel %s", "etcd", "quay.io/test/etcd.0.9.0", "beta")}), + remainingBundles: []string{ + "quay.io/test/etcd.0.9.0/alpha", + "quay.io/test/etcd.0.9.0/beta", + "quay.io/test/etcd.0.9.0/stable", + "quay.io/test/etcd.0.9.2/stable", + "quay.io/test/etcd.0.9.2/alpha", + "quay.io/test/prometheus.0.14.0/preview", + "quay.io/test/prometheus.0.14.0/stable", + "quay.io/test/prometheus.0.15.0/preview", + "quay.io/test/prometheus.0.15.0/stable", + "quay.io/test/prometheus.0.22.2/preview", + }, + deprecatedBundles: []string{}, + remainingPkgChannels: pkgChannel{ + "etcd": []string{ + "alpha", + "beta", + "stable", + }, + "prometheus": []string{ + "preview", + "stable", + }, + }, + }, + }, + { + description: "RemoveEntirePackage/AndDeprecateAdditionalBundle", + args: args{ + bundles: []string{ + "quay.io/test/etcd.0.9.2", + "quay.io/test/etcd.0.9.0", + "quay.io/test/prometheus.0.14.0", + }, + }, + expected: expected{ + err: nil, + remainingBundles: []string{ + "quay.io/test/prometheus.0.22.2/preview", + "quay.io/test/prometheus.0.15.0/preview", + "quay.io/test/prometheus.0.15.0/stable", + "quay.io/test/prometheus.0.14.0/preview", + "quay.io/test/prometheus.0.14.0/stable", + }, + deprecatedBundles: []string{ + "quay.io/test/prometheus.0.14.0/preview", + "quay.io/test/prometheus.0.14.0/stable", + }, + remainingPkgChannels: pkgChannel{ + "prometheus": []string{ + "preview", + "stable", + }, + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + logrus.SetLevel(logrus.DebugLevel) + db, cleanup := CreateTestDb(t) + defer cleanup() + + querier, err := createAndPopulateDB(db) + require.NoError(t, err) + + store, err := sqlite.NewSQLLiteLoader(db) + require.NoError(t, err) + + deprecator := sqlite.NewSQLDeprecatorForBundles(store, tt.args.bundles) + packageDeprecator := sqlite.NewSQLDeprecatorForBundlesAndPackages(deprecator, querier) + require.Equal(t, tt.expected.err, packageDeprecator.MaybeRemovePackages()) + if len(tt.expected.deprecatedBundles) > 0 { + require.Equal(t, tt.expected.err, deprecator.Deprecate()) + } + + // Ensure remaining bundlePaths in db match + bundles, err := querier.ListBundles(context.Background()) + require.NoError(t, err) + var bundlePaths []string + for _, bundle := range bundles { + bundlePaths = append(bundlePaths, strings.Join([]string{bundle.BundlePath, bundle.ChannelName}, "/")) + } + require.ElementsMatch(t, tt.expected.remainingBundles, bundlePaths) + + // Ensure deprecated bundles match + var deprecatedBundles []string + deprecatedProperty, err := json.Marshal(registry.DeprecatedProperty{}) + require.NoError(t, err) + for _, bundle := range bundles { + for _, prop := range bundle.Properties { + if prop.Type == registry.DeprecatedType && prop.Value == string(deprecatedProperty) { + deprecatedBundles = append(deprecatedBundles, strings.Join([]string{bundle.BundlePath, bundle.ChannelName}, "/")) + } + } + } + + require.ElementsMatch(t, tt.expected.deprecatedBundles, deprecatedBundles) + + // Ensure remaining channels match + packages, err := querier.ListPackages(context.Background()) + require.NoError(t, err) + + for _, pkg := range packages { + channelEntries, err := querier.GetChannelEntriesFromPackage(context.Background(), pkg) + require.NoError(t, err) + + uniqueChannels := make(map[string]struct{}) + var channels []string + for _, ch := range channelEntries { + uniqueChannels[ch.ChannelName] = struct{}{} + } + for k := range uniqueChannels { + channels = append(channels, k) + } + require.ElementsMatch(t, tt.expected.remainingPkgChannels[pkg], channels) + } + }) + } +} + func TestAddAfterDeprecate(t *testing.T) { type args struct { existing []string diff --git a/staging/operator-registry/pkg/sqlite/deprecate.go b/staging/operator-registry/pkg/sqlite/deprecate.go index a8cdb24fa9..4ac3d61ebf 100644 --- a/staging/operator-registry/pkg/sqlite/deprecate.go +++ b/staging/operator-registry/pkg/sqlite/deprecate.go @@ -1,6 +1,7 @@ package sqlite import ( + "context" "errors" "fmt" @@ -20,7 +21,14 @@ type BundleDeprecator struct { bundles []string } +// PackageDeprecator removes bundles and optionally entire packages from the index +type PackageDeprecator struct { + *BundleDeprecator + querier *SQLQuerier +} + var _ SQLDeprecator = &BundleDeprecator{} +var _ SQLDeprecator = &PackageDeprecator{} func NewSQLDeprecatorForBundles(store registry.Load, bundles []string) *BundleDeprecator { return &BundleDeprecator{ @@ -29,6 +37,13 @@ func NewSQLDeprecatorForBundles(store registry.Load, bundles []string) *BundleDe } } +func NewSQLDeprecatorForBundlesAndPackages(deprecator *BundleDeprecator, querier *SQLQuerier) *PackageDeprecator { + return &PackageDeprecator{ + BundleDeprecator: deprecator, + querier: querier, + } +} + func (d *BundleDeprecator) Deprecate() error { log := logrus.WithField("bundles", d.bundles) log.Info("deprecating bundles") @@ -45,3 +60,87 @@ func (d *BundleDeprecator) Deprecate() error { return utilerrors.NewAggregate(errs) } + +// MaybeRemovePackages queries the DB to establish if any provided bundles are the head of the default channel of a package. +// If so, the list of bundles must also contain the head of all other channels in the package, otherwise an error is produced. +// If the heads of all channels are being deprecated (including the default channel), the package is removed entirely from the index. +// MaybeRemovePackages deletes all bundles from the associated package from the bundles array, so that the subsequent +// Deprecate() call can proceed with deprecating other potential bundles from other packages. +func (d *PackageDeprecator) MaybeRemovePackages() error { + log := logrus.WithField("bundles", d.bundles) + log.Info("allow-package-removal enabled: checking default channel heads for package removal") + + var errs []error + var removedBundlePaths []string + var remainingBundlePaths []string + + // Iterate over bundles list - see if any bundle is the head of a default channel in a package + var packages []string + for _, bundle := range d.bundles { + found, err := d.querier.PackageFromDefaultChannelHeadBundle(context.TODO(), bundle) + if err != nil { + errs = append(errs, fmt.Errorf("error checking if bundle is default channel head %s: %s", bundle, err)) + } + if found != "" { + packages = append(packages, found) + } + } + + if len(packages) == 0 { + log.Info("no head of default channel found - skipping package removal") + return nil + } + + // If so, ensure list contains head of all other channels in that package + // If not, return error + for _, pkg := range packages { + channels, err := d.querier.ListChannels(context.TODO(), pkg) + if err != nil { + errs = append(errs, fmt.Errorf("error listing channels for package %s: %s", pkg, err)) + } + for _, channel := range channels { + found, err := d.querier.BundlePathForChannelHead(context.TODO(), pkg, channel) + if err != nil { + errs = append(errs, fmt.Errorf("error listing channel head for package %s: %s", pkg, err)) + } + if !contains(found, d.bundles) { + // terminal error + errs = append(errs, fmt.Errorf("cannot deprecate default channel head from package without removing all other channel heads in package %s: must deprecate %s, head of channel %s", pkg, found, channel)) + return utilerrors.NewAggregate(errs) + } + removedBundlePaths = append(removedBundlePaths, found) + } + } + + // Remove associated package from index + log.Infof("removing packages %#v", packages) + for _, pkg := range packages { + err := d.store.RemovePackage(pkg) + if err != nil { + errs = append(errs, fmt.Errorf("error removing package %s: %s", pkg, err)) + } + } + + // Remove bundles from the removed package from the deprecation request + // This enables other bundles to be deprecated via the expected flow + // Build a new array with just the outstanding bundles + for _, bundlePath := range d.bundles { + if contains(bundlePath, removedBundlePaths) { + continue + } + remainingBundlePaths = append(remainingBundlePaths, bundlePath) + } + d.bundles = remainingBundlePaths + log.Infof("remaining bundles to deprecate %#v", d.bundles) + + return utilerrors.NewAggregate(errs) +} + +func contains(bundlePath string, bundles []string) bool { + for _, b := range bundles { + if b == bundlePath { + return true + } + } + return false +} diff --git a/staging/operator-registry/pkg/sqlite/query.go b/staging/operator-registry/pkg/sqlite/query.go index 6a30c35bfe..432b998112 100644 --- a/staging/operator-registry/pkg/sqlite/query.go +++ b/staging/operator-registry/pkg/sqlite/query.go @@ -1338,3 +1338,57 @@ func (s *SQLQuerier) listBundleChannels(ctx context.Context, bundleName string) return channels, nil } + +// PackageFromDefaultChannelHeadBundle returns the package name if the provided bundle is the head of its default channel. +func (s *SQLQuerier) PackageFromDefaultChannelHeadBundle(ctx context.Context, bundle string) (string, error) { + packageFromDefaultChannelHeadBundle := ` + SELECT package_name FROM package + INNER JOIN channel ON channel.name = package.default_channel + WHERE channel.head_operatorbundle_name = (SELECT name FROM operatorbundle WHERE bundlepath=? LIMIT 1) ` + + rows, err := s.db.QueryContext(ctx, packageFromDefaultChannelHeadBundle, bundle) + if err != nil { + return "", err + } + defer rows.Close() + + var packageName sql.NullString + for rows.Next() { + if err := rows.Scan(&packageName); err != nil { + return "", err + } + + if !packageName.Valid { + return "", fmt.Errorf("package name column corrupt for bundle %s", bundle) + } + } + + return packageName.String, nil +} + +// BundlePathForChannelHead returns the bundlepath for the given package and channel +func (s *SQLQuerier) BundlePathForChannelHead(ctx context.Context, pkg string, channel string) (string, error) { + bundlePathForChannelHeadQuery := ` + SELECT bundlepath FROM operatorbundle + INNER JOIN channel ON channel.head_operatorbundle_name = operatorbundle.name + WHERE channel.package_name = ? AND channel.name = ? +` + + rows, err := s.db.QueryContext(ctx, bundlePathForChannelHeadQuery, pkg, channel) + if err != nil { + return "", err + } + defer rows.Close() + + var bundlePath sql.NullString + for rows.Next() { + if err := rows.Scan(&bundlePath); err != nil { + return "", err + } + if !bundlePath.Valid { + return "", fmt.Errorf("bundlepath column corrupt for package %s, channel %s", pkg, channel) + } + } + + return bundlePath.String, nil +} diff --git a/staging/operator-registry/release/goreleaser.opm.Dockerfile b/staging/operator-registry/release/goreleaser.opm.Dockerfile new file mode 100644 index 0000000000..eac78673c8 --- /dev/null +++ b/staging/operator-registry/release/goreleaser.opm.Dockerfile @@ -0,0 +1,10 @@ +# NOTE: This Dockerfile is used in conjuction with GoReleaser to +# build opm images. See the configurations in .goreleaser.yaml +# and .github/workflows/release.yaml. + +FROM --platform=$BUILDPLATFORM ghcr.io/grpc-ecosystem/grpc-health-probe:v0.4.4 as grpc_health_probe +FROM gcr.io/distroless/base:debug +COPY --from=grpc_health_probe /grpc_health_probe /bin/grpc_health_probe +COPY ["nsswitch.conf", "/etc/nsswitch.conf"] +COPY opm /bin/opm +ENTRYPOINT ["/bin/opm"] diff --git a/staging/operator-registry/scripts/fetch b/staging/operator-registry/scripts/fetch new file mode 100755 index 0000000000..ac383f67d2 --- /dev/null +++ b/staging/operator-registry/scripts/fetch @@ -0,0 +1,31 @@ +#!/usr/bin/env bash + +ROOT="$( git rev-parse --show-toplevel )" + +fetch() { + local tool=$1; shift + local ver=$1; shift + + local ver_cmd="" + local tool_fetch_cmd="" + case "$tool" in + "golangci-lint") + ver_cmd="${ROOT}/bin/golangci-lint --version 2>/dev/null | cut -d\" \" -f4" + fetch_cmd="curl -sSfL \"https://raw.githubusercontent.com/golangci/golangci-lint/v${ver}/install.sh\" | sh -s -- -b \"${ROOT}/bin\" \"v${ver}\"" + ;; + "goreleaser") + ver_cmd="${ROOT}/bin/goreleaser --version 2>/dev/null | grep 'goreleaser version' | cut -d' ' -f3" + fetch_cmd="curl -sSfL https://install.goreleaser.com/github.com/goreleaser/goreleaser.sh | sh -s -- -b \"${ROOT}/bin\" -d \"v${ver}\"" + ;; + *) + echo "unknown tool $tool" + return 1 + ;; + esac + + if [[ "${ver}" != "$(eval ${ver_cmd})" ]]; then + echo "${tool} missing or not version '${ver}', downloading..." + eval ${fetch_cmd} + fi +} +fetch $@ diff --git a/staging/operator-registry/test/e2e/opm_test.go b/staging/operator-registry/test/e2e/opm_test.go index 1c09332159..f0598a0eb0 100644 --- a/staging/operator-registry/test/e2e/opm_test.go +++ b/staging/operator-registry/test/e2e/opm_test.go @@ -332,7 +332,6 @@ var _ = Describe("opm", func() { By("loading manifests from a directory") err = initialize() Expect(err).NotTo(HaveOccurred()) - }) It("build bundles and index via inference", func() { diff --git a/staging/operator-registry/upstream-opm-builder.Dockerfile b/staging/operator-registry/upstream-opm-builder.Dockerfile index b1b89845ae..7d4d202562 100644 --- a/staging/operator-registry/upstream-opm-builder.Dockerfile +++ b/staging/operator-registry/upstream-opm-builder.Dockerfile @@ -1,3 +1,8 @@ +## +## Deprecated: release/goreleaser.opm.Dockerfile is used in conjunction with +## GoReleaser to build and push multi-arch images for opm +## + FROM golang:1.16-alpine AS builder RUN apk update && apk add sqlite build-base git mercurial bash diff --git a/vendor/github.com/operator-framework/operator-lifecycle-manager/pkg/controller/operators/olm/operator.go b/vendor/github.com/operator-framework/operator-lifecycle-manager/pkg/controller/operators/olm/operator.go index 94d3339f50..b71abe2115 100644 --- a/vendor/github.com/operator-framework/operator-lifecycle-manager/pkg/controller/operators/olm/operator.go +++ b/vendor/github.com/operator-framework/operator-lifecycle-manager/pkg/controller/operators/olm/operator.go @@ -38,6 +38,7 @@ import ( "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/operators/internal/pruning" "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/operators/olm/overrides" "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver" + resolvercache "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/cache" "github.com/operator-framework/operator-lifecycle-manager/pkg/lib/clients" csvutility "github.com/operator-framework/operator-lifecycle-manager/pkg/lib/csv" "github.com/operator-framework/operator-lifecycle-manager/pkg/lib/event" @@ -1364,7 +1365,7 @@ func (a *Operator) transitionCSVState(in v1alpha1.ClusterServiceVersion) (out *v out = in.DeepCopy() now := a.now() - operatorSurface, err := resolver.NewOperatorFromV1Alpha1CSV(out) + operatorSurface, err := resolvercache.NewOperatorFromV1Alpha1CSV(out) if err != nil { // If the resolver is unable to retrieve the operator info from the CSV the CSV requires changes, a syncError should not be returned. logger.WithError(err).Warn("Unable to retrieve operator information from CSV") @@ -1428,7 +1429,7 @@ func (a *Operator) transitionCSVState(in v1alpha1.ClusterServiceVersion) (out *v groupSurface := resolver.NewOperatorGroup(operatorGroup) otherGroupSurfaces := resolver.NewOperatorGroupSurfaces(otherGroups...) - providedAPIs := operatorSurface.ProvidedAPIs().StripPlural() + providedAPIs := operatorSurface.GetProvidedAPIs().StripPlural() switch result := a.apiReconciler.Reconcile(providedAPIs, groupSurface, otherGroupSurfaces...); { case operatorGroup.Spec.StaticProvidedAPIs && (result == resolver.AddAPIs || result == resolver.RemoveAPIs): diff --git a/vendor/github.com/operator-framework/operator-lifecycle-manager/pkg/controller/operators/olm/operatorgroup.go b/vendor/github.com/operator-framework/operator-lifecycle-manager/pkg/controller/operators/olm/operatorgroup.go index eb9139d89a..8c28ef0d1e 100644 --- a/vendor/github.com/operator-framework/operator-lifecycle-manager/pkg/controller/operators/olm/operatorgroup.go +++ b/vendor/github.com/operator-framework/operator-lifecycle-manager/pkg/controller/operators/olm/operatorgroup.go @@ -22,6 +22,7 @@ import ( "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/install" "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/operators/decorators" "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver" + "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/cache" hashutil "github.com/operator-framework/operator-lifecycle-manager/pkg/lib/kubernetes/pkg/util/hash" "github.com/operator-framework/operator-lifecycle-manager/pkg/lib/ownerutil" opregistry "github.com/operator-framework/operator-registry/pkg/registry" @@ -46,7 +47,7 @@ var ( ) func aggregationLabelFromAPIKey(k opregistry.APIKey, suffix string) (string, error) { - hash, err := resolver.APIKeyToGVKHash(k) + hash, err := cache.APIKeyToGVKHash(k) if err != nil { return "", err } @@ -188,7 +189,7 @@ func (a *Operator) syncOperatorGroups(obj interface{}) error { groupSurface := resolver.NewOperatorGroup(op) groupProvidedAPIs := groupSurface.ProvidedAPIs() providedAPIsForCSVs := a.providedAPIsFromCSVs(op, logger) - providedAPIsForGroup := make(resolver.APISet) + providedAPIsForGroup := make(cache.APISet) for api := range providedAPIsForCSVs { providedAPIsForGroup[api] = struct{}{} } @@ -309,26 +310,26 @@ func (a *Operator) providedAPIsFromCSVs(group *v1.OperatorGroup, logger *logrus. // TODO: Throw out CSVs that aren't members of the group due to group related failures? // Union the providedAPIsFromCSVs from existing members of the group - operatorSurface, err := resolver.NewOperatorFromV1Alpha1CSV(csv) + operatorSurface, err := cache.NewOperatorFromV1Alpha1CSV(csv) if err != nil { logger.WithError(err).Warn("could not create OperatorSurface from csv") continue } - for providedAPI := range operatorSurface.ProvidedAPIs().StripPlural() { + for providedAPI := range operatorSurface.GetProvidedAPIs().StripPlural() { providedAPIsFromCSVs[providedAPI] = csv } } return providedAPIsFromCSVs } -func (a *Operator) pruneProvidedAPIs(group *v1.OperatorGroup, groupProvidedAPIs resolver.APISet, providedAPIsFromCSVs map[opregistry.APIKey]*v1alpha1.ClusterServiceVersion, logger *logrus.Entry) { +func (a *Operator) pruneProvidedAPIs(group *v1.OperatorGroup, groupProvidedAPIs cache.APISet, providedAPIsFromCSVs map[opregistry.APIKey]*v1alpha1.ClusterServiceVersion, logger *logrus.Entry) { // Don't prune providedAPIsFromCSVs if static if group.Spec.StaticProvidedAPIs { a.logger.Debug("group has static provided apis. skipping provided api pruning") return } - intersection := make(resolver.APISet) + intersection := make(cache.APISet) for api := range providedAPIsFromCSVs { if _, ok := groupProvidedAPIs[api]; ok { intersection[api] = struct{}{} @@ -530,8 +531,14 @@ func (a *Operator) ensureSingletonRBAC(operatorNamespace string, csv *v1alpha1.C Resources: []string{"namespaces"}, }), } - if _, err := a.opClient.CreateClusterRole(clusterRole); err != nil { - return err + // TODO: this should do something smarter if the cluster role already exists + if cr, err := a.opClient.CreateClusterRole(clusterRole); err != nil { + // if the CR already exists, but the label is correct, the cache is just behind + if k8serrors.IsAlreadyExists(err) && ownerutil.IsOwnedByLabel(cr, csv) { + continue + } else { + return err + } } a.logger.Debug("created cluster role") } @@ -564,8 +571,14 @@ func (a *Operator) ensureSingletonRBAC(operatorNamespace string, csv *v1alpha1.C Name: r.RoleRef.Name, }, } - if _, err := a.opClient.CreateClusterRoleBinding(clusterRoleBinding); err != nil { - return err + // TODO: this should do something smarter if the cluster role binding already exists + if crb, err := a.opClient.CreateClusterRoleBinding(clusterRoleBinding); err != nil { + // if the CR already exists, but the label is correct, the cache is just behind + if k8serrors.IsAlreadyExists(err) && ownerutil.IsOwnedByLabel(crb, csv) { + continue + } else { + return err + } } } } @@ -966,7 +979,7 @@ func (a *Operator) updateNamespaceList(op *v1.OperatorGroup) ([]string, error) { return namespaceList, nil } -func (a *Operator) ensureOpGroupClusterRole(op *v1.OperatorGroup, suffix string, apis resolver.APISet) error { +func (a *Operator) ensureOpGroupClusterRole(op *v1.OperatorGroup, suffix string, apis cache.APISet) error { clusterRole := &rbacv1.ClusterRole{ ObjectMeta: metav1.ObjectMeta{ Name: strings.Join([]string{op.GetName(), suffix}, "-"), @@ -1017,7 +1030,7 @@ func (a *Operator) ensureOpGroupClusterRole(op *v1.OperatorGroup, suffix string, return nil } -func (a *Operator) ensureOpGroupClusterRoles(op *v1.OperatorGroup, apis resolver.APISet) error { +func (a *Operator) ensureOpGroupClusterRoles(op *v1.OperatorGroup, apis cache.APISet) error { for _, suffix := range Suffices { if err := a.ensureOpGroupClusterRole(op, suffix, apis); err != nil { return err @@ -1026,7 +1039,7 @@ func (a *Operator) ensureOpGroupClusterRoles(op *v1.OperatorGroup, apis resolver return nil } -func (a *Operator) findCSVsThatProvideAnyOf(provide resolver.APISet) ([]*v1alpha1.ClusterServiceVersion, error) { +func (a *Operator) findCSVsThatProvideAnyOf(provide cache.APISet) ([]*v1alpha1.ClusterServiceVersion, error) { csvs, err := a.lister.OperatorsV1alpha1().ClusterServiceVersionLister().ClusterServiceVersions(metav1.NamespaceAll).List(labels.Everything()) if err != nil { return nil, err @@ -1039,12 +1052,12 @@ func (a *Operator) findCSVsThatProvideAnyOf(provide resolver.APISet) ([]*v1alpha continue } - operatorSurface, err := resolver.NewOperatorFromV1Alpha1CSV(csv) + operatorSurface, err := cache.NewOperatorFromV1Alpha1CSV(csv) if err != nil { continue } - if len(operatorSurface.ProvidedAPIs().StripPlural().Intersection(provide)) > 0 { + if len(operatorSurface.GetProvidedAPIs().StripPlural().Intersection(provide)) > 0 { providers = append(providers, csv) } } diff --git a/vendor/github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/cache.go b/vendor/github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/cache/cache.go similarity index 78% rename from vendor/github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/cache.go rename to vendor/github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/cache/cache.go index 68a060f9c7..c26f30d434 100644 --- a/vendor/github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/cache.go +++ b/vendor/github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/cache/cache.go @@ -1,4 +1,4 @@ -package resolver +package cache import ( "context" @@ -8,6 +8,8 @@ import ( "sync" "time" + "k8s.io/apimachinery/pkg/util/errors" + "github.com/sirupsen/logrus" "github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/listers/operators/v1alpha1" @@ -74,9 +76,22 @@ func NewOperatorCache(rcp RegistryClientProvider, log logrus.FieldLogger, catsrc } type NamespacedOperatorCache struct { - namespaces []string + Namespaces []string existing *registry.CatalogKey - snapshots map[registry.CatalogKey]*CatalogSnapshot + Snapshots map[registry.CatalogKey]*CatalogSnapshot +} + +func (c *NamespacedOperatorCache) Error() error { + var errs []error + for key, snapshot := range c.Snapshots { + snapshot.m.Lock() + err := snapshot.err + snapshot.m.Unlock() + if err != nil { + errs = append(errs, fmt.Errorf("error using catalog %s (in namespace %s): %w", key.Name, key.Namespace, err)) + } + } + return errors.NewAggregate(errs) } func (c *OperatorCache) Expire(catalog registry.CatalogKey) { @@ -98,8 +113,8 @@ func (c *OperatorCache) Namespaced(namespaces ...string) MultiCatalogOperatorFin clients := c.rcp.ClientsForNamespaces(namespaces...) result := NamespacedOperatorCache{ - namespaces: namespaces, - snapshots: make(map[registry.CatalogKey]*CatalogSnapshot), + Namespaces: namespaces, + Snapshots: make(map[registry.CatalogKey]*CatalogSnapshot), } var misses []registry.CatalogKey @@ -112,8 +127,8 @@ func (c *OperatorCache) Namespaced(namespaces ...string) MultiCatalogOperatorFin func() { snapshot.m.RLock() defer snapshot.m.RUnlock() - if !snapshot.Expired(now) && snapshot.operators != nil && len(snapshot.operators) > 0 { - result.snapshots[key] = snapshot + if !snapshot.Expired(now) && snapshot.Operators != nil && len(snapshot.Operators) > 0 { + result.Snapshots[key] = snapshot } else { misses = append(misses, key) } @@ -147,8 +162,8 @@ func (c *OperatorCache) Namespaced(namespaces ...string) MultiCatalogOperatorFin // Check for any snapshots that were populated while waiting to acquire the lock. var found int for i := range misses { - if snapshot, ok := c.snapshots[misses[i]]; ok && !snapshot.Expired(now) && snapshot.operators != nil && len(snapshot.operators) > 0 { - result.snapshots[misses[i]] = snapshot + if snapshot, ok := c.snapshots[misses[i]]; ok && !snapshot.Expired(now) && snapshot.Operators != nil && len(snapshot.Operators) > 0 { + result.Snapshots[misses[i]] = snapshot misses[found], misses[i] = misses[i], misses[found] found++ } @@ -167,14 +182,14 @@ func (c *OperatorCache) Namespaced(namespaces ...string) MultiCatalogOperatorFin s := CatalogSnapshot{ logger: c.logger.WithField("catalog", miss), - key: miss, + Key: miss, expiry: now.Add(c.ttl), pop: cancel, - priority: catalogSourcePriority(catsrcPriority), + Priority: catalogSourcePriority(catsrcPriority), } s.m.Lock() c.snapshots[miss] = &s - result.snapshots[miss] = &s + result.Snapshots[miss] = &s go c.populate(ctx, &s, clients[miss]) } @@ -183,6 +198,12 @@ func (c *OperatorCache) Namespaced(namespaces ...string) MultiCatalogOperatorFin func (c *OperatorCache) populate(ctx context.Context, snapshot *CatalogSnapshot, registry client.Interface) { defer snapshot.m.Unlock() + defer func() { + // Don't cache an errorred snapshot. + if snapshot.err != nil { + snapshot.expiry = time.Time{} + } + }() c.sem <- struct{}{} defer func() { <-c.sem }() @@ -195,9 +216,10 @@ func (c *OperatorCache) populate(ctx context.Context, snapshot *CatalogSnapshot, it, err := registry.ListBundles(ctx) if err != nil { snapshot.logger.Errorf("failed to list bundles: %s", err.Error()) + snapshot.err = err return } - c.logger.WithField("catalog", snapshot.key.String()).Debug("updating cache") + c.logger.WithField("catalog", snapshot.Key.String()).Debug("updating cache") var operators []*Operator for b := it.Next(); b != nil; b = it.Next() { defaultChannel, ok := defaultChannels[b.PackageName] @@ -210,25 +232,26 @@ func (c *OperatorCache) populate(ctx context.Context, snapshot *CatalogSnapshot, defaultChannel = p.DefaultChannelName } } - o, err := NewOperatorFromBundle(b, "", snapshot.key, defaultChannel) + o, err := NewOperatorFromBundle(b, "", snapshot.Key, defaultChannel) if err != nil { snapshot.logger.Warnf("failed to construct operator from bundle, continuing: %v", err) continue } - o.providedAPIs = o.ProvidedAPIs().StripPlural() - o.requiredAPIs = o.RequiredAPIs().StripPlural() - o.replaces = b.Replaces - ensurePackageProperty(o, b.PackageName, b.Version) + o.ProvidedAPIs = o.ProvidedAPIs.StripPlural() + o.RequiredAPIs = o.RequiredAPIs.StripPlural() + o.Replaces = b.Replaces + EnsurePackageProperty(o, b.PackageName, b.Version) operators = append(operators, o) } if err := it.Error(); err != nil { snapshot.logger.Warnf("error encountered while listing bundles: %s", err.Error()) + snapshot.err = err } - snapshot.operators = operators + snapshot.Operators = operators } -func ensurePackageProperty(o *Operator, name, version string) { - for _, p := range o.Properties() { +func EnsurePackageProperty(o *Operator, name, version string) { + for _, p := range o.Properties { if p.Type == opregistry.PackageType { return } @@ -241,7 +264,7 @@ func ensurePackageProperty(o *Operator, name, version string) { if err != nil { return } - o.properties = append(o.properties, &api.Property{ + o.Properties = append(o.Properties, &api.Property{ Type: opregistry.PackageType, Value: string(bytes), }) @@ -252,7 +275,7 @@ func (c *NamespacedOperatorCache) Catalog(k registry.CatalogKey) OperatorFinder if k.Empty() { return c } - if snapshot, ok := c.snapshots[k]; ok { + if snapshot, ok := c.Snapshots[k]; ok { return snapshot } return EmptyOperatorFinder{} @@ -263,7 +286,7 @@ func (c *NamespacedOperatorCache) FindPreferred(preferred *registry.CatalogKey, if preferred != nil && preferred.Empty() { preferred = nil } - sorted := NewSortableSnapshots(c.existing, preferred, c.namespaces, c.snapshots) + sorted := NewSortableSnapshots(c.existing, preferred, c.Namespaces, c.Snapshots) sort.Sort(sorted) for _, snapshot := range sorted.snapshots { result = append(result, snapshot.Find(p...)...) @@ -273,11 +296,11 @@ func (c *NamespacedOperatorCache) FindPreferred(preferred *registry.CatalogKey, func (c *NamespacedOperatorCache) WithExistingOperators(snapshot *CatalogSnapshot) MultiCatalogOperatorFinder { o := &NamespacedOperatorCache{ - namespaces: c.namespaces, - existing: &snapshot.key, - snapshots: c.snapshots, + Namespaces: c.Namespaces, + existing: &snapshot.Key, + Snapshots: c.Snapshots, } - o.snapshots[snapshot.key] = snapshot + o.Snapshots[snapshot.Key] = snapshot return o } @@ -287,12 +310,13 @@ func (c *NamespacedOperatorCache) Find(p ...OperatorPredicate) []*Operator { type CatalogSnapshot struct { logger logrus.FieldLogger - key registry.CatalogKey + Key registry.CatalogKey expiry time.Time - operators []*Operator + Operators []*Operator m sync.RWMutex pop context.CancelFunc - priority catalogSourcePriority + Priority catalogSourcePriority + err error } func (s *CatalogSnapshot) Cancel() { @@ -308,8 +332,8 @@ func (s *CatalogSnapshot) Expired(at time.Time) bool { func NewRunningOperatorSnapshot(logger logrus.FieldLogger, key registry.CatalogKey, o []*Operator) *CatalogSnapshot { return &CatalogSnapshot{ logger: logger, - key: key, - operators: o, + Key: key, + Operators: o, } } @@ -348,37 +372,37 @@ func (s SortableSnapshots) Len() int { func (s SortableSnapshots) Less(i, j int) bool { // existing operators are preferred over catalog operators if s.existing != nil && - s.snapshots[i].key.Name == s.existing.Name && - s.snapshots[i].key.Namespace == s.existing.Namespace { + s.snapshots[i].Key.Name == s.existing.Name && + s.snapshots[i].Key.Namespace == s.existing.Namespace { return true } if s.existing != nil && - s.snapshots[j].key.Name == s.existing.Name && - s.snapshots[j].key.Namespace == s.existing.Namespace { + s.snapshots[j].Key.Name == s.existing.Name && + s.snapshots[j].Key.Namespace == s.existing.Namespace { return false } // preferred catalog is less than all other catalogs if s.preferred != nil && - s.snapshots[i].key.Name == s.preferred.Name && - s.snapshots[i].key.Namespace == s.preferred.Namespace { + s.snapshots[i].Key.Name == s.preferred.Name && + s.snapshots[i].Key.Namespace == s.preferred.Namespace { return true } if s.preferred != nil && - s.snapshots[j].key.Name == s.preferred.Name && - s.snapshots[j].key.Namespace == s.preferred.Namespace { + s.snapshots[j].Key.Name == s.preferred.Name && + s.snapshots[j].Key.Namespace == s.preferred.Namespace { return false } // the rest are sorted first on priority, namespace and then by name - if s.snapshots[i].priority != s.snapshots[j].priority { - return s.snapshots[i].priority > s.snapshots[j].priority + if s.snapshots[i].Priority != s.snapshots[j].Priority { + return s.snapshots[i].Priority > s.snapshots[j].Priority } - if s.snapshots[i].key.Namespace != s.snapshots[j].key.Namespace { - return s.namespaces[s.snapshots[i].key.Namespace] < s.namespaces[s.snapshots[j].key.Namespace] + if s.snapshots[i].Key.Namespace != s.snapshots[j].Key.Namespace { + return s.namespaces[s.snapshots[i].Key.Namespace] < s.namespaces[s.snapshots[j].Key.Namespace] } - return s.snapshots[i].key.Name < s.snapshots[j].key.Name + return s.snapshots[i].Key.Name < s.snapshots[j].Key.Name } // Swap swaps the elements with indexes i and j. @@ -389,7 +413,7 @@ func (s SortableSnapshots) Swap(i, j int) { func (s *CatalogSnapshot) Find(p ...OperatorPredicate) []*Operator { s.m.RLock() defer s.m.RUnlock() - return Filter(s.operators, p...) + return Filter(s.Operators, p...) } type OperatorFinder interface { @@ -400,6 +424,7 @@ type MultiCatalogOperatorFinder interface { Catalog(registry.CatalogKey) OperatorFinder FindPreferred(*registry.CatalogKey, ...OperatorPredicate) []*Operator WithExistingOperators(*CatalogSnapshot) MultiCatalogOperatorFinder + Error() error OperatorFinder } diff --git a/staging/operator-lifecycle-manager/pkg/controller/registry/resolver/operators.go b/vendor/github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/cache/operators.go similarity index 75% rename from staging/operator-lifecycle-manager/pkg/controller/registry/resolver/operators.go rename to vendor/github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/cache/operators.go index 32c38b3f09..7b85124133 100644 --- a/staging/operator-lifecycle-manager/pkg/controller/registry/resolver/operators.go +++ b/vendor/github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/cache/operators.go @@ -1,4 +1,4 @@ -package resolver +package cache import ( "encoding/json" @@ -208,28 +208,28 @@ var ExistingOperator = OperatorSourceInfo{Package: "", Channel: "", StartingCSV: // OperatorSurface describes the API surfaces provided and required by an Operator. type OperatorSurface interface { - ProvidedAPIs() APISet - RequiredAPIs() APISet + GetProvidedAPIs() APISet + GetRequiredAPIs() APISet Identifier() string - Replaces() string - Version() *semver.Version - SourceInfo() *OperatorSourceInfo - Bundle() *api.Bundle + GetReplaces() string + GetVersion() *semver.Version + GetSourceInfo() *OperatorSourceInfo + GetBundle() *api.Bundle Inline() bool - Properties() []*api.Property - Skips() []string + GetProperties() []*api.Property + GetSkips() []string } type Operator struct { - name string - replaces string - providedAPIs APISet - requiredAPIs APISet - version *semver.Version - bundle *api.Bundle - sourceInfo *OperatorSourceInfo - properties []*api.Property - skips []string + Name string + Replaces string + ProvidedAPIs APISet + RequiredAPIs APISet + Version *semver.Version + Bundle *api.Bundle + SourceInfo *OperatorSourceInfo + Properties []*api.Property + Skips []string } var _ OperatorSurface = &Operator{} @@ -265,13 +265,13 @@ func NewOperatorFromBundle(bundle *api.Bundle, startingCSV string, sourceKey reg } } if len(bundle.Dependencies) > 0 { - ps, err := legacyDependenciesToProperties(bundle.Dependencies) + ps, err := LegacyDependenciesToProperties(bundle.Dependencies) if err != nil { return nil, fmt.Errorf("failed to translate legacy dependencies to properties: %w", err) } properties = append(properties, ps...) } else { - ps, err := requiredAPIsToProperties(required) + ps, err := RequiredAPIsToProperties(required) if err != nil { return nil, err } @@ -279,15 +279,15 @@ func NewOperatorFromBundle(bundle *api.Bundle, startingCSV string, sourceKey reg } o := &Operator{ - name: bundle.CsvName, - replaces: bundle.Replaces, - version: version, - providedAPIs: provided, - requiredAPIs: required, - bundle: bundle, - sourceInfo: sourceInfo, - properties: properties, - skips: bundle.Skips, + Name: bundle.CsvName, + Replaces: bundle.Replaces, + Version: version, + ProvidedAPIs: provided, + RequiredAPIs: required, + Bundle: bundle, + SourceInfo: sourceInfo, + Properties: properties, + Skips: bundle.Skips, } if !o.Inline() { @@ -331,91 +331,83 @@ func NewOperatorFromV1Alpha1CSV(csv *v1alpha1.ClusterServiceVersion) (*Operator, if err != nil { return nil, err } - dependencies, err := requiredAPIsToProperties(requiredAPIs) + dependencies, err := RequiredAPIsToProperties(requiredAPIs) if err != nil { return nil, err } properties = append(properties, dependencies...) return &Operator{ - name: csv.GetName(), - version: &csv.Spec.Version.Version, - providedAPIs: providedAPIs, - requiredAPIs: requiredAPIs, - sourceInfo: &ExistingOperator, - properties: properties, + Name: csv.GetName(), + Version: &csv.Spec.Version.Version, + ProvidedAPIs: providedAPIs, + RequiredAPIs: requiredAPIs, + SourceInfo: &ExistingOperator, + Properties: properties, }, nil } -func (o *Operator) Name() string { - return o.name +func (o *Operator) GetProvidedAPIs() APISet { + return o.ProvidedAPIs } -func (o *Operator) ProvidedAPIs() APISet { - return o.providedAPIs -} - -func (o *Operator) RequiredAPIs() APISet { - return o.requiredAPIs +func (o *Operator) GetRequiredAPIs() APISet { + return o.RequiredAPIs } func (o *Operator) Identifier() string { - return o.name + return o.Name } -func (o *Operator) Replaces() string { - return o.replaces +func (o *Operator) GetReplaces() string { + return o.Replaces } -func (o *Operator) Skips() []string { - return o.skips -} - -func (o *Operator) SetReplaces(replacing string) { - o.replaces = replacing +func (o *Operator) GetSkips() []string { + return o.Skips } func (o *Operator) Package() string { - if o.bundle != nil { - return o.bundle.PackageName + if o.Bundle != nil { + return o.Bundle.PackageName } return "" } func (o *Operator) Channel() string { - if o.bundle != nil { - return o.bundle.ChannelName + if o.Bundle != nil { + return o.Bundle.ChannelName } return "" } -func (o *Operator) SourceInfo() *OperatorSourceInfo { - return o.sourceInfo +func (o *Operator) GetSourceInfo() *OperatorSourceInfo { + return o.SourceInfo } -func (o *Operator) Bundle() *api.Bundle { - return o.bundle +func (o *Operator) GetBundle() *api.Bundle { + return o.Bundle } -func (o *Operator) Version() *semver.Version { - return o.version +func (o *Operator) GetVersion() *semver.Version { + return o.Version } func (o *Operator) SemverRange() (semver.Range, error) { - return semver.ParseRange(o.Bundle().SkipRange) + return semver.ParseRange(o.Bundle.SkipRange) } func (o *Operator) Inline() bool { - return o.bundle != nil && o.bundle.GetBundlePath() == "" + return o.Bundle != nil && o.Bundle.GetBundlePath() == "" } -func (o *Operator) Properties() []*api.Property { - return o.properties +func (o *Operator) GetProperties() []*api.Property { + return o.Properties } func (o *Operator) DependencyPredicates() (predicates []OperatorPredicate, err error) { predicates = make([]OperatorPredicate, 0) - for _, property := range o.Properties() { + for _, property := range o.Properties { predicate, err := PredicateForProperty(property) if err != nil { return nil, err @@ -428,7 +420,7 @@ func (o *Operator) DependencyPredicates() (predicates []OperatorPredicate, err e return } -func requiredAPIsToProperties(apis APISet) (out []*api.Property, err error) { +func RequiredAPIsToProperties(apis APISet) (out []*api.Property, err error) { if len(apis) == 0 { return } @@ -479,7 +471,7 @@ func providedAPIsToProperties(apis APISet) (out []*api.Property, err error) { return nil, nil } -func legacyDependenciesToProperties(dependencies []*api.Dependency) ([]*api.Property, error) { +func LegacyDependenciesToProperties(dependencies []*api.Dependency) ([]*api.Property, error) { var result []*api.Property for _, dependency := range dependencies { switch dependency.Type { @@ -538,3 +530,88 @@ func legacyDependenciesToProperties(dependencies []*api.Dependency) ([]*api.Prop } return result, nil } + +func APISetToProperties(crds, apis APISet, deprecated bool) (out []*api.Property) { + out = make([]*api.Property, 0) + for a := range crds { + val, err := json.Marshal(opregistry.GVKProperty{ + Group: a.Group, + Kind: a.Kind, + Version: a.Version, + }) + if err != nil { + panic(err) + } + out = append(out, &api.Property{ + Type: opregistry.GVKType, + Value: string(val), + }) + } + for a := range apis { + val, err := json.Marshal(opregistry.GVKProperty{ + Group: a.Group, + Kind: a.Kind, + Version: a.Version, + }) + if err != nil { + panic(err) + } + out = append(out, &api.Property{ + Type: opregistry.GVKType, + Value: string(val), + }) + } + if deprecated { + val, err := json.Marshal(opregistry.DeprecatedProperty{}) + if err != nil { + panic(err) + } + out = append(out, &api.Property{ + Type: opregistry.DeprecatedType, + Value: string(val), + }) + } + if len(out) == 0 { + return nil + } + return +} + +func APISetToDependencies(crds, apis APISet) (out []*api.Dependency) { + if len(crds)+len(apis) == 0 { + return nil + } + out = make([]*api.Dependency, 0) + for a := range crds { + val, err := json.Marshal(opregistry.GVKDependency{ + Group: a.Group, + Kind: a.Kind, + Version: a.Version, + }) + if err != nil { + panic(err) + } + out = append(out, &api.Dependency{ + Type: opregistry.GVKType, + Value: string(val), + }) + } + for a := range apis { + val, err := json.Marshal(opregistry.GVKDependency{ + Group: a.Group, + Kind: a.Kind, + Version: a.Version, + }) + if err != nil { + panic(err) + } + out = append(out, &api.Dependency{ + Type: opregistry.GVKType, + Value: string(val), + }) + } + if len(out) == 0 { + return nil + } + return +} diff --git a/vendor/github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/predicates.go b/vendor/github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/cache/predicates.go similarity index 95% rename from vendor/github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/predicates.go rename to vendor/github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/cache/predicates.go index 4e731dd412..171ee5ac91 100644 --- a/vendor/github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/predicates.go +++ b/vendor/github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/cache/predicates.go @@ -1,4 +1,4 @@ -package resolver +package cache import ( "bytes" @@ -24,7 +24,7 @@ func CSVNamePredicate(name string) OperatorPredicate { } func (c csvNamePredicate) Test(o *Operator) bool { - return o.Name() == string(c) + return o.Name == string(c) } func (c csvNamePredicate) String() string { @@ -42,10 +42,10 @@ func (ch channelPredicate) Test(o *Operator) bool { if string(ch) == "" { return true } - if o.Bundle() == nil { + if o.Bundle == nil { return false } - return o.Bundle().ChannelName == string(ch) + return o.Bundle.ChannelName == string(ch) } func (ch channelPredicate) String() string { @@ -59,7 +59,7 @@ func PkgPredicate(pkg string) OperatorPredicate { } func (pkg pkgPredicate) Test(o *Operator) bool { - for _, p := range o.Properties() { + for _, p := range o.Properties { if p.Type != opregistry.PackageType { continue } @@ -89,7 +89,7 @@ func VersionInRangePredicate(r semver.Range, version string) OperatorPredicate { } func (v versionInRangePredicate) Test(o *Operator) bool { - for _, p := range o.Properties() { + for _, p := range o.Properties { if p.Type != opregistry.PackageType { continue } @@ -106,7 +106,7 @@ func (v versionInRangePredicate) Test(o *Operator) bool { return true } } - return o.Version() != nil && v.r(*o.Version()) + return o.Version != nil && v.r(*o.Version) } func (v versionInRangePredicate) String() string { @@ -119,7 +119,7 @@ func LabelPredicate(label string) OperatorPredicate { return labelPredicate(label) } func (l labelPredicate) Test(o *Operator) bool { - for _, p := range o.Properties() { + for _, p := range o.Properties { if p.Type != opregistry.LabelType { continue } @@ -148,7 +148,7 @@ func CatalogPredicate(key registry.CatalogKey) OperatorPredicate { } func (c catalogPredicate) Test(o *Operator) bool { - return c.key.Equal(o.SourceInfo().Catalog) + return c.key.Equal(o.SourceInfo.Catalog) } func (c catalogPredicate) String() string { @@ -166,7 +166,7 @@ func ProvidingAPIPredicate(api opregistry.APIKey) OperatorPredicate { } func (g gvkPredicate) Test(o *Operator) bool { - for _, p := range o.Properties() { + for _, p := range o.Properties { if p.Type != opregistry.GVKType { continue } @@ -210,10 +210,10 @@ func ReplacesPredicate(replaces string) OperatorPredicate { } func (r replacesPredicate) Test(o *Operator) bool { - if o.Replaces() == string(r) { + if o.Replaces == string(r) { return true } - for _, s := range o.Skips() { + for _, s := range o.Skips { if s == string(r) { return true } diff --git a/vendor/github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/groups.go b/vendor/github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/groups.go index 2dcc276e08..72f3e68ebf 100644 --- a/vendor/github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/groups.go +++ b/vendor/github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/groups.go @@ -5,6 +5,7 @@ import ( "strings" v1 "github.com/operator-framework/api/pkg/operators/v1" + "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/cache" ) type NamespaceSet map[string]struct{} @@ -93,7 +94,7 @@ type OperatorGroupSurface interface { Identifier() string Namespace() string Targets() NamespaceSet - ProvidedAPIs() APISet + ProvidedAPIs() cache.APISet GroupIntersection(groups ...OperatorGroupSurface) []OperatorGroupSurface } @@ -103,7 +104,7 @@ type OperatorGroup struct { namespace string name string targets NamespaceSet - providedAPIs APISet + providedAPIs cache.APISet } func NewOperatorGroup(group *v1.OperatorGroup) *OperatorGroup { @@ -119,7 +120,7 @@ func NewOperatorGroup(group *v1.OperatorGroup) *OperatorGroup { namespace: group.GetNamespace(), name: group.GetName(), targets: NewNamespaceSet(namespaces), - providedAPIs: GVKStringToProvidedAPISet(gvksStr), + providedAPIs: cache.GVKStringToProvidedAPISet(gvksStr), } } @@ -144,7 +145,7 @@ func (g *OperatorGroup) Targets() NamespaceSet { return g.targets } -func (g *OperatorGroup) ProvidedAPIs() APISet { +func (g *OperatorGroup) ProvidedAPIs() cache.APISet { return g.providedAPIs } @@ -174,18 +175,18 @@ const ( ) type APIIntersectionReconciler interface { - Reconcile(add APISet, group OperatorGroupSurface, otherGroups ...OperatorGroupSurface) APIReconciliationResult + Reconcile(add cache.APISet, group OperatorGroupSurface, otherGroups ...OperatorGroupSurface) APIReconciliationResult } -type APIIntersectionReconcileFunc func(add APISet, group OperatorGroupSurface, otherGroups ...OperatorGroupSurface) APIReconciliationResult +type APIIntersectionReconcileFunc func(add cache.APISet, group OperatorGroupSurface, otherGroups ...OperatorGroupSurface) APIReconciliationResult -func (a APIIntersectionReconcileFunc) Reconcile(add APISet, group OperatorGroupSurface, otherGroups ...OperatorGroupSurface) APIReconciliationResult { +func (a APIIntersectionReconcileFunc) Reconcile(add cache.APISet, group OperatorGroupSurface, otherGroups ...OperatorGroupSurface) APIReconciliationResult { return a(add, group, otherGroups...) } -func ReconcileAPIIntersection(add APISet, group OperatorGroupSurface, otherGroups ...OperatorGroupSurface) APIReconciliationResult { +func ReconcileAPIIntersection(add cache.APISet, group OperatorGroupSurface, otherGroups ...OperatorGroupSurface) APIReconciliationResult { groupIntersection := group.GroupIntersection(otherGroups...) - providedAPIIntersection := make(APISet) + providedAPIIntersection := make(cache.APISet) for _, g := range groupIntersection { providedAPIIntersection = providedAPIIntersection.Union(g.ProvidedAPIs()) } diff --git a/vendor/github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/installabletypes.go b/vendor/github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/installabletypes.go index a76361d3f2..94302d53ee 100644 --- a/vendor/github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/installabletypes.go +++ b/vendor/github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/installabletypes.go @@ -5,6 +5,7 @@ import ( "strings" "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry" + "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/cache" "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/solver" operatorregistry "github.com/operator-framework/operator-registry/pkg/registry" ) @@ -55,14 +56,13 @@ func bundleId(bundle, channel string, catalog registry.CatalogKey) solver.Identi return solver.IdentifierFromString(fmt.Sprintf("%s/%s/%s", catalog.String(), channel, bundle)) } -func NewBundleInstallableFromOperator(o *Operator) (BundleInstallable, error) { - src := o.SourceInfo() - if src == nil { - return BundleInstallable{}, fmt.Errorf("unable to resolve the source of bundle %s", o.Identifier()) +func NewBundleInstallableFromOperator(o *cache.Operator) (BundleInstallable, error) { + if o.SourceInfo == nil { + return BundleInstallable{}, fmt.Errorf("unable to resolve the source of bundle %s", o.Name) } - id := bundleId(o.Identifier(), o.Channel(), src.Catalog) + id := bundleId(o.Identifier(), o.Channel(), o.SourceInfo.Catalog) var constraints []solver.Constraint - if src.Catalog.Virtual() && src.Subscription == nil { + if o.SourceInfo.Catalog.Virtual() && o.SourceInfo.Subscription == nil { // CSVs already associated with a Subscription // may be replaced, but freestanding CSVs must // appear in any solution. @@ -71,7 +71,7 @@ func NewBundleInstallableFromOperator(o *Operator) (BundleInstallable, error) { fmt.Sprintf("clusterserviceversion %s exists and is not referenced by a subscription", o.Identifier()), )) } - for _, p := range o.bundle.GetProperties() { + for _, p := range o.Properties { if p.GetType() == operatorregistry.DeprecatedType { constraints = append(constraints, PrettyConstraint( solver.Prohibited(), diff --git a/vendor/github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/labeler.go b/vendor/github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/labeler.go index cf408ef00d..9b10eb2d6d 100644 --- a/vendor/github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/labeler.go +++ b/vendor/github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/labeler.go @@ -1,6 +1,7 @@ package resolver import ( + "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/cache" "github.com/operator-framework/operator-registry/pkg/registry" extv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" "k8s.io/apimachinery/pkg/labels" @@ -15,7 +16,7 @@ const ( // Concrete types other than OperatorSurface and CustomResource definition no-op. func LabelSetsFor(obj interface{}) ([]labels.Set, error) { switch v := obj.(type) { - case OperatorSurface: + case cache.OperatorSurface: return labelSetsForOperatorSurface(v) case *extv1beta1.CustomResourceDefinition: return labelSetsForCRD(v) @@ -24,17 +25,17 @@ func LabelSetsFor(obj interface{}) ([]labels.Set, error) { } } -func labelSetsForOperatorSurface(surface OperatorSurface) ([]labels.Set, error) { +func labelSetsForOperatorSurface(surface cache.OperatorSurface) ([]labels.Set, error) { labelSet := labels.Set{} - for key := range surface.ProvidedAPIs().StripPlural() { - hash, err := APIKeyToGVKHash(key) + for key := range surface.GetProvidedAPIs().StripPlural() { + hash, err := cache.APIKeyToGVKHash(key) if err != nil { return nil, err } labelSet[APILabelKeyPrefix+hash] = "provided" } - for key := range surface.RequiredAPIs().StripPlural() { - hash, err := APIKeyToGVKHash(key) + for key := range surface.GetRequiredAPIs().StripPlural() { + hash, err := cache.APIKeyToGVKHash(key) if err != nil { return nil, err } @@ -52,7 +53,7 @@ func labelSetsForCRD(crd *extv1beta1.CustomResourceDefinition) ([]labels.Set, er // Add label sets for each version for _, version := range crd.Spec.Versions { - hash, err := APIKeyToGVKHash(registry.APIKey{ + hash, err := cache.APIKeyToGVKHash(registry.APIKey{ Group: crd.Spec.Group, Version: version.Name, Kind: crd.Spec.Names.Kind, diff --git a/vendor/github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/resolver.go b/vendor/github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/resolver.go index b00ec8a269..a8eeaf51bf 100644 --- a/vendor/github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/resolver.go +++ b/vendor/github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/resolver.go @@ -14,6 +14,7 @@ import ( "github.com/operator-framework/api/pkg/operators/v1alpha1" v1alpha1listers "github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/listers/operators/v1alpha1" "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry" + "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/cache" "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/projection" "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/solver" "github.com/operator-framework/operator-registry/pkg/api" @@ -21,17 +22,17 @@ import ( ) type OperatorResolver interface { - SolveOperators(csvs []*v1alpha1.ClusterServiceVersion, subs []*v1alpha1.Subscription, add map[OperatorSourceInfo]struct{}) (OperatorSet, error) + SolveOperators(csvs []*v1alpha1.ClusterServiceVersion, subs []*v1alpha1.Subscription, add map[cache.OperatorSourceInfo]struct{}) (cache.OperatorSet, error) } type SatResolver struct { - cache OperatorCacheProvider + cache cache.OperatorCacheProvider log logrus.FieldLogger } -func NewDefaultSatResolver(rcp RegistryClientProvider, catsrcLister v1alpha1listers.CatalogSourceLister, log logrus.FieldLogger) *SatResolver { +func NewDefaultSatResolver(rcp cache.RegistryClientProvider, catsrcLister v1alpha1listers.CatalogSourceLister, log logrus.FieldLogger) *SatResolver { return &SatResolver{ - cache: NewOperatorCache(rcp, log, catsrcLister), + cache: cache.NewOperatorCache(rcp, log, catsrcLister), log: log, } } @@ -46,11 +47,11 @@ func (w *debugWriter) Write(b []byte) (int, error) { return n, nil } -func (r *SatResolver) SolveOperators(namespaces []string, csvs []*v1alpha1.ClusterServiceVersion, subs []*v1alpha1.Subscription) (OperatorSet, error) { +func (r *SatResolver) SolveOperators(namespaces []string, csvs []*v1alpha1.ClusterServiceVersion, subs []*v1alpha1.Subscription) (cache.OperatorSet, error) { var errs []error installables := make(map[solver.Identifier]solver.Installable, 0) - visited := make(map[OperatorSurface]*BundleInstallable, 0) + visited := make(map[cache.OperatorSurface]*BundleInstallable, 0) // TODO: better abstraction startingCSVs := make(map[string]struct{}) @@ -73,10 +74,10 @@ func (r *SatResolver) SolveOperators(namespaces []string, csvs []*v1alpha1.Clust // build constraints for each Subscription for _, sub := range subs { // find the currently installed operator (if it exists) - var current *Operator + var current *cache.Operator for _, csv := range csvs { if csv.Name == sub.Status.InstalledCSV { - op, err := NewOperatorFromV1Alpha1CSV(csv) + op, err := cache.NewOperatorFromV1Alpha1CSV(csv) if err != nil { return nil, err } @@ -103,6 +104,10 @@ func (r *SatResolver) SolveOperators(namespaces []string, csvs []*v1alpha1.Clust r.addInvariants(namespacedCache, installables) + if err := namespacedCache.Error(); err != nil { + return nil, err + } + input := make([]solver.Installable, 0) for _, i := range installables { input = append(input, i) @@ -136,7 +141,7 @@ func (r *SatResolver) SolveOperators(namespaces []string, csvs []*v1alpha1.Clust } } - operators := make(map[string]OperatorSurface, 0) + operators := make(map[string]cache.OperatorSurface, 0) for _, installableOperator := range operatorInstallables { csvName, channel, catalog, err := installableOperator.BundleSourceInfo() if err != nil { @@ -144,18 +149,18 @@ func (r *SatResolver) SolveOperators(namespaces []string, csvs []*v1alpha1.Clust continue } - op, err := ExactlyOne(namespacedCache.Catalog(catalog).Find(CSVNamePredicate(csvName), ChannelPredicate(channel))) + op, err := cache.ExactlyOne(namespacedCache.Catalog(catalog).Find(cache.CSVNamePredicate(csvName), cache.ChannelPredicate(channel))) if err != nil { errs = append(errs, err) continue } if len(installableOperator.Replaces) > 0 { - op.replaces = installableOperator.Replaces + op.Replaces = installableOperator.Replaces // TODO: Don't mutate object from cache! } // lookup if this installable came from a starting CSV if _, ok := startingCSVs[csvName]; ok { - op.sourceInfo.StartingCSV = csvName + op.SourceInfo.StartingCSV = csvName // TODO: Don't mutate object from cache! } operators[csvName] = op @@ -168,8 +173,8 @@ func (r *SatResolver) SolveOperators(namespaces []string, csvs []*v1alpha1.Clust return operators, nil } -func (r *SatResolver) getSubscriptionInstallables(sub *v1alpha1.Subscription, current *Operator, namespacedCache MultiCatalogOperatorFinder, visited map[OperatorSurface]*BundleInstallable) (map[solver.Identifier]solver.Installable, error) { - var cachePredicates, channelPredicates []OperatorPredicate +func (r *SatResolver) getSubscriptionInstallables(sub *v1alpha1.Subscription, current *cache.Operator, namespacedCache cache.MultiCatalogOperatorFinder, visited map[cache.OperatorSurface]*BundleInstallable) (map[solver.Identifier]solver.Installable, error) { + var cachePredicates, channelPredicates []cache.OperatorPredicate installables := make(map[solver.Identifier]solver.Installable, 0) catalog := registry.CatalogKey{ @@ -177,24 +182,24 @@ func (r *SatResolver) getSubscriptionInstallables(sub *v1alpha1.Subscription, cu Namespace: sub.Spec.CatalogSourceNamespace, } - var bundles []*Operator + var bundles []*cache.Operator { var nall, npkg, nch, ncsv int - csvPredicate := True() + csvPredicate := cache.True() if current != nil { // if we found an existing installed operator, we should filter the channel by operators that can replace it - channelPredicates = append(channelPredicates, Or(SkipRangeIncludesPredicate(*current.Version()), ReplacesPredicate(current.Identifier()))) + channelPredicates = append(channelPredicates, cache.Or(cache.SkipRangeIncludesPredicate(*current.Version), cache.ReplacesPredicate(current.Identifier()))) } else if sub.Spec.StartingCSV != "" { // if no operator is installed and we have a startingCSV, filter for it - csvPredicate = CSVNamePredicate(sub.Spec.StartingCSV) + csvPredicate = cache.CSVNamePredicate(sub.Spec.StartingCSV) } - cachePredicates = append(cachePredicates, And( - CountingPredicate(True(), &nall), - CountingPredicate(PkgPredicate(sub.Spec.Package), &npkg), - CountingPredicate(ChannelPredicate(sub.Spec.Channel), &nch), - CountingPredicate(csvPredicate, &ncsv), + cachePredicates = append(cachePredicates, cache.And( + cache.CountingPredicate(cache.True(), &nall), + cache.CountingPredicate(cache.PkgPredicate(sub.Spec.Package), &npkg), + cache.CountingPredicate(cache.ChannelPredicate(sub.Spec.Channel), &nch), + cache.CountingPredicate(csvPredicate, &ncsv), )) bundles = namespacedCache.Catalog(catalog).Find(cachePredicates...) @@ -219,23 +224,23 @@ func (r *SatResolver) getSubscriptionInstallables(sub *v1alpha1.Subscription, cu // bundles in the default channel appear first, then lexicographically order by channel name sort.SliceStable(bundles, func(i, j int) bool { var idef bool - if isrc := bundles[i].SourceInfo(); isrc != nil { + if isrc := bundles[i].SourceInfo; isrc != nil { idef = isrc.DefaultChannel } var jdef bool - if jsrc := bundles[j].SourceInfo(); jsrc != nil { + if jsrc := bundles[j].SourceInfo; jsrc != nil { jdef = jsrc.DefaultChannel } if idef == jdef { - return bundles[i].bundle.ChannelName < bundles[j].bundle.ChannelName + return bundles[i].Bundle.ChannelName < bundles[j].Bundle.ChannelName } return idef }) - var sortedBundles []*Operator + var sortedBundles []*cache.Operator lastChannel, lastIndex := "", 0 for i := 0; i <= len(bundles); i++ { - if i != len(bundles) && bundles[i].bundle.ChannelName == lastChannel { + if i != len(bundles) && bundles[i].Bundle.ChannelName == lastChannel { continue } channel, err := sortChannel(bundles[lastIndex:i]) @@ -245,14 +250,14 @@ func (r *SatResolver) getSubscriptionInstallables(sub *v1alpha1.Subscription, cu sortedBundles = append(sortedBundles, channel...) if i != len(bundles) { - lastChannel = bundles[i].bundle.ChannelName + lastChannel = bundles[i].Bundle.ChannelName lastIndex = i } } candidates := make([]*BundleInstallable, 0) - for _, o := range Filter(sortedBundles, channelPredicates...) { - predicates := append(cachePredicates, CSVNamePredicate(o.Identifier())) + for _, o := range cache.Filter(sortedBundles, channelPredicates...) { + predicates := append(cachePredicates, cache.CSVNamePredicate(o.Identifier())) stack := namespacedCache.Catalog(catalog).Find(predicates...) id, installable, err := r.getBundleInstallables(catalog, stack, namespacedCache, visited) if err != nil { @@ -298,12 +303,12 @@ func (r *SatResolver) getSubscriptionInstallables(sub *v1alpha1.Subscription, cu return installables, nil } -func (r *SatResolver) getBundleInstallables(catalog registry.CatalogKey, bundleStack []*Operator, namespacedCache MultiCatalogOperatorFinder, visited map[OperatorSurface]*BundleInstallable) (map[solver.Identifier]struct{}, map[solver.Identifier]*BundleInstallable, error) { +func (r *SatResolver) getBundleInstallables(catalog registry.CatalogKey, bundleStack []*cache.Operator, namespacedCache cache.MultiCatalogOperatorFinder, visited map[cache.OperatorSurface]*BundleInstallable) (map[solver.Identifier]struct{}, map[solver.Identifier]*BundleInstallable, error) { errs := make([]error, 0) installables := make(map[solver.Identifier]*BundleInstallable, 0) // all installables, including dependencies // track the first layer of installable ids - var initial = make(map[*Operator]struct{}) + var initial = make(map[*cache.Operator]struct{}) for _, o := range bundleStack { initial[o] = struct{}{} } @@ -336,15 +341,15 @@ func (r *SatResolver) getBundleInstallables(catalog registry.CatalogKey, bundleS } for _, d := range dependencyPredicates { - sourcePredicate := False() + sourcePredicate := cache.False() // Build a filter matching all (catalog, // package, channel) combinations that contain // at least one candidate bundle, even if only // a subset of those bundles actually satisfy // the dependency. - sources := map[OperatorSourceInfo]struct{}{} + sources := map[cache.OperatorSourceInfo]struct{}{} for _, b := range namespacedCache.Find(d) { - si := b.SourceInfo() + si := b.SourceInfo if _, ok := sources[*si]; ok { // Predicate already covers this source. @@ -353,19 +358,19 @@ func (r *SatResolver) getBundleInstallables(catalog registry.CatalogKey, bundleS sources[*si] = struct{}{} if si.Catalog.Virtual() { - sourcePredicate = Or(sourcePredicate, And( - CSVNamePredicate(b.Identifier()), - CatalogPredicate(si.Catalog), + sourcePredicate = cache.Or(sourcePredicate, cache.And( + cache.CSVNamePredicate(b.Identifier()), + cache.CatalogPredicate(si.Catalog), )) } else { - sourcePredicate = Or(sourcePredicate, And( - PkgPredicate(si.Package), - ChannelPredicate(si.Channel), - CatalogPredicate(si.Catalog), + sourcePredicate = cache.Or(sourcePredicate, cache.And( + cache.PkgPredicate(si.Package), + cache.ChannelPredicate(si.Channel), + cache.CatalogPredicate(si.Catalog), )) } } - sortedBundles, err := r.sortBundles(namespacedCache.FindPreferred(&bundle.sourceInfo.Catalog, sourcePredicate)) + sortedBundles, err := r.sortBundles(namespacedCache.FindPreferred(&bundle.SourceInfo.Catalog, sourcePredicate)) if err != nil { errs = append(errs, err) continue @@ -374,7 +379,7 @@ func (r *SatResolver) getBundleInstallables(catalog registry.CatalogKey, bundleS // The dependency predicate is applied here // (after sorting) to remove all bundles that // don't satisfy the dependency. - for _, b := range Filter(sortedBundles, d) { + for _, b := range cache.Filter(sortedBundles, d) { i, err := NewBundleInstallableFromOperator(b) if err != nil { errs = append(errs, err) @@ -386,7 +391,7 @@ func (r *SatResolver) getBundleInstallables(catalog registry.CatalogKey, bundleS } bundleInstallable.AddConstraint(PrettyConstraint( solver.Dependency(bundleDependencies...), - fmt.Sprintf("bundle %s requires an operator %s", bundle.name, d.String()), + fmt.Sprintf("bundle %s requires an operator %s", bundle.Name, d.String()), )) } @@ -417,7 +422,7 @@ func (r *SatResolver) inferProperties(csv *v1alpha1.ClusterServiceVersion, subs // package against catalog contents, updates to the // Subscription spec could result in a bad package // inference. - for _, entry := range r.cache.Namespaced(sub.Namespace).Catalog(registry.CatalogKey{Namespace: sub.Spec.CatalogSourceNamespace, Name: sub.Spec.CatalogSource}).Find(And(CSVNamePredicate(csv.Name), PkgPredicate(sub.Spec.Package))) { + for _, entry := range r.cache.Namespaced(sub.Namespace).Catalog(registry.CatalogKey{Namespace: sub.Spec.CatalogSourceNamespace, Name: sub.Spec.CatalogSource}).Find(cache.And(cache.CSVNamePredicate(csv.Name), cache.PkgPredicate(sub.Spec.Package))) { if pkg := entry.Package(); pkg != "" { packages[pkg] = struct{}{} } @@ -450,7 +455,7 @@ func (r *SatResolver) inferProperties(csv *v1alpha1.ClusterServiceVersion, subs return properties, nil } -func (r *SatResolver) newSnapshotForNamespace(namespace string, subs []*v1alpha1.Subscription, csvs []*v1alpha1.ClusterServiceVersion) (*CatalogSnapshot, error) { +func (r *SatResolver) newSnapshotForNamespace(namespace string, subs []*v1alpha1.Subscription, csvs []*v1alpha1.ClusterServiceVersion) (*cache.CatalogSnapshot, error) { existingOperatorCatalog := registry.NewVirtualCatalogKey(namespace) // build a catalog snapshot of CSVs without subscriptions csvSubscriptions := make(map[*v1alpha1.ClusterServiceVersion]*v1alpha1.Subscription) @@ -463,9 +468,9 @@ func (r *SatResolver) newSnapshotForNamespace(namespace string, subs []*v1alpha1 } } var csvsMissingProperties []*v1alpha1.ClusterServiceVersion - standaloneOperators := make([]*Operator, 0) + standaloneOperators := make([]*cache.Operator, 0) for _, csv := range csvs { - op, err := NewOperatorFromV1Alpha1CSV(csv) + op, err := cache.NewOperatorFromV1Alpha1CSV(csv) if err != nil { return nil, err } @@ -475,20 +480,20 @@ func (r *SatResolver) newSnapshotForNamespace(namespace string, subs []*v1alpha1 if inferred, err := r.inferProperties(csv, subs); err != nil { r.log.Warnf("unable to infer properties for csv %q: %w", csv.Name, err) } else { - op.properties = append(op.properties, inferred...) + op.Properties = append(op.Properties, inferred...) } } else if props, err := projection.PropertyListFromPropertiesAnnotation(anno); err != nil { return nil, fmt.Errorf("failed to retrieve properties of csv %q: %w", csv.GetName(), err) } else { - op.properties = props + op.Properties = props } - op.sourceInfo = &OperatorSourceInfo{ + op.SourceInfo = &cache.OperatorSourceInfo{ Catalog: existingOperatorCatalog, Subscription: csvSubscriptions[csv], } // Try to determine source package name from properties and add to SourceInfo. - for _, p := range op.properties { + for _, p := range op.Properties { if p.Type != opregistry.PackageType { continue } @@ -498,7 +503,7 @@ func (r *SatResolver) newSnapshotForNamespace(namespace string, subs []*v1alpha1 r.log.Warnf("failed to unmarshal package property of csv %q: %w", csv.Name, err) continue } - op.sourceInfo.Package = pp.PackageName + op.SourceInfo.Package = pp.PackageName } standaloneOperators = append(standaloneOperators, op) @@ -512,10 +517,10 @@ func (r *SatResolver) newSnapshotForNamespace(namespace string, subs []*v1alpha1 r.log.Infof("considered csvs without properties annotation during resolution: %v", names) } - return NewRunningOperatorSnapshot(r.log, existingOperatorCatalog, standaloneOperators), nil + return cache.NewRunningOperatorSnapshot(r.log, existingOperatorCatalog, standaloneOperators), nil } -func (r *SatResolver) addInvariants(namespacedCache MultiCatalogOperatorFinder, installables map[solver.Identifier]solver.Installable) { +func (r *SatResolver) addInvariants(namespacedCache cache.MultiCatalogOperatorFinder, installables map[solver.Identifier]solver.Installable) { // no two operators may provide the same GVK or Package in a namespace gvkConflictToInstallable := make(map[opregistry.GVKProperty][]solver.Identifier) packageConflictToInstallable := make(map[string][]solver.Identifier) @@ -529,13 +534,13 @@ func (r *SatResolver) addInvariants(namespacedCache MultiCatalogOperatorFinder, continue } - op, err := ExactlyOne(namespacedCache.Catalog(catalog).Find(CSVNamePredicate(csvName), ChannelPredicate(channel))) + op, err := cache.ExactlyOne(namespacedCache.Catalog(catalog).Find(cache.CSVNamePredicate(csvName), cache.ChannelPredicate(channel))) if err != nil { continue } // cannot provide the same GVK - for _, p := range op.Properties() { + for _, p := range op.Properties { if p.Type != opregistry.GVKType { continue } @@ -548,7 +553,7 @@ func (r *SatResolver) addInvariants(namespacedCache MultiCatalogOperatorFinder, } // cannot have the same package - for _, p := range op.Properties() { + for _, p := range op.Properties { if p.Type != opregistry.PackageType { continue } @@ -572,7 +577,7 @@ func (r *SatResolver) addInvariants(namespacedCache MultiCatalogOperatorFinder, } } -func (r *SatResolver) sortBundles(bundles []*Operator) ([]*Operator, error) { +func (r *SatResolver) sortBundles(bundles []*cache.Operator) ([]*cache.Operator, error) { // assume bundles have been passed in sorted by catalog already catalogOrder := make([]registry.CatalogKey, 0) @@ -584,22 +589,22 @@ func (r *SatResolver) sortBundles(bundles []*Operator) ([]*Operator, error) { channelOrder := make(map[registry.CatalogKey][]PackageChannel) // partition by catalog -> channel -> bundle - partitionedBundles := map[registry.CatalogKey]map[PackageChannel][]*Operator{} + partitionedBundles := map[registry.CatalogKey]map[PackageChannel][]*cache.Operator{} for _, b := range bundles { pc := PackageChannel{ Package: b.Package(), Channel: b.Channel(), - DefaultChannel: b.SourceInfo().DefaultChannel, + DefaultChannel: b.SourceInfo.DefaultChannel, } - if _, ok := partitionedBundles[b.sourceInfo.Catalog]; !ok { - catalogOrder = append(catalogOrder, b.sourceInfo.Catalog) - partitionedBundles[b.sourceInfo.Catalog] = make(map[PackageChannel][]*Operator) + if _, ok := partitionedBundles[b.SourceInfo.Catalog]; !ok { + catalogOrder = append(catalogOrder, b.SourceInfo.Catalog) + partitionedBundles[b.SourceInfo.Catalog] = make(map[PackageChannel][]*cache.Operator) } - if _, ok := partitionedBundles[b.sourceInfo.Catalog][pc]; !ok { - channelOrder[b.sourceInfo.Catalog] = append(channelOrder[b.sourceInfo.Catalog], pc) - partitionedBundles[b.sourceInfo.Catalog][pc] = make([]*Operator, 0) + if _, ok := partitionedBundles[b.SourceInfo.Catalog][pc]; !ok { + channelOrder[b.SourceInfo.Catalog] = append(channelOrder[b.SourceInfo.Catalog], pc) + partitionedBundles[b.SourceInfo.Catalog][pc] = make([]*cache.Operator, 0) } - partitionedBundles[b.sourceInfo.Catalog][pc] = append(partitionedBundles[b.sourceInfo.Catalog][pc], b) + partitionedBundles[b.SourceInfo.Catalog][pc] = append(partitionedBundles[b.SourceInfo.Catalog][pc], b) } for catalog := range partitionedBundles { @@ -621,7 +626,7 @@ func (r *SatResolver) sortBundles(bundles []*Operator) ([]*Operator, error) { partitionedBundles[catalog][channel] = sorted } } - all := make([]*Operator, 0) + all := make([]*cache.Operator, 0) for _, catalog := range catalogOrder { for _, channel := range channelOrder[catalog] { all = append(all, partitionedBundles[catalog][channel]...) @@ -632,7 +637,7 @@ func (r *SatResolver) sortBundles(bundles []*Operator) ([]*Operator, error) { // Sorts bundle in a channel by replaces. All entries in the argument // are assumed to have the same Package and Channel. -func sortChannel(bundles []*Operator) ([]*Operator, error) { +func sortChannel(bundles []*cache.Operator) ([]*cache.Operator, error) { if len(bundles) < 1 { return bundles, nil } @@ -640,25 +645,25 @@ func sortChannel(bundles []*Operator) ([]*Operator, error) { packageName := bundles[0].Package() channelName := bundles[0].Channel() - bundleLookup := map[string]*Operator{} + bundleLookup := map[string]*cache.Operator{} // if a replaces b, then replacedBy[b] = a - replacedBy := map[*Operator]*Operator{} - replaces := map[*Operator]*Operator{} - skipped := map[string]*Operator{} + replacedBy := map[*cache.Operator]*cache.Operator{} + replaces := map[*cache.Operator]*cache.Operator{} + skipped := map[string]*cache.Operator{} for _, b := range bundles { bundleLookup[b.Identifier()] = b } for _, b := range bundles { - if b.replaces != "" { - if r, ok := bundleLookup[b.replaces]; ok { + if b.Replaces != "" { + if r, ok := bundleLookup[b.Replaces]; ok { replacedBy[r] = b replaces[b] = r } } - for _, skip := range b.skips { + for _, skip := range b.Skips { if r, ok := bundleLookup[skip]; ok { replacedBy[r] = b skipped[skip] = r @@ -668,7 +673,7 @@ func sortChannel(bundles []*Operator) ([]*Operator, error) { // a bundle without a replacement is a channel head, but if we // find more than one of those something is weird - headCandidates := []*Operator{} + headCandidates := []*cache.Operator{} for _, b := range bundles { if _, ok := replacedBy[b]; !ok { headCandidates = append(headCandidates, b) @@ -678,10 +683,10 @@ func sortChannel(bundles []*Operator) ([]*Operator, error) { return nil, fmt.Errorf("no channel heads (entries not replaced by another entry) found in channel %q of package %q", channelName, packageName) } - var chains [][]*Operator + var chains [][]*cache.Operator for _, head := range headCandidates { - var chain []*Operator - visited := make(map[*Operator]struct{}) + var chain []*cache.Operator + visited := make(map[*cache.Operator]struct{}) current := head for { visited[current] = struct{}{} diff --git a/vendor/github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/step_resolver.go b/vendor/github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/step_resolver.go index 6f211e1bd4..9fb369ca32 100644 --- a/vendor/github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/step_resolver.go +++ b/vendor/github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/step_resolver.go @@ -19,6 +19,7 @@ import ( v1alpha1listers "github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/listers/operators/v1alpha1" controllerbundle "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/bundle" "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry" + "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/cache" "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/projection" "github.com/operator-framework/operator-lifecycle-manager/pkg/lib/operatorlister" ) @@ -48,7 +49,7 @@ type OperatorStepResolver struct { var _ StepResolver = &OperatorStepResolver{} func NewOperatorStepResolver(lister operatorlister.OperatorLister, client versioned.Interface, kubeclient kubernetes.Interface, - globalCatalogNamespace string, provider RegistryClientProvider, log logrus.FieldLogger) *OperatorStepResolver { + globalCatalogNamespace string, provider cache.RegistryClientProvider, log logrus.FieldLogger) *OperatorStepResolver { return &OperatorStepResolver{ subLister: lister.OperatorsV1alpha1().SubscriptionLister(), csvLister: lister.OperatorsV1alpha1().ClusterServiceVersionLister(), @@ -56,7 +57,7 @@ func NewOperatorStepResolver(lister operatorlister.OperatorLister, client versio client: client, kubeclient: kubeclient, globalCatalogNamespace: globalCatalogNamespace, - satResolver: NewDefaultSatResolver(NewDefaultRegistryClientProvider(log, provider), lister.OperatorsV1alpha1().CatalogSourceLister(), log), + satResolver: NewDefaultSatResolver(cache.NewDefaultRegistryClientProvider(log, provider), lister.OperatorsV1alpha1().CatalogSourceLister(), log), log: log, } } @@ -86,7 +87,7 @@ func (r *OperatorStepResolver) ResolveSteps(namespace string, _ SourceQuerier) ( return nil, nil, nil, err } - var operators OperatorSet + var operators cache.OperatorSet namespaces := []string{namespace, r.globalCatalogNamespace} operators, err = r.satResolver.SolveOperators(namespaces, csvs, subs) if err != nil { @@ -101,7 +102,7 @@ func (r *OperatorStepResolver) ResolveSteps(namespace string, _ SourceQuerier) ( for name, op := range operators { // Find any existing subscriptions that resolve to this operator. existingSubscriptions := make(map[*v1alpha1.Subscription]bool) - sourceInfo := *op.SourceInfo() + sourceInfo := *op.GetSourceInfo() for _, sub := range subs { if sub.Spec.Package != sourceInfo.Package { continue @@ -137,21 +138,21 @@ func (r *OperatorStepResolver) ResolveSteps(namespace string, _ SourceQuerier) ( } // add steps for any new bundle - if op.Bundle() != nil { + if op.GetBundle() != nil { if op.Inline() { - bundleSteps, err := NewStepsFromBundle(op.Bundle(), namespace, op.Replaces(), op.SourceInfo().Catalog.Name, op.SourceInfo().Catalog.Namespace) + bundleSteps, err := NewStepsFromBundle(op.GetBundle(), namespace, op.GetReplaces(), op.GetSourceInfo().Catalog.Name, op.GetSourceInfo().Catalog.Namespace) if err != nil { return nil, nil, nil, fmt.Errorf("failed to turn bundle into steps: %s", err.Error()) } steps = append(steps, bundleSteps...) } else { lookup := v1alpha1.BundleLookup{ - Path: op.Bundle().GetBundlePath(), + Path: op.GetBundle().GetBundlePath(), Identifier: op.Identifier(), - Replaces: op.Replaces(), + Replaces: op.GetReplaces(), CatalogSourceRef: &corev1.ObjectReference{ - Namespace: op.SourceInfo().Catalog.Namespace, - Name: op.SourceInfo().Catalog.Name, + Namespace: op.GetSourceInfo().Catalog.Namespace, + Name: op.GetSourceInfo().Catalog.Name, }, Conditions: []v1alpha1.BundleLookupCondition{ { @@ -168,7 +169,7 @@ func (r *OperatorStepResolver) ResolveSteps(namespace string, _ SourceQuerier) ( }, }, } - if anno, err := projection.PropertiesAnnotationFromPropertyList(op.Properties()); err != nil { + if anno, err := projection.PropertiesAnnotationFromPropertyList(op.GetProperties()); err != nil { return nil, nil, nil, fmt.Errorf("failed to serialize operator properties for %q: %w", op.Identifier(), err) } else { lookup.Properties = anno @@ -178,8 +179,8 @@ func (r *OperatorStepResolver) ResolveSteps(namespace string, _ SourceQuerier) ( if len(existingSubscriptions) == 0 { // explicitly track the resolved CSV as the starting CSV on the resolved subscriptions - op.SourceInfo().StartingCSV = op.Identifier() - subStep, err := NewSubscriptionStepResource(namespace, *op.SourceInfo()) + op.GetSourceInfo().StartingCSV = op.Identifier() + subStep, err := NewSubscriptionStepResource(namespace, *op.GetSourceInfo()) if err != nil { return nil, nil, nil, err } diff --git a/vendor/github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/steps.go b/vendor/github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/steps.go index d800dff869..7f2270881b 100644 --- a/vendor/github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/steps.go +++ b/vendor/github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/steps.go @@ -17,6 +17,7 @@ import ( k8sscheme "k8s.io/client-go/kubernetes/scheme" "github.com/operator-framework/api/pkg/operators/v1alpha1" + "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/cache" "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/projection" "github.com/operator-framework/operator-lifecycle-manager/pkg/lib/ownerutil" ) @@ -86,7 +87,7 @@ func NewStepResourceFromObject(obj runtime.Object, catalogSourceName, catalogSou return resource, nil } -func NewSubscriptionStepResource(namespace string, info OperatorSourceInfo) (v1alpha1.StepResource, error) { +func NewSubscriptionStepResource(namespace string, info cache.OperatorSourceInfo) (v1alpha1.StepResource, error) { return NewStepResourceFromObject(&v1alpha1.Subscription{ ObjectMeta: metav1.ObjectMeta{ Namespace: namespace, diff --git a/vendor/github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/types.go b/vendor/github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/types.go index 11c3f99fa8..55e2c9f40c 100644 --- a/vendor/github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/types.go +++ b/vendor/github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/types.go @@ -33,7 +33,7 @@ func (k *CatalogKey) Virtual() bool { func NewVirtualCatalogKey(namespace string) CatalogKey { return CatalogKey{ - Name: ExistingOperatorKey, + Name: ExistingOperatorKey, Namespace: namespace, } } diff --git a/vendor/github.com/operator-framework/operator-registry/cmd/opm/index/deprecatetruncate.go b/vendor/github.com/operator-framework/operator-registry/cmd/opm/index/deprecatetruncate.go index 4413440a6f..dc76e9ab9c 100644 --- a/vendor/github.com/operator-framework/operator-registry/cmd/opm/index/deprecatetruncate.go +++ b/vendor/github.com/operator-framework/operator-registry/cmd/opm/index/deprecatetruncate.go @@ -26,7 +26,9 @@ var deprecateLong = templates.LongDesc(` Produces the following update graph in quay.io/my/index:v2 1.4.0 -- replaces -> 1.3.0 [deprecated] - Deprecating a bundle that removes the default channel is not allowed. Changing the default channel prior to deprecation is possible by publishing a new bundle to the index. + Deprecating a bundle that removes the default channel is not allowed unless the head(s) of all channels are being deprecated (the package is subsequently removed from the index). + This behavior can be enabled via the allow-package-removal flag. + Changing the default channel prior to deprecation is possible by publishing a new bundle to the index. `) + "\n\n" + sqlite.DeprecationMessage func newIndexDeprecateTruncateCmd() *cobra.Command { @@ -61,6 +63,7 @@ func newIndexDeprecateTruncateCmd() *cobra.Command { if err := indexCmd.Flags().MarkHidden("debug"); err != nil { logrus.Panic(err.Error()) } + indexCmd.Flags().Bool("allow-package-removal", false, "removes the entire package if the heads of all channels in the package are deprecated") return indexCmd } @@ -111,6 +114,11 @@ func runIndexDeprecateTruncateCmdFunc(cmd *cobra.Command, args []string) error { return err } + allowPackageRemoval, err := cmd.Flags().GetBool("allow-package-removal") + if err != nil { + return err + } + logger := logrus.WithFields(logrus.Fields{"bundles": bundles}) logger.Info("deprecating bundles from the index") @@ -121,14 +129,15 @@ func runIndexDeprecateTruncateCmdFunc(cmd *cobra.Command, args []string) error { logger) request := indexer.DeprecateFromIndexRequest{ - Generate: generate, - FromIndex: fromIndex, - BinarySourceImage: binaryImage, - OutDockerfile: outDockerfile, - Tag: tag, - Bundles: bundles, - Permissive: permissive, - SkipTLS: skipTLS, + Generate: generate, + FromIndex: fromIndex, + BinarySourceImage: binaryImage, + OutDockerfile: outDockerfile, + Tag: tag, + Bundles: bundles, + Permissive: permissive, + SkipTLS: skipTLS, + AllowPackageRemoval: allowPackageRemoval, } err = indexDeprecator.DeprecateFromIndex(request) diff --git a/vendor/github.com/operator-framework/operator-registry/internal/declcfg/load.go b/vendor/github.com/operator-framework/operator-registry/internal/declcfg/load.go index bc9c23e2cd..c53c61d7e9 100644 --- a/vendor/github.com/operator-framework/operator-registry/internal/declcfg/load.go +++ b/vendor/github.com/operator-framework/operator-registry/internal/declcfg/load.go @@ -17,6 +17,10 @@ import ( "github.com/operator-framework/operator-registry/internal/property" ) +const ( + indexIgnoreFilename = ".indexignore" +) + type WalkFunc func(path string, cfg *DeclarativeConfig, err error) error // WalkFS walks root using a gitignore-style filename matcher to skip files @@ -27,7 +31,7 @@ func WalkFS(root fs.FS, walkFn WalkFunc) error { if root == nil { return fmt.Errorf("no declarative config filesystem provided") } - matcher, err := ignore.NewMatcher(root, ".indexignore") + matcher, err := ignore.NewMatcher(root, indexIgnoreFilename) if err != nil { return err } @@ -36,7 +40,9 @@ func WalkFS(root fs.FS, walkFn WalkFunc) error { if err != nil { return walkFn(path, nil, err) } - if info.IsDir() || matcher.Match(path, false) { + // avoid validating a directory, an .indexignore file, or any file that matches + // an ignore pattern outlined in a .indexignore file. + if info.IsDir() || info.Name() == indexIgnoreFilename || matcher.Match(path, false) { return nil } file, err := root.Open(path) diff --git a/vendor/github.com/operator-framework/operator-registry/pkg/lib/bundle/supported_resources.go b/vendor/github.com/operator-framework/operator-registry/pkg/lib/bundle/supported_resources.go index 534e6568c1..20d7e5402e 100644 --- a/vendor/github.com/operator-framework/operator-registry/pkg/lib/bundle/supported_resources.go +++ b/vendor/github.com/operator-framework/operator-registry/pkg/lib/bundle/supported_resources.go @@ -17,6 +17,9 @@ const ( PriorityClassKind = "PriorityClass" VerticalPodAutoscalerKind = "VerticalPodAutoscaler" ConsoleYamlSampleKind = "ConsoleYamlSample" + ConsoleQuickStartKind = "ConsoleQuickStart" + ConsoleCLIDownloadKind = "ConsoleCLIDownload" + ConsoleLinkKind = "ConsoleLink" ) // Namespaced indicates whether the resource is namespace scoped (true) or cluster-scoped (false). @@ -41,6 +44,9 @@ var supportedResources = map[string]Namespaced{ PriorityClassKind: false, VerticalPodAutoscalerKind: false, ConsoleYamlSampleKind: false, + ConsoleQuickStartKind: false, + ConsoleCLIDownloadKind: false, + ConsoleLinkKind: false, } // IsSupported checks if the object kind is OLM-supported and if it is namespaced diff --git a/vendor/github.com/operator-framework/operator-registry/pkg/lib/indexer/indexer.go b/vendor/github.com/operator-framework/operator-registry/pkg/lib/indexer/indexer.go index d15e3472d2..54eae4db1a 100644 --- a/vendor/github.com/operator-framework/operator-registry/pkg/lib/indexer/indexer.go +++ b/vendor/github.com/operator-framework/operator-registry/pkg/lib/indexer/indexer.go @@ -643,15 +643,16 @@ func generatePackageYaml(dbQuerier pregistry.Query, packageName, downloadPath st // DeprecateFromIndexRequest defines the parameters to send to the PruneFromIndex API type DeprecateFromIndexRequest struct { - Generate bool - Permissive bool - BinarySourceImage string - FromIndex string - OutDockerfile string - Bundles []string - Tag string - CaFile string - SkipTLS bool + Generate bool + Permissive bool + BinarySourceImage string + FromIndex string + OutDockerfile string + Bundles []string + Tag string + CaFile string + SkipTLS bool + AllowPackageRemoval bool } // DeprecateFromIndex takes a DeprecateFromIndexRequest and deprecates the requested @@ -668,14 +669,14 @@ func (i ImageIndexer) DeprecateFromIndex(request DeprecateFromIndexRequest) erro return err } - // Run opm registry prune on the database deprecateFromRegistryReq := registry.DeprecateFromRegistryRequest{ - Bundles: request.Bundles, - InputDatabase: databasePath, - Permissive: request.Permissive, + Bundles: request.Bundles, + InputDatabase: databasePath, + Permissive: request.Permissive, + AllowPackageRemoval: request.AllowPackageRemoval, } - // Prune the bundles from the registry + // Deprecate the bundles from the registry err = i.RegistryDeprecator.DeprecateFromRegistry(deprecateFromRegistryReq) if err != nil { return err diff --git a/vendor/github.com/operator-framework/operator-registry/pkg/lib/registry/registry.go b/vendor/github.com/operator-framework/operator-registry/pkg/lib/registry/registry.go index 34dc493e2c..e8ec05cce2 100644 --- a/vendor/github.com/operator-framework/operator-registry/pkg/lib/registry/registry.go +++ b/vendor/github.com/operator-framework/operator-registry/pkg/lib/registry/registry.go @@ -331,9 +331,10 @@ func (r RegistryUpdater) PruneFromRegistry(request PruneFromRegistryRequest) err } type DeprecateFromRegistryRequest struct { - Permissive bool - InputDatabase string - Bundles []string + Permissive bool + InputDatabase string + Bundles []string + AllowPackageRemoval bool } func (r RegistryUpdater) DeprecateFromRegistry(request DeprecateFromRegistryRequest) error { @@ -366,6 +367,23 @@ func (r RegistryUpdater) DeprecateFromRegistry(request DeprecateFromRegistryRequ } deprecator := sqlite.NewSQLDeprecatorForBundles(dbLoader, toDeprecate) + + // Check for deprecation of head of default channel. If deprecation request includes heads of all other channels, + // then remove the package entirely. Otherwise, deprecate provided bundles. This enables deprecating an entire package. + // By default deprecating the head of default channel is not permitted. + if request.AllowPackageRemoval { + packageDeprecator := sqlite.NewSQLDeprecatorForBundlesAndPackages(deprecator, dbQuerier) + if err := packageDeprecator.MaybeRemovePackages(); err != nil { + r.Logger.Debugf("unable to deprecate package from database: %s", err) + if !request.Permissive { + r.Logger.WithError(err).Error("permissive mode disabled") + return err + } + r.Logger.WithError(err).Warn("permissive mode enabled") + } + } + + // Any bundles associated with removed packages are now removed from the list of bundles to deprecate. if err := deprecator.Deprecate(); err != nil { r.Logger.Debugf("unable to deprecate bundles from database: %s", err) if !request.Permissive { diff --git a/vendor/github.com/operator-framework/operator-registry/pkg/sqlite/deprecate.go b/vendor/github.com/operator-framework/operator-registry/pkg/sqlite/deprecate.go index a8cdb24fa9..4ac3d61ebf 100644 --- a/vendor/github.com/operator-framework/operator-registry/pkg/sqlite/deprecate.go +++ b/vendor/github.com/operator-framework/operator-registry/pkg/sqlite/deprecate.go @@ -1,6 +1,7 @@ package sqlite import ( + "context" "errors" "fmt" @@ -20,7 +21,14 @@ type BundleDeprecator struct { bundles []string } +// PackageDeprecator removes bundles and optionally entire packages from the index +type PackageDeprecator struct { + *BundleDeprecator + querier *SQLQuerier +} + var _ SQLDeprecator = &BundleDeprecator{} +var _ SQLDeprecator = &PackageDeprecator{} func NewSQLDeprecatorForBundles(store registry.Load, bundles []string) *BundleDeprecator { return &BundleDeprecator{ @@ -29,6 +37,13 @@ func NewSQLDeprecatorForBundles(store registry.Load, bundles []string) *BundleDe } } +func NewSQLDeprecatorForBundlesAndPackages(deprecator *BundleDeprecator, querier *SQLQuerier) *PackageDeprecator { + return &PackageDeprecator{ + BundleDeprecator: deprecator, + querier: querier, + } +} + func (d *BundleDeprecator) Deprecate() error { log := logrus.WithField("bundles", d.bundles) log.Info("deprecating bundles") @@ -45,3 +60,87 @@ func (d *BundleDeprecator) Deprecate() error { return utilerrors.NewAggregate(errs) } + +// MaybeRemovePackages queries the DB to establish if any provided bundles are the head of the default channel of a package. +// If so, the list of bundles must also contain the head of all other channels in the package, otherwise an error is produced. +// If the heads of all channels are being deprecated (including the default channel), the package is removed entirely from the index. +// MaybeRemovePackages deletes all bundles from the associated package from the bundles array, so that the subsequent +// Deprecate() call can proceed with deprecating other potential bundles from other packages. +func (d *PackageDeprecator) MaybeRemovePackages() error { + log := logrus.WithField("bundles", d.bundles) + log.Info("allow-package-removal enabled: checking default channel heads for package removal") + + var errs []error + var removedBundlePaths []string + var remainingBundlePaths []string + + // Iterate over bundles list - see if any bundle is the head of a default channel in a package + var packages []string + for _, bundle := range d.bundles { + found, err := d.querier.PackageFromDefaultChannelHeadBundle(context.TODO(), bundle) + if err != nil { + errs = append(errs, fmt.Errorf("error checking if bundle is default channel head %s: %s", bundle, err)) + } + if found != "" { + packages = append(packages, found) + } + } + + if len(packages) == 0 { + log.Info("no head of default channel found - skipping package removal") + return nil + } + + // If so, ensure list contains head of all other channels in that package + // If not, return error + for _, pkg := range packages { + channels, err := d.querier.ListChannels(context.TODO(), pkg) + if err != nil { + errs = append(errs, fmt.Errorf("error listing channels for package %s: %s", pkg, err)) + } + for _, channel := range channels { + found, err := d.querier.BundlePathForChannelHead(context.TODO(), pkg, channel) + if err != nil { + errs = append(errs, fmt.Errorf("error listing channel head for package %s: %s", pkg, err)) + } + if !contains(found, d.bundles) { + // terminal error + errs = append(errs, fmt.Errorf("cannot deprecate default channel head from package without removing all other channel heads in package %s: must deprecate %s, head of channel %s", pkg, found, channel)) + return utilerrors.NewAggregate(errs) + } + removedBundlePaths = append(removedBundlePaths, found) + } + } + + // Remove associated package from index + log.Infof("removing packages %#v", packages) + for _, pkg := range packages { + err := d.store.RemovePackage(pkg) + if err != nil { + errs = append(errs, fmt.Errorf("error removing package %s: %s", pkg, err)) + } + } + + // Remove bundles from the removed package from the deprecation request + // This enables other bundles to be deprecated via the expected flow + // Build a new array with just the outstanding bundles + for _, bundlePath := range d.bundles { + if contains(bundlePath, removedBundlePaths) { + continue + } + remainingBundlePaths = append(remainingBundlePaths, bundlePath) + } + d.bundles = remainingBundlePaths + log.Infof("remaining bundles to deprecate %#v", d.bundles) + + return utilerrors.NewAggregate(errs) +} + +func contains(bundlePath string, bundles []string) bool { + for _, b := range bundles { + if b == bundlePath { + return true + } + } + return false +} diff --git a/vendor/github.com/operator-framework/operator-registry/pkg/sqlite/query.go b/vendor/github.com/operator-framework/operator-registry/pkg/sqlite/query.go index 6a30c35bfe..432b998112 100644 --- a/vendor/github.com/operator-framework/operator-registry/pkg/sqlite/query.go +++ b/vendor/github.com/operator-framework/operator-registry/pkg/sqlite/query.go @@ -1338,3 +1338,57 @@ func (s *SQLQuerier) listBundleChannels(ctx context.Context, bundleName string) return channels, nil } + +// PackageFromDefaultChannelHeadBundle returns the package name if the provided bundle is the head of its default channel. +func (s *SQLQuerier) PackageFromDefaultChannelHeadBundle(ctx context.Context, bundle string) (string, error) { + packageFromDefaultChannelHeadBundle := ` + SELECT package_name FROM package + INNER JOIN channel ON channel.name = package.default_channel + WHERE channel.head_operatorbundle_name = (SELECT name FROM operatorbundle WHERE bundlepath=? LIMIT 1) ` + + rows, err := s.db.QueryContext(ctx, packageFromDefaultChannelHeadBundle, bundle) + if err != nil { + return "", err + } + defer rows.Close() + + var packageName sql.NullString + for rows.Next() { + if err := rows.Scan(&packageName); err != nil { + return "", err + } + + if !packageName.Valid { + return "", fmt.Errorf("package name column corrupt for bundle %s", bundle) + } + } + + return packageName.String, nil +} + +// BundlePathForChannelHead returns the bundlepath for the given package and channel +func (s *SQLQuerier) BundlePathForChannelHead(ctx context.Context, pkg string, channel string) (string, error) { + bundlePathForChannelHeadQuery := ` + SELECT bundlepath FROM operatorbundle + INNER JOIN channel ON channel.head_operatorbundle_name = operatorbundle.name + WHERE channel.package_name = ? AND channel.name = ? +` + + rows, err := s.db.QueryContext(ctx, bundlePathForChannelHeadQuery, pkg, channel) + if err != nil { + return "", err + } + defer rows.Close() + + var bundlePath sql.NullString + for rows.Next() { + if err := rows.Scan(&bundlePath); err != nil { + return "", err + } + if !bundlePath.Valid { + return "", fmt.Errorf("bundlepath column corrupt for package %s, channel %s", pkg, channel) + } + } + + return bundlePath.String, nil +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 231b0a63ed..705e1f54a8 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -74,7 +74,7 @@ github.com/blang/semver/v4 github.com/cespare/xxhash/v2 # github.com/containerd/cgroups v0.0.0-20200531161412-0dbf7f05ba59 github.com/containerd/cgroups/stats/v1 -# github.com/containerd/containerd v1.4.4 => github.com/ecordell/containerd v1.3.1-0.20200629153125-0ff1a1be2fa5 +# github.com/containerd/containerd v1.4.8 => github.com/ecordell/containerd v1.3.1-0.20200629153125-0ff1a1be2fa5 github.com/containerd/containerd/archive github.com/containerd/containerd/archive/compression github.com/containerd/containerd/containers @@ -536,6 +536,7 @@ github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/grpc github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/reconciler github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver +github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/cache github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/projection github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/solver github.com/operator-framework/operator-lifecycle-manager/pkg/feature