diff --git a/go.mod b/go.mod index 97c6b63f6d..8cdd998cbe 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/mikefarah/yq/v3 v3.0.0-20201202084205-8846255d1c37 github.com/onsi/ginkgo v1.16.5 github.com/openshift/api v0.0.0-20200331152225-585af27e34fd - github.com/operator-framework/api v0.12.0 + github.com/operator-framework/api v0.14.1-0.20220413143725-33310d6154f3 github.com/operator-framework/operator-lifecycle-manager v0.0.0-00010101000000-000000000000 github.com/operator-framework/operator-registry v1.17.5 github.com/sirupsen/logrus v1.8.1 diff --git a/manifests/0000_50_olm_00-operatorgroups.crd.yaml b/manifests/0000_50_olm_00-operatorgroups.crd.yaml index 36e90f667f..e18d45a577 100644 --- a/manifests/0000_50_olm_00-operatorgroups.crd.yaml +++ b/manifests/0000_50_olm_00-operatorgroups.crd.yaml @@ -39,6 +39,8 @@ spec: spec: description: OperatorGroupSpec is the spec for an OperatorGroup resource. type: object + default: + upgradeStrategy: Default properties: selector: description: Selector selects the OperatorGroup's target namespaces. @@ -82,6 +84,13 @@ spec: items: type: string x-kubernetes-list-type: set + upgradeStrategy: + description: "UpgradeStrategy defines the upgrade strategy for operators in the namespace. There are currently two supported upgrade strategies: \n Default: OLM will only allow clusterServiceVersions to move to the replacing phase from the succeeded phase. This effectively means that OLM will not allow operators to move to the next version if an installation or upgrade has failed. \n TechPreviewUnsafeFailForward: OLM will allow clusterServiceVersions to move to the replacing phase from the succeeded phase or from the failed phase. Additionally, OLM will generate new installPlans when a subscription references a failed installPlan and the catalog has been updated with a new upgrade for the existing set of operators. \n WARNING: The TechPreviewUnsafeFailForward upgrade strategy is unsafe and may result in unexpected behavior or unrecoverable data loss unless you have deep understanding of the set of operators being managed in the namespace." + type: string + default: Default + enum: + - Default + - TechPreviewUnsafeFailForward status: description: OperatorGroupStatus is the status for an OperatorGroupResource. type: object diff --git a/staging/api/crds/operators.coreos.com_operatorgroups.yaml b/staging/api/crds/operators.coreos.com_operatorgroups.yaml index 56ef0b5c4f..b62f3996a9 100644 --- a/staging/api/crds/operators.coreos.com_operatorgroups.yaml +++ b/staging/api/crds/operators.coreos.com_operatorgroups.yaml @@ -37,6 +37,8 @@ spec: spec: description: OperatorGroupSpec is the spec for an OperatorGroup resource. type: object + default: + upgradeStrategy: Default properties: selector: description: Selector selects the OperatorGroup's target namespaces. @@ -80,6 +82,13 @@ spec: items: type: string x-kubernetes-list-type: set + upgradeStrategy: + description: "UpgradeStrategy defines the upgrade strategy for operators in the namespace. There are currently two supported upgrade strategies: \n Default: OLM will only allow clusterServiceVersions to move to the replacing phase from the succeeded phase. This effectively means that OLM will not allow operators to move to the next version if an installation or upgrade has failed. \n TechPreviewUnsafeFailForward: OLM will allow clusterServiceVersions to move to the replacing phase from the succeeded phase or from the failed phase. Additionally, OLM will generate new installPlans when a subscription references a failed installPlan and the catalog has been updated with a new upgrade for the existing set of operators. \n WARNING: The TechPreviewUnsafeFailForward upgrade strategy is unsafe and may result in unexpected behavior or unrecoverable data loss unless you have deep understanding of the set of operators being managed in the namespace." + type: string + default: Default + enum: + - Default + - TechPreviewUnsafeFailForward status: description: OperatorGroupStatus is the status for an OperatorGroupResource. type: object diff --git a/staging/api/crds/zz_defs.go b/staging/api/crds/zz_defs.go index 31a7d8bb5e..46f68c053b 100644 --- a/staging/api/crds/zz_defs.go +++ b/staging/api/crds/zz_defs.go @@ -185,7 +185,7 @@ func operatorsCoreosCom_operatorconditionsYaml() (*asset, error) { return a, nil } -var _operatorsCoreosCom_operatorgroupsYaml = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xec\x5a\xe9\x6f\x1b\x37\x94\xff\xee\xbf\xe2\x41\x5d\x20\x76\x56\x1a\xc5\xee\xa2\xdb\x0a\x08\x02\x23\x69\x0a\x6f\x73\x18\xb1\xdb\x0f\x6b\x7b\xb7\xd4\xcc\xd3\x88\x35\x87\x9c\x92\x1c\xdb\x6a\x90\xff\x7d\xf1\x1e\x39\x87\x6e\x39\x49\xbb\xdd\x85\xfc\x25\xd1\xf0\x7a\xe7\xef\x1d\xa4\x28\xe5\xaf\x68\x9d\x34\x7a\x04\xa2\x94\xf8\xe0\x51\xd3\x2f\x97\xdc\x7e\xef\x12\x69\x86\x77\xc7\x07\xb7\x52\x67\x23\x78\x59\x39\x6f\x8a\x0f\xe8\x4c\x65\x53\x7c\x85\x13\xa9\xa5\x97\x46\x1f\x14\xe8\x45\x26\xbc\x18\x1d\x00\x08\xad\x8d\x17\xf4\xd9\xd1\x4f\x80\xd4\x68\x6f\x8d\x52\x68\x07\x39\xea\xe4\xb6\x1a\xe3\xb8\x92\x2a\x43\xcb\x9b\xd7\x47\xdf\x3d\x4b\xbe\x4f\x9e\x1d\x00\xa4\x16\x79\xf9\xa5\x2c\xd0\x79\x51\x94\x23\xd0\x95\x52\x07\x00\x5a\x14\x38\x02\x53\xa2\x15\xde\xd8\xdc\x9a\xaa\x74\x49\xfd\xd3\x25\xa9\xb1\x68\xe8\x9f\xe2\xc0\x95\x98\xd2\xe9\x3c\xa7\x5d\x32\x37\x27\xec\x57\x13\x29\x3c\xe6\xc6\xca\xfa\x37\xc0\x00\x8c\x2a\xf8\xff\x81\xf9\xf7\x71\x8f\x9f\x68\x4b\xfe\xae\xa4\xf3\x3f\x2f\x8f\xbd\x91\xce\xf3\x78\xa9\x2a\x2b\xd4\x22\xc1\x3c\xe4\xa6\xc6\xfa\x77\xed\xf1\x7c\x5c\x1e\x86\xa4\xce\x2b\x25\xec\xc2\xba\x03\x00\x97\x9a\x12\x47\xc0\xcb\x4a\x91\x62\x76\x00\x10\xc5\x17\xb7\x19\x44\x11\xdd\x1d\xc7\x5d\x5d\x3a\xc5\x42\xd4\x67\x00\x6d\xa9\x4f\xcf\xcf\x7e\xfd\xf6\x62\x61\x00\x20\x43\x97\x5a\x59\x7a\x56\xc6\x1c\x43\x20\x1d\xf8\x29\x42\xa5\xa5\x07\x33\x81\xa2\x52\x5e\x7a\xd4\x42\xa7\x33\x98\x18\x0b\xef\xdf\xbc\x85\x42\x68\x91\x63\xd6\x11\x35\x9c\x79\xd2\xbd\xf3\x56\x48\x1d\x76\x90\xda\x79\xa1\x14\xab\x97\x76\x6a\x26\x83\xd4\x20\xbd\x0b\x1a\x21\xde\xc0\x1b\x10\x40\x6a\x94\x13\x89\x19\x38\xe4\xa3\xbd\xb0\x39\xfa\x76\x9a\x4b\x3a\x1c\xf8\x19\x89\xc7\x8c\x7f\xc7\xd4\x77\x3e\x5b\xfc\xa3\x92\x16\xb3\x2e\xb3\x24\xaa\xda\x68\x3b\x9f\x4b\x4b\x14\xf9\x8e\x15\x84\xbf\x8e\x8b\xcc\x7d\x5f\x90\xda\x13\x12\x6d\x98\x07\x19\x79\x07\x06\xb6\xa3\x92\x88\x0d\x16\x3b\x73\x32\x95\x0e\x2c\x96\x16\x1d\x6a\xdf\x48\x44\xe8\xc8\x40\x02\x17\x68\x69\x21\xd9\x4a\xa5\x32\x12\xe5\x1d\x5a\x0f\x16\x53\x93\x6b\xf9\x67\xb3\x9b\x23\x59\xd1\x31\x4a\x78\x74\x1e\xa4\xf6\x68\xb5\x50\x70\x27\x54\x85\x7d\x10\x3a\x83\x42\xcc\xc0\x22\xed\x0b\x95\xee\xec\xc0\x53\x5c\x02\x6f\x8d\x25\xed\x4c\xcc\x08\xa6\xde\x97\x6e\x34\x1c\xe6\xd2\xd7\x00\x90\x9a\xa2\x20\xe5\xcf\x86\xec\xcb\x72\x5c\x91\xce\x86\x19\xde\xa1\x1a\x3a\x99\x0f\x84\x4d\xa7\xd2\x63\xea\x2b\x8b\x43\x51\xca\x01\x13\xab\x19\x04\x92\x22\xfb\xc6\x46\xc8\x70\x4f\x16\xc4\x17\x54\xe6\xbc\x95\x3a\x9f\x1b\x62\x9f\xdb\x28\x6b\xf2\x3c\xb2\x4c\x11\x97\x07\x5e\x5a\x91\xd2\x27\x92\xca\x87\x1f\x2f\x2e\xa1\x26\x20\x88\x3d\x48\xb8\x9d\xea\x5a\x61\x93\xa0\xa4\x9e\xa0\x0d\x33\x27\xd6\x14\xbc\x0b\xea\xac\x34\x52\x7b\xfe\x91\x2a\x89\xda\x83\xab\xc6\x05\x19\x2d\x19\x18\x3a\x4f\x7a\x48\xe0\x25\xe3\x1f\x8c\x11\xaa\x32\x13\x1e\xb3\x04\xce\x34\xbc\x14\x05\xaa\x97\xc2\xe1\x5f\x2e\x6a\x92\xa8\x1b\x90\xf8\x76\x17\x76\x17\xbe\x97\x17\x2c\x39\x14\x40\x0d\xaf\x6b\xb5\x33\x87\x1f\x17\x25\xa6\x35\x86\xd0\x4a\xc6\x0c\xa1\x17\x40\xa6\x56\x51\xb2\x2b\x11\xeb\xdd\x95\x49\x44\x85\xa9\x37\x76\x79\x64\x81\xd4\x8b\x38\x31\xae\x08\x64\xce\x91\xf6\xc4\x6d\xc6\x9d\x1d\x28\xdd\x46\x2d\x6b\x41\xf8\x74\xfa\xe3\x03\xd9\x64\x07\xd2\xb7\x50\xbf\xb8\x28\x78\x04\x45\x26\x42\x13\x25\xc6\xa8\x1a\x51\xd4\x48\x58\x04\x93\xbf\x9c\xe2\xdc\x17\x10\x16\xe1\xf4\xdd\x2b\xcc\x56\x31\xd7\x32\x28\xac\x15\xb3\x35\x33\xa4\xc7\x62\x2d\xe1\x0b\xa4\x9f\x6e\x20\x2f\x3a\x76\x3d\xe2\xa7\x82\x63\x89\xe7\x48\x12\x40\xab\x0f\x02\x6e\x71\x16\xf0\x8d\x60\x33\xaa\x2c\x4c\xb6\xc8\x68\xc8\xca\xbc\xc5\x19\x4f\x8a\x60\xb7\x96\xba\x2d\xfa\x0b\x7f\xab\xa3\xc9\xfc\xdf\x80\x8e\xdc\x38\x5e\x13\xbb\x76\xd2\x36\x63\x09\x7f\xb7\x38\xdb\x34\xbc\x20\x70\x92\x43\x74\xc3\x20\x79\xfa\xc0\xd2\x62\xcf\xac\x85\x2d\xca\x52\x49\x64\x34\xdb\xb8\xf7\x5a\x38\x99\xff\xab\x59\x7d\x04\xa1\x8d\x2a\x5b\x84\x0e\xca\x7e\xe2\x82\x62\xc9\xd2\xa7\xb2\x8c\x49\x42\x48\x0d\xea\x50\xf6\xab\x50\xb2\x93\x86\xb0\x55\x9f\xe9\x3e\xbc\x33\x9e\xfe\xf9\xf1\x41\x12\x54\x93\x3d\xbc\x32\xe8\xde\x19\xcf\x5f\xbe\x0a\xab\x81\x84\x47\x30\x1a\x16\xb0\xb1\xeb\xe0\x57\xc4\x49\x37\x9e\x51\x1a\x35\x61\xfd\x34\x42\x91\x8e\x22\x8a\xb1\x35\x47\x9c\x61\x84\x8d\xc2\x16\x45\xe5\x38\x00\x69\xa3\x07\x58\x94\x7e\xb6\x72\x8f\x28\x08\x63\xe7\xe4\xb0\x61\xbb\xb8\xd5\x25\xc5\xc5\x30\x12\x32\x18\x45\xa9\x28\x64\x15\x13\xcd\xd1\x98\x72\x69\x99\x42\x81\x36\x47\x28\x09\xa1\x76\x11\xef\x26\x5c\x09\x7f\x5b\xd0\x65\x47\x5d\x31\x64\xbe\x21\x07\x78\x04\xc4\x86\xf9\x01\x96\x0a\x51\x92\x9a\x3e\x12\xfa\xb0\xa4\x3e\x41\x29\x24\x65\xbc\xa7\x9c\xbd\x2b\x9c\x1b\x93\x9a\x65\xda\xdd\x86\x76\x90\x0e\x08\x4a\xee\x84\x22\xbc\x23\x4b\xd6\x80\x2a\xa0\x1f\x25\xd9\x0b\xc0\xde\x87\xfb\xa9\x71\x01\xcc\x26\x12\x15\xe7\x3e\xbd\x5b\x9c\xf5\xfa\x4b\xaa\xed\x9d\xe9\x5e\xc0\xc5\x25\x65\x36\x20\x6a\xb4\x9a\x41\x8f\xc7\x7a\x9f\x1f\x0b\x36\x82\xa5\xc8\x32\x2e\x0f\x85\x3a\xdf\x01\xcd\x36\xea\xcd\xa1\xbd\x93\x29\x9e\xa6\xa9\xa9\x34\x17\x4e\x3b\xc4\xf5\xc5\x25\x35\xf8\x89\xac\x90\x7a\xae\xb6\xe0\x99\x20\xc2\x54\xb8\x9f\xca\x74\x0a\xf7\x52\x29\x4e\xe3\x1c\x66\xa4\x9e\x0c\x4b\x65\x66\x8d\x9c\x0f\xdd\x51\xd0\x2c\xe5\x93\xb5\xec\xb9\x52\x5b\x9f\x1a\xac\x63\x8e\xd2\xff\xf4\xdc\x9a\x3b\x99\x61\x76\x7a\x7e\xb6\x52\x4a\xf3\xcc\xf1\x12\xf0\xa8\x94\xe3\xf2\x8b\x72\x4e\x6f\x62\xce\xb9\x32\x85\x29\x3b\xfb\x77\x8a\xf4\xb5\xc4\x8e\x8d\x51\x28\x96\xc7\x43\x2a\xd4\x14\xa1\xdb\x69\xbd\x5c\x58\x10\xe1\x0e\x1f\x4a\x25\x53\xe9\x6b\xfc\x6e\x73\x2b\xae\x67\x78\x11\x03\x97\xe4\x6c\xc0\xa1\xef\xb7\xb9\x9a\x74\x20\x73\x6d\xec\x6a\xfb\xdc\x8c\x27\x1b\x50\x64\x0b\x76\x3c\x0c\x6e\xab\x31\x5a\x8d\x1e\xdd\x80\x72\xac\x41\x5c\x80\x0b\xe9\xb1\x17\xbe\x5a\x3a\x62\x43\x82\xcc\xf3\x9b\x14\x39\xfc\x5a\x95\x24\x7f\x78\x7c\x8e\xbc\x3e\x5f\x19\x80\x12\xce\xff\x12\xaa\x94\x47\x64\xd6\xa9\xd1\xc1\xaf\xb7\xab\xfe\x65\x33\x75\x31\xc6\xad\xb2\xd0\x76\xe3\xaf\xaa\xd4\x39\x8a\x7a\x0d\x49\x2d\x14\x66\xe8\x85\x54\x41\xe2\x46\x23\x08\x82\x06\x5f\x53\x99\x56\xd6\x72\xb5\xe7\xc9\xb3\xea\xca\xfd\xf4\xfc\x0c\x1a\x6d\xc0\x60\x30\x08\x71\xd1\x79\x5b\xa5\x6c\xaf\x54\x85\xeb\x0c\x33\xde\x35\x93\x96\x4b\x6f\x47\x9b\xb7\x72\x88\x99\x57\x80\xf3\x52\xf8\x29\x24\x41\xf9\x49\x47\x14\x00\xaf\x8d\x05\x7c\x10\x45\xa9\xb0\xcf\x62\x80\xd7\xc6\x44\x9b\x09\x07\x7e\x84\xe1\x10\x3e\xb4\xc9\x12\x07\x84\x31\xe1\x5a\xc8\x95\xb8\xb3\x00\x13\x63\x48\xca\x5d\x7e\x12\x5a\xf8\xb3\x36\xf7\x7a\xd5\xd1\x7c\x96\xb0\x38\x82\xeb\xde\xe9\x9d\x90\x4a\x8c\x15\x5e\xf7\xfa\x70\xdd\x3b\xb7\x26\xe7\xd0\xa4\xf3\xeb\x18\x6b\xae\x7b\xaf\x30\xb7\x22\xc3\xec\xba\x47\xdb\xfe\x2b\x47\xfe\xb7\x94\x04\xfc\x8c\xb3\xe7\xbc\x59\xf3\xf9\x22\x64\x09\xb3\xe7\x21\x49\xa0\xef\xe4\x50\x97\xb3\x12\x9f\x53\x74\xac\x3f\xbc\x15\x65\xb3\xb8\x63\x4d\x57\x37\x54\xb3\xde\x1d\x27\xad\x3a\x7f\xfb\xdd\x19\x3d\xba\xee\xb5\xf4\xf7\x4d\x41\x66\x51\xfa\xd9\x75\x0f\xe6\x4e\x1d\x5d\xf7\xf8\xdc\xfa\x7b\x4d\xe4\xe8\xba\x47\x27\xd1\x67\x6b\xbc\x19\x57\x93\xd1\x75\x6f\x3c\xf3\xe8\xfa\xc7\x7d\x8b\x65\x9f\x40\xea\x79\x7b\xc2\x75\xef\x37\xb8\xd6\x44\xac\xf1\x53\xb4\x41\x93\x0e\x3e\xf5\x36\xa0\xcb\x86\x90\xb9\xad\xb6\x08\x1e\x7b\x69\x85\x76\xb2\xee\x90\xae\x9d\x5a\xa0\x73\x22\x5f\x3f\x6e\x51\xb8\x95\xf0\x1f\x86\x83\x35\xac\x1d\x26\x5e\x56\x0e\x6e\x2f\x5c\x96\x79\xd8\xb1\x60\x5c\x5e\xd8\x96\x33\xce\x83\xa7\x0f\xec\xb1\x8d\x4d\xf8\x66\x36\x39\xa2\x35\x05\xfb\x77\x04\x58\x4e\xb9\x58\x6f\x31\xa9\x8d\x8d\xb6\x31\xc2\xfd\x14\x75\x6c\x79\x66\x68\xd5\x8c\x32\xdb\x76\xd7\x74\x2a\x74\x8e\x59\x02\x21\xad\x16\xec\xef\x14\x80\x6f\xc9\x91\x38\x1d\xd3\x50\xb9\xba\x01\xc5\x74\x35\x3b\x12\x70\x04\x87\x8f\xdb\x30\x32\xa6\x29\x96\x9e\xbc\x6b\x5b\x75\xba\xa5\x06\x99\x18\x5b\x08\x3f\x02\xc2\xf4\x81\x5f\x6f\x1e\xd1\x38\x76\x14\x7c\x9c\x1d\xb2\xdf\x69\x55\x08\x4d\xd6\x93\x11\xbd\xed\x98\xce\x64\x2a\xb8\xeb\x56\xe3\xa9\x18\x9b\x2a\x20\x5c\xab\x87\x28\xea\x42\xcc\x48\xce\x94\x06\x90\x7f\x46\xb6\xbe\x90\xf9\x42\x3c\xbc\x41\x9d\xfb\xe9\x08\xbe\x3d\xf9\xf7\xef\xbe\x5f\x33\x31\x00\x23\x66\x3f\xa1\xa6\xf8\xb3\xa2\xa9\xbb\x46\x0c\xcb\x0b\xbb\x05\x2a\xf1\x99\xd4\x9d\xb4\x24\x6f\xe7\x34\x15\x76\x6b\x41\xf7\x82\x13\x1a\x18\x0b\x4a\x2e\xab\x92\xe4\x42\x28\xcf\xfd\x71\x9d\x62\x1f\xe4\x64\xf5\x66\xb2\x01\x70\x35\x83\xe3\x93\x3e\x8c\xa3\x88\x97\xe1\xfb\xea\xe1\x26\x59\x41\xb2\x74\xf0\x43\x7f\x81\x1e\xca\x61\x2b\x8e\x78\x9c\x3e\xde\x4b\x3f\x05\x8b\x21\x0c\xc6\xe6\xf2\x8a\x30\x88\x0d\xbd\xdb\x14\x47\xc1\x30\xc7\xf5\xdd\x8e\xda\x6c\xa5\xf6\xdf\xfd\xdb\x7a\xfd\x4a\x2d\x8b\xaa\x18\xc1\xb3\x35\x53\x02\xa4\xed\xa8\xcd\x30\xb9\xcd\x02\x04\x41\x57\x6e\x45\x51\x70\x6a\x2d\x33\xd4\x9e\xea\x03\xdb\x35\x6d\xcf\x75\x12\x2f\x9c\x70\xab\xa9\x23\xc5\x27\x2e\xe2\x50\xc7\xd8\xcf\xad\xc9\xaa\x14\x2d\x47\xe0\x58\x71\xa4\x5d\x80\x9a\x95\x18\xbc\x21\xdc\x17\x50\x56\x8c\xa9\x6f\x3a\xf3\xa1\x79\x8f\x42\x4b\x9d\xbb\x78\xa4\x74\x01\x40\x42\xd4\xbd\x9f\x22\x87\x9e\xb9\x4a\x8f\xa9\x72\x32\x43\x8b\x19\x08\xc8\x2b\x61\x85\xf6\x88\x19\xc1\x4f\xa8\xf6\x42\xb7\xbc\x85\x3c\xd1\xf6\xa8\x6b\x6f\x0c\xae\x1a\xc0\x8a\x48\x8c\x7d\xed\xd0\x07\xf8\x6a\xae\x7a\xfc\xec\x64\xa3\xca\x9b\x79\xeb\x7b\x65\xc2\x7b\xb4\x7a\x04\xff\x75\x75\x3a\xf8\x4f\x31\xf8\xf3\xe6\x30\xfe\xe7\xd9\xe0\x87\xff\xee\x8f\x6e\x9e\x76\x7e\xde\x1c\xbd\xf8\x97\x35\x3b\xad\x4e\xdb\xd7\x98\x4f\x0c\x22\x75\x92\x58\x6b\xb4\xcf\x11\xc6\x4c\xe0\xd2\x56\xd8\x87\xd7\x42\x39\xec\xc3\x2f\x9a\x43\xc3\x17\x0a\x0d\x75\x55\x6c\x6e\x3b\xf6\xe8\xd4\xd5\xc9\x47\x33\x85\x49\xda\x3c\x27\x92\xbb\xa9\xf2\xdf\x4d\x48\x9c\xb6\x99\x49\x17\x69\x3a\x77\x21\xc0\x88\x47\x69\x69\x12\xd3\xdb\x24\x35\xc5\xb0\x73\x57\x42\x79\xf5\x5b\xa1\x67\xd0\xc2\x5a\x48\x4a\x17\x2d\xdd\x79\xc2\x26\x91\x5a\xe3\x5c\x73\x93\xe0\x40\xc9\x5b\x84\x26\x73\x0d\x60\x39\xc6\x54\x70\x22\x6e\xc7\xd2\x5b\x61\x67\x9d\xba\x03\x52\xa1\x63\xcd\x3f\xa9\x14\x1c\x3a\x44\x48\xb4\xc9\x70\x19\x5d\x8f\x02\x86\x8a\xb1\x54\xd2\xcf\x42\x83\x20\x35\x7a\xa2\x64\xcc\xff\x8b\xd2\x58\x2f\xb4\xaf\x9b\x2b\x39\x3e\x50\x29\xcb\x7d\x9d\x50\x04\x1f\x66\xda\x1d\x1f\x9f\x7c\x7b\x51\x8d\x33\x53\x08\xa9\x5f\x17\x7e\x78\xf4\xe2\xf0\x8f\x4a\x28\xee\x4c\x50\xcd\xfc\xba\xf0\x47\x5f\x2f\x2c\x1e\x7f\xb7\x83\x17\x1d\x5e\x05\x5f\xb9\x39\xbc\x1a\xc4\xff\x3d\xad\x3f\x1d\xbd\x38\xbc\x4e\x36\x8e\x1f\x3d\x25\x1e\x3a\x1e\x78\x73\x35\x68\xdd\x2f\xb9\x79\x7a\xf4\xa2\x33\x76\xb4\xec\x8c\x9d\xaa\x74\x6b\x81\xf9\xa6\x9d\x1b\xb2\x13\x5f\x3f\x1a\xa8\x3d\x73\x3e\x35\x5c\x2c\x39\xa3\x17\x53\x3c\x8e\xdb\x3c\xba\x7b\xb3\x4b\xd2\xa5\x77\xef\x96\xcc\xf7\x49\x42\x63\x7e\xf5\xd5\x77\x13\x81\xe6\x98\xfa\x27\xf6\x43\x60\xa9\x83\xf7\x01\x27\x8f\x6c\xe0\x7d\xc0\x09\x58\x9c\xa0\x45\x9d\x62\x2d\x98\xf9\xbe\x5d\xbc\xd6\x6d\x1a\x7b\x7f\xc1\x1d\xdd\xfa\x87\x00\x2b\x59\xa0\x64\x3f\x5e\xfe\xd7\xf6\x18\x79\x58\x7b\xe1\xb0\xd5\xa5\x39\x1e\x9f\x0b\x3f\xdd\x89\x82\x27\x67\x51\x6c\xdc\x9d\xe7\xfb\x92\x52\x62\x8a\x73\x6f\x0d\x38\x8f\x43\x91\xc5\x8f\x94\xf8\x58\x8c\x63\xfd\x90\x71\xc4\x3b\x89\xf6\x2d\x02\x25\x4d\x20\x08\x88\x65\x06\xff\x71\xf1\xfe\xdd\xf0\x27\x13\x73\x05\xaa\x66\x5c\xf0\x2d\xee\x26\xf7\xc1\x55\xe9\x14\x84\x23\xd2\xa8\xbe\xbd\xe0\xd6\x43\x21\xb4\x9c\xa0\xf3\x49\xdc\x0d\xad\xbb\x3a\xb9\x49\xe6\xdb\x1d\x32\x5e\x5c\xd4\x37\xf6\xd1\x00\xd8\x37\x88\x99\x66\x2d\x27\xad\x4c\x52\x69\xb2\x48\xf4\x3d\x13\xeb\xc5\x2d\x82\x89\xc4\x56\xc8\x41\x61\x04\x3d\x32\x93\xce\xd1\x1f\xc9\xb1\x3e\xf5\xe0\xf0\x7e\x8a\x16\xa1\x47\x3f\x7b\xe1\xc0\xe6\x81\x05\x7d\xeb\x44\xfc\x78\x70\xc8\xef\xad\xcc\x73\x4e\xb7\xf8\xb5\xc0\x1d\x6a\x7f\xc4\xf1\x6d\x02\xda\x74\x26\xeb\xd8\x87\x6e\xbb\xcf\x8b\x84\x5c\x9d\xdc\xf4\xe0\x70\x9e\x2f\x4a\x41\xf1\x01\x4e\x9a\x8e\x73\x69\xb2\xa3\xba\x6a\x9d\x69\x2f\x1e\xb8\x30\x98\x1a\x87\x3a\x74\xf6\xbd\x81\xa9\xb8\x43\x70\x86\x8a\x4f\x54\x6a\x10\x12\xcc\x0c\xee\x43\x03\xae\x16\x65\xb8\xb4\x29\x85\xf5\x0b\xcf\x4f\x2e\xdf\xbf\x7a\x3f\x0a\xa7\x91\xda\x72\x5d\x57\xb9\x13\xa9\x85\x8a\xb7\x0b\x4d\x7e\x48\x84\x54\x41\x49\xde\xc4\xd2\xb6\xbe\xf9\x98\x54\xbe\xb2\x98\x2c\x3e\x47\xd8\xd9\xe2\x57\xbd\x05\x59\x6d\xec\xfc\x26\x64\xd1\xd1\xfe\x17\x5f\x5c\xec\xcc\xa2\x5e\x73\xa3\xb1\xcc\xe2\xbb\x8e\x0d\x6e\x64\xb1\x85\x66\xe2\x32\x33\xa9\x23\x06\x53\x2c\xbd\x1b\x9a\x3b\x82\x4e\xbc\x1f\xde\x1b\x7b\x2b\x75\x3e\x20\x23\x1b\x04\xcd\xbb\x21\x87\x98\xe1\x37\xfc\xcf\x17\x71\xc4\x71\x6a\x77\xb6\xc2\xc3\xaf\xbf\x81\x37\x0e\x9f\xc3\xcf\x66\xad\xce\x2f\x1f\x13\x09\x9e\x5c\xd4\xc5\xdf\xc2\x6a\x72\x97\x70\xe1\x14\x5f\x84\x75\x10\xae\x10\x59\x80\x40\xa1\x67\x7f\xb9\x19\x93\x00\xb9\xc6\x4f\x67\x83\xf8\x64\x73\x20\x74\x36\x68\xf2\xeb\x74\xf6\xd9\x12\xab\xe4\x8e\x0e\xfc\xcb\xd9\xab\xbf\xc7\xb8\x2b\xf9\x28\x6f\x0d\x5d\x94\x11\x78\x5b\xd5\xd9\x9d\xf3\xc6\x8a\x1c\xe7\xbf\x55\xe3\xa6\xf8\x68\x19\x8e\x75\x25\x7c\xfc\xc4\x9f\xda\x47\x9a\x42\x95\x53\x71\x52\xaf\xdd\x3f\xd5\xdc\x3f\xd5\xdc\x3f\xd5\xdc\x3f\xd5\xdc\x28\xec\xfd\x53\xcd\xfd\x53\xcd\xfd\x53\xcd\xfd\x53\xcd\xfd\x53\xcd\x2f\x63\x75\xff\x54\x73\xff\x54\x73\xff\x54\xb3\xf9\xdb\x3f\xd5\xdc\x91\xb9\xfd\x53\xcd\x7f\xfa\x53\xcd\xff\xdf\x8f\x2f\xf7\x97\x63\xff\x37\x2e\xc7\xf6\xd7\x5d\xfb\xeb\xae\xfd\x75\xd7\xfe\xba\xeb\x33\x2c\x7e\x7f\xdd\xb5\xbf\xee\xda\x5f\x77\xed\xaf\xbb\xfe\xa1\xd7\x5d\x13\xa1\xdc\xce\xf7\x5d\xff\x13\x00\x00\xff\xff\xc7\xa0\x23\x13\x5c\x46\x00\x00") +var _operatorsCoreosCom_operatorgroupsYaml = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xec\x5a\x79\x6f\x23\x37\x96\xff\xbf\x3f\xc5\x83\x66\x81\xb6\xb3\x3a\xda\x9d\x45\x36\x23\x20\x08\x8c\xee\x74\xe0\x4d\xb7\xdb\x68\xbb\xb3\xc0\x5a\xde\x1d\xaa\xea\x55\x89\x63\x16\x59\x43\xb2\x24\x6b\x82\x7c\xf7\xc5\x7b\x64\x1d\xba\xe5\x1c\xb3\x93\x85\xea\x1f\x5b\x3c\xdf\xc1\xf7\x7b\x07\x29\x4a\xf9\x23\x5a\x27\x8d\x1e\x83\x28\x25\x3e\x79\xd4\xf4\xcb\x0d\x1f\xbf\x76\x43\x69\x46\xf3\x8b\x17\x8f\x52\xa7\x63\x78\x53\x39\x6f\x8a\x4f\xe8\x4c\x65\x13\x7c\x8b\x99\xd4\xd2\x4b\xa3\x5f\x14\xe8\x45\x2a\xbc\x18\xbf\x00\x10\x5a\x1b\x2f\xa8\xd9\xd1\x4f\x80\xc4\x68\x6f\x8d\x52\x68\x07\x39\xea\xe1\x63\x35\xc5\x69\x25\x55\x8a\x96\x17\xaf\xb7\x9e\xbf\x1a\x7e\x3d\x7c\xf5\x02\x20\xb1\xc8\xd3\xef\x64\x81\xce\x8b\xa2\x1c\x83\xae\x94\x7a\x01\xa0\x45\x81\x63\x30\x25\x5a\xe1\x8d\xcd\xad\xa9\x4a\x37\xac\x7f\xba\x61\x62\x2c\x1a\xfa\x53\xbc\x70\x25\x26\xb4\x3b\x8f\x69\xa7\xac\x8c\x09\xeb\xd5\x44\x0a\x8f\xb9\xb1\xb2\xfe\x0d\x30\x00\xa3\x0a\xfe\x3f\x30\xff\x31\xae\xf1\x3d\x2d\xc9\xed\x4a\x3a\xff\xc3\x66\xdf\x7b\xe9\x3c\xf7\x97\xaa\xb2\x42\xad\x13\xcc\x5d\x6e\x66\xac\xbf\x6e\xb7\xe7\xed\xf2\xd0\x25\x75\x5e\x29\x61\xd7\xe6\xbd\x00\x70\x89\x29\x71\x0c\x3c\xad\x14\x09\xa6\x2f\x00\xa2\xf8\xe2\x32\x83\x28\xa2\xf9\x45\x5c\xd5\x25\x33\x2c\x44\xbd\x07\xd0\x92\xfa\xf2\xe6\xea\xc7\x2f\x6f\xd7\x3a\x00\x52\x74\x89\x95\xa5\x67\x65\xac\x30\x04\xd2\x81\x9f\x21\x54\x5a\x7a\x30\x19\x14\x95\xf2\xd2\xa3\x16\x3a\x59\x42\x66\x2c\x7c\x7c\xff\x01\x0a\xa1\x45\x8e\x69\x47\xd4\x70\xe5\x49\xf7\xce\x5b\x21\x75\x58\x41\x6a\xe7\x85\x52\xac\x5e\x5a\xa9\x19\x0c\x52\x83\xf4\x2e\x68\x84\x78\x03\x6f\x40\x00\xa9\x51\x66\x12\x53\x70\xc8\x5b\x7b\x61\x73\xf4\xed\x30\x37\xec\x70\xe0\x97\x24\x1e\x33\xfd\x2b\x26\xbe\xd3\x6c\xf1\x6f\x95\xb4\x98\x76\x99\x25\x51\xd5\x87\xb6\xd3\x5c\x5a\xa2\xc8\x77\x4e\x41\xf8\x3a\x26\xb2\xd2\xbe\x26\xb5\x97\x24\xda\x30\x0e\x52\xb2\x0e\x0c\x6c\x47\x25\x11\x1b\x2c\x76\xe6\x64\x26\x1d\x58\x2c\x2d\x3a\xd4\xbe\x91\x88\xd0\x91\x81\x21\xdc\xa2\xa5\x89\x74\x56\x2a\x95\x92\x28\xe7\x68\x3d\x58\x4c\x4c\xae\xe5\xdf\x9b\xd5\x1c\xc9\x8a\xb6\x51\xc2\xa3\xf3\x20\xb5\x47\xab\x85\x82\xb9\x50\x15\xf6\x41\xe8\x14\x0a\xb1\x04\x8b\xb4\x2e\x54\xba\xb3\x02\x0f\x71\x43\xf8\x60\x2c\x69\x27\x33\x63\x98\x79\x5f\xba\xf1\x68\x94\x4b\x5f\x03\x40\x62\x8a\x82\x94\xbf\x1c\xb1\x2d\xcb\x69\x45\x3a\x1b\xa5\x38\x47\x35\x72\x32\x1f\x08\x9b\xcc\xa4\xc7\xc4\x57\x16\x47\xa2\x94\x03\x26\x56\x33\x08\x0c\x8b\xf4\x4f\x36\x42\x86\x7b\xb9\x26\xbe\xa0\x32\xe7\xad\xd4\xf9\x4a\x17\xdb\xdc\x5e\x59\x93\xe5\xd1\xc9\x14\x71\x7a\xe0\xa5\x15\x29\x35\x91\x54\x3e\x7d\x77\x7b\x07\x35\x01\x41\xec\x41\xc2\xed\x50\xd7\x0a\x9b\x04\x25\x75\x86\x36\x8c\xcc\xac\x29\x78\x15\xd4\x69\x69\xa4\xf6\xfc\x23\x51\x12\xb5\x07\x57\x4d\x0b\x3a\xb4\x74\xc0\xd0\x79\xd2\xc3\x10\xde\x30\xfe\xc1\x14\xa1\x2a\x53\xe1\x31\x1d\xc2\x95\x86\x37\xa2\x40\xf5\x46\x38\xfc\xdd\x45\x4d\x12\x75\x03\x12\xdf\xf1\xc2\xee\xc2\xf7\xe6\x84\x0d\x83\x02\xa8\xe1\x75\xa7\x76\x56\xf0\xe3\xb6\xc4\xa4\xc6\x10\x9a\xc9\x98\x21\xf4\x1a\xc8\xd4\x2a\x1a\x1e\x4b\x04\x6d\x99\x89\x4a\xf9\x75\x4a\x00\xaa\x32\xb7\x22\xc5\x5b\x6f\x09\xd6\x97\x63\x78\x1b\x46\xae\x0d\xdc\x65\xee\xcc\x22\x2a\x4c\xbc\xb1\x9b\x3d\x6b\xac\xde\xc6\x81\x71\x46\x60\x73\x85\xb5\x97\x6e\x3f\x6e\x1d\xc1\xe9\x21\x6a\xe9\x2b\x84\x4f\x66\xdf\x3d\xd1\x99\xee\xb8\x84\x03\xd4\xaf\x4f\x0a\x16\x45\x9e\x8d\xd0\x48\x89\x29\xaa\x46\x14\x35\x92\x16\xc1\x64\xee\x66\xb8\xd2\x02\xc2\x22\x5c\x5e\xbf\xc5\x74\x1b\x73\x2d\x83\xc2\x5a\xb1\xdc\x31\x42\x7a\x2c\x76\x12\xbe\x46\xfa\xe5\x1e\xf2\x22\x30\xd4\x3d\x7e\x26\xd8\x17\x79\xf6\x44\x01\xf4\xfa\x20\xe0\x11\x97\x01\x1f\x09\x76\xa3\xca\xc2\x60\x8b\x8c\xa6\xac\xcc\x47\x5c\xf2\xa0\x08\x96\x3b\xa9\x3b\xa0\xbf\xf0\x6d\xf7\x46\xab\xdf\x80\xb6\xdc\xdb\x5f\x13\xbb\x73\xd0\xa1\xc3\x12\xbe\x47\x5c\xee\xeb\x5e\x13\x38\xc9\x21\x9a\x71\x90\x3c\x35\xb0\xb4\xd8\xb2\x6b\x61\x8b\xb2\x54\x12\x19\x0d\xf7\xae\xbd\x13\x8e\x56\xbf\x9a\xd5\x67\x10\xda\xa8\xb2\x45\xf8\xa0\xec\x97\x2e\x28\x96\x4e\xfa\x4c\x96\x31\xc8\x08\xa1\x45\xed\x0a\x7f\x14\x4a\x76\xc2\x18\x3e\xd5\x57\xba\x0f\xd7\xc6\xd3\x9f\xef\x9e\x24\x41\x3d\x9d\x87\xb7\x06\xdd\xb5\xf1\xdc\xf2\x9b\xb0\x1a\x48\x78\x06\xa3\x61\x02\x1f\x76\x1d\xec\x8a\x38\xe9\xfa\x43\x0a\xc3\x32\xd6\x4f\x23\x14\xe9\xc8\x23\x19\x5b\x73\xc4\x11\x4a\x58\x28\x2c\x51\x54\x8e\x1d\x98\x36\x7a\x80\x45\xe9\x97\x5b\xd7\x88\x82\x30\x76\x45\x0e\x7b\x96\x8b\x4b\xdd\x91\x5f\x0d\x3d\x21\x02\x52\x14\xca\x42\x5a\x31\xd1\xec\xcd\x09\xb4\x65\x02\x05\xda\x1c\xa1\x24\x84\x3a\x46\xbc\xfb\x70\x25\x7c\x07\xd0\xe5\x48\x5d\x31\x64\xbe\x27\x03\x78\x06\xc4\x86\xf1\x01\x96\x0a\x51\x92\x9a\x7e\x22\xf4\x61\x49\xfd\x0c\xa5\x90\x14\x31\x5f\x72\xf4\xaf\x70\xa5\x4f\x6a\x96\x69\x77\x19\x5a\x41\x3a\x20\x28\x99\x0b\x45\x78\x47\x27\x59\x03\xaa\x80\x7e\x14\xa4\xaf\x01\x7b\x1f\x16\x33\xe3\x02\x98\x65\x12\x15\xc7\x4e\xbd\x47\x5c\xf6\xfa\x1b\xaa\xed\x5d\xe9\x5e\xc0\xc5\x0d\x65\x36\x20\x6a\xb4\x5a\x42\x8f\xfb\x7a\xbf\xdc\x17\xec\x05\x4b\x91\xa6\x9c\x5e\x0a\x75\x73\x04\x9a\xed\xd5\x9b\x43\x3b\x97\x09\x5e\x26\x89\xa9\x34\x27\x5e\x47\xf8\xf5\xf5\x29\x35\xf8\x89\xb4\x90\x7a\x25\x37\xe1\x91\x20\xc2\x50\x58\xcc\x64\x32\x83\x85\x54\x8a\xc3\x40\x87\x29\xa9\x27\xc5\x52\x99\x65\x23\xe7\x33\x77\x1e\x34\x4b\xf1\x68\x2d\x7b\xce\xf4\x76\x87\x06\xbb\x98\xa3\xf4\x21\xb9\xb1\x66\x2e\x53\x4c\x2f\x6f\xae\xb6\x4a\x69\x95\x39\x9e\x02\x1e\x95\x72\x9c\xbe\x51\xcc\xea\x4d\x8c\x59\xb7\x86\x30\x65\x67\xfd\x4e\x92\xbf\x93\xd8\xa9\x31\x0a\xc5\x66\x7f\x08\x85\x9a\x24\xf6\x30\xad\x77\x6b\x13\x22\xdc\xe1\x53\xa9\x64\x22\x7d\x8d\xdf\x6d\x6c\xc5\xf9\x10\x4f\x62\xe0\x92\x1c\x0d\x38\xf4\xfd\x36\x56\x93\x0e\x64\xae\x8d\xdd\x7e\x3e\xf7\xe3\xc9\x1e\x14\x39\x80\x1d\x4f\x83\xc7\x6a\x8a\x56\xa3\x47\x37\xa0\x18\x6b\x10\x27\xe0\xa6\x09\xac\x87\xb0\x87\xa4\xd4\xfb\xbc\x3a\x61\x25\x01\x8d\x8b\xd5\xd8\x1a\xb2\xf6\x95\xe4\x9b\x46\x35\x12\x64\x7b\xb6\xc8\x46\x9c\x54\xd6\xa2\xf6\x6a\x09\x7e\x61\xc0\x55\x65\x69\xac\xc7\x74\x7d\x49\x32\x4d\x98\xe8\x3a\xd0\x1e\xf3\xa1\x62\x13\x60\xa0\x10\x4a\x99\x05\x24\xaa\x72\x1e\x6d\xb4\xac\x98\x29\xb3\xba\x0a\x33\xc7\x3a\x8d\x0d\x2e\x81\x9c\x41\x39\x13\x0e\xdb\x1c\xcc\x55\x49\x82\x98\x62\x1a\x3a\xa2\x2b\xc1\x2c\xc3\xc4\xcb\x39\xaa\x25\x14\x28\xb8\xd2\x20\x7c\xbb\x3f\x9d\xec\xb0\x7d\xcb\xf0\xda\x8e\x1a\x9f\x7c\x9d\xa4\x83\xe4\x24\x7c\xb5\x52\x61\x1b\x76\x67\xc2\x41\x26\xa4\xa2\xbc\x6e\xa2\xe1\x0e\x93\xd9\x8d\xc5\xb9\xc4\xc5\x67\xed\x44\x86\xef\x84\x54\xef\x8c\x5d\x08\x9b\x76\x64\xf0\x7b\xb0\x4f\x54\x35\x7d\x81\xa4\x5a\x2e\x97\x0d\x70\xaa\x65\xbf\xa5\x22\x47\x4d\x02\x20\x7e\x17\x35\x83\x37\x8a\x24\xb6\x98\xa1\x26\xd7\x5b\x4d\x9b\x13\x05\x16\x33\xb4\xa8\xc9\x9e\x44\xbd\x7e\x67\x52\xe3\x1e\x12\xe1\x85\x32\x39\x4b\x66\x8a\xa8\xeb\xbc\x17\x16\xd2\xcf\x40\xf0\x66\xb5\xf4\x32\x0e\xaf\x11\x90\x42\x05\x62\x31\xda\x6e\xa7\x68\x34\xd1\xf0\x9f\x97\x9f\xae\xaf\xae\xbf\x1f\xb3\x57\xd9\x27\xe1\xcd\x73\x2d\x1d\x54\x3c\xaa\x53\xf5\x70\x95\xf2\x74\xc4\x2b\x8d\x4f\x25\x26\x44\xda\x14\x67\x62\x2e\xc9\x06\x6c\xac\x87\xcc\xd1\x8a\xa9\x42\xa0\x34\x18\x94\x71\xb4\x8e\x42\xe7\x60\x69\x2a\x98\x89\x39\x42\x8a\x58\x42\xa5\x53\xb4\xce\x0b\x9d\x12\xf5\x26\x8b\x91\xef\x2a\x13\x30\x45\xea\xad\x2b\x62\x1b\xd6\xd5\x7b\x2e\xc0\xb7\x99\xee\x8e\x44\x96\x3e\xd4\x55\xb1\x1d\x95\x06\x7b\x66\x51\xef\x3e\x11\xaf\xa6\xfd\x5e\xf8\x6a\x03\xfa\xf6\x24\xfe\x3c\xbe\x49\xfd\xc3\xaf\x6d\xc9\xff\xa7\xe7\xe7\xfe\xbb\xf3\xa8\x01\x28\xe1\xfc\xe7\x70\x0a\x9f\x91\xf1\x27\x46\x07\xb3\x39\xec\x92\xde\x34\x43\xd7\x63\xef\x6d\x9e\xb3\x5d\xf8\x37\x75\x36\xab\xf0\xdf\x90\xd4\x86\x68\x29\x7a\x21\x55\x90\xb8\xd1\x08\x82\x42\x16\x5f\x53\x19\x81\x9d\xd5\x82\x4d\x45\xf2\xf2\xe6\x0a\x1a\x6d\xc0\x60\x30\x08\x20\xeb\xbc\xad\x12\xf6\xa3\x52\x7b\xd4\x04\x42\xb4\x6a\x2a\x2d\x97\x14\x1d\x2d\xde\xca\x21\x66\x84\x21\xcc\x2c\x85\x9f\xc1\x30\x28\x7f\xd8\x11\x05\xc0\x3b\x63\x01\x9f\x44\x51\x2a\xec\xb3\x18\xe0\x9d\x31\xf1\xcc\x84\x0d\x7f\x82\xd1\x08\x3e\xb5\x49\x1c\x07\xaa\x53\x8a\xb7\x42\x0e\xc7\x15\x53\xc8\x8c\x21\x29\x77\xf9\x19\xd2\xc4\x1f\xb4\x59\xe8\x6d\x5b\xf3\x5e\xc2\xe2\x18\x26\xbd\xcb\xb9\x90\x8a\x4c\x7f\xd2\xeb\xc3\xa4\x77\x63\x4d\xce\x21\xb3\xce\x27\x31\x06\x9e\xf4\xde\x22\xc3\x4c\x3a\xe9\xd1\xb2\xff\xca\x19\xc9\x07\x4a\x4e\x7e\xc0\xe5\x37\xbc\x58\xd3\x5c\xbb\xdf\x6f\x42\xf2\x42\xed\xe4\xe8\xef\x96\x25\x7e\x43\x51\x7b\xdd\xf0\x41\x94\xcd\xe4\xce\x69\xba\x7f\x28\xd0\x8b\xf9\xc5\xb0\x55\xe7\x5f\xfe\xea\x8c\x1e\x4f\x7a\x2d\xfd\x7d\x53\xd0\xb1\x28\xfd\x72\xd2\x83\x95\x5d\xc7\x93\x1e\xef\x5b\xb7\xd7\x44\x8e\x27\x3d\xda\x89\x9a\xad\xf1\x66\x5a\x65\xe3\x49\x6f\xba\xf4\xe8\xfa\x17\x7d\x8b\x65\x9f\xc0\xe9\x9b\x76\x87\x49\xef\x2f\x04\xc4\xa3\x11\x18\x3f\x43\x1b\x34\xe9\xe0\xe7\x6d\xc8\x75\x44\x28\x7f\xa8\xe6\x11\x2c\xf6\xce\x0a\xed\x64\x7d\xf3\xb3\x73\x68\x81\xce\x89\x7c\x77\xbf\x45\xe1\xb6\x86\xa5\xa1\x3b\x9c\x86\x9d\xdd\xc4\xcb\xd6\xce\xc3\x05\x95\x4d\x1e\x8e\x2c\x64\x6d\x4e\x6c\xcb\x2c\xce\x83\xa7\x06\xb6\xd8\xe6\x4c\xf8\x66\x34\x19\x22\x05\x01\x64\xdf\x11\x60\x39\x15\x64\xbd\xc5\x08\x29\x5e\x20\x4c\x31\xf8\xf9\x70\x95\x93\xa2\x55\x4b\x72\x53\xed\xaa\xc9\x4c\xe8\x9c\x02\x9b\x90\xee\x0b\xb6\x77\x0a\x9f\x1e\xc9\x90\x38\x4d\xd4\x50\xb9\xba\xb0\xce\x74\x35\x2b\x12\x70\x04\x83\x8f\xcb\x30\x32\x26\x09\x96\x9e\xac\xeb\x50\xd5\xec\x40\x6d\x24\x33\xb6\x10\x7e\x4c\xee\x19\x07\x7e\xf7\xf1\x88\x87\xe3\x48\xc1\xc7\xd1\x21\x2b\x9f\x55\x85\xa0\xa8\x47\xa4\x1c\x08\x34\x7d\x3a\x95\x89\xe0\x60\xa5\xc6\x53\x31\x35\x55\x40\xb8\x56\x0f\x51\xd4\x14\x71\x4c\x91\xd3\x13\xb2\xcf\xc8\xd6\xaf\x64\xbe\x10\x4f\xef\x51\xe7\x7e\x36\x86\x2f\x5f\xff\xfb\x57\x5f\xef\x18\x18\x80\x11\xd3\xef\x43\x98\xb7\xe5\xb2\x6a\x87\x18\x36\x27\x76\x0b\x67\xc4\xe7\xb0\xbe\x21\x18\xe6\xed\x98\xa6\xf2\xd7\x9e\xa0\x85\xe0\x44\x0b\xa6\xc2\x71\x8a\x40\x72\x21\x94\xe7\xb8\x51\x27\xd8\xa7\xe8\x7a\xeb\x62\xd2\x75\x32\x8d\x8b\xd7\x7d\x98\x46\x11\x6f\xc2\xf7\xfd\xd3\xc3\x70\x0b\xc9\xd2\xc1\x9f\xfb\x6b\xf4\x50\x6e\x5d\xb1\xc7\xe3\xb4\x96\x23\x52\x8b\xc1\x0d\xc6\x70\x7b\x8b\x1b\xc4\x86\xde\x43\x8a\x23\x67\x98\xe3\xee\x2a\x6c\x7d\x6c\xa5\xf6\x5f\xfd\xdb\x6e\xfd\x4a\x2d\x8b\xaa\x18\xc3\xab\x1d\x43\x02\xa4\x1d\xa9\xcd\x30\xb8\x8d\x02\x04\x41\x57\x6e\x45\x51\x70\xca\x2f\x53\xd4\x5e\x66\x12\x6d\xf7\x68\x87\xc4\x83\x27\xd6\x31\x7a\x23\xc5\x97\x2e\xe2\x50\xe7\xb0\xdf\x58\x93\x56\x09\x5a\xf6\xc0\xb1\x12\x92\x74\x01\x6a\x59\x62\xb0\x86\x90\x86\x42\x13\x7a\xd7\xd5\x24\x0a\xcf\x51\x68\xa9\x73\x17\xb7\x94\x2e\x00\x48\xf0\xba\x8b\x19\xb2\xeb\x59\xa9\x40\x31\x55\x4e\xa6\x68\x31\x05\x01\x79\x25\xac\xd0\x1e\x31\x25\xf8\x09\x55\xa8\x70\x0b\xd8\x42\x9e\x68\xef\xde\x6a\x6b\x0c\xa6\x1a\xc0\x8a\x48\x8c\xf7\x75\xa1\x3e\xf9\x9b\x99\xea\xc5\xab\xd7\x7b\x55\xde\x8c\xdb\x5d\xc3\x17\xde\xa3\xd5\x63\xf8\xef\xfb\xcb\xc1\x7f\x89\xc1\xdf\x1f\xce\xe2\x3f\xaf\x06\x7f\xfe\x9f\xfe\xf8\xe1\x8b\xce\xcf\x87\xf3\x6f\xff\x65\xc7\x4a\xdb\xc3\xf6\x1d\xc7\x27\x3a\x91\x3a\x48\xac\x35\xda\x67\x0f\x63\x32\xb8\xb3\x15\xf6\xe1\x9d\x50\x0e\xfb\xf0\x59\xb3\x6b\xf8\x95\x42\xdb\x9d\xb9\x84\x6f\x00\x3d\xda\x75\x7b\xf0\xd1\x0c\x61\x92\xf6\x8f\x89\xe4\xee\xab\x48\x1e\x27\x24\x0e\xdb\x4c\xd6\x45\x9a\xce\x1d\x2f\x30\xe2\x51\x58\x3a\x8c\xe1\xed\x30\x31\xc5\xa8\x73\x07\x4c\x71\xf5\x07\xa1\x97\xd0\xc2\x5a\x08\x4a\xd7\x4f\xba\xf3\x84\x4d\x22\xb1\x94\x91\x36\xb7\xe8\xa0\xe4\x23\x42\x13\xb9\x06\xb0\x9c\x62\x22\x38\x10\xb7\x53\xe9\xad\xb0\xcb\x4e\xde\x01\x89\xd0\xb1\x16\x99\x55\x0a\xce\x1c\x22\x0c\xb5\x49\x71\x13\x5d\xcf\x03\x86\x8a\xa9\x54\xd2\x2f\x43\xe1\x32\x31\x3a\x53\x32\xc6\xff\x45\x69\xac\x17\xda\xd7\x45\xdf\x1c\x9f\x40\xfa\x50\x6f\x0e\xc5\xb9\xb3\x54\xbb\x8b\x8b\xd7\x5f\xde\x56\xd3\xd4\x14\x42\xea\x77\x85\x1f\x9d\x7f\x7b\xf6\xb7\x4a\x28\xae\x98\x5e\x8b\x02\xdf\x15\xfe\xfc\xb7\x73\x8b\x17\x5f\x1d\x61\x45\x67\xf7\xc1\x56\x1e\xce\xee\x07\xf1\xbf\x2f\xea\xa6\xf3\x6f\xcf\x26\xc3\xbd\xfd\xe7\x5f\x10\x0f\x1d\x0b\x7c\xb8\x1f\xb4\xe6\x37\x7c\xf8\xe2\xfc\xdb\x4e\xdf\xf9\xa6\x31\x76\xb2\xd2\x83\x09\xe6\xfb\x76\x6c\x88\x4e\x7c\xfd\x18\xaa\xb6\xcc\xd5\xd0\x70\x3d\xe5\x8c\x56\x4c\xfe\x38\x2e\xf3\xec\xaa\xf2\x31\x41\x97\x3e\xbe\x8a\xbb\x5a\xbf\xed\x94\x4d\x36\xae\xc6\x1b\x0f\xb4\xc2\xd4\x3f\x6b\x9d\x76\xf5\x66\xe1\x13\x66\xcf\xbc\x58\xf8\x84\x59\xb7\xd4\x16\x04\xb3\x7a\x9f\x10\x9f\xab\x34\x17\x0e\xbf\xc3\xdb\x81\xdd\x0f\x9c\xb6\xb2\x40\xc1\x7e\x5d\x2f\x8d\xe7\x31\xf2\xb0\xf3\x22\xf4\xa0\x49\xb3\x3f\xbe\x11\x7e\x76\x14\x05\x2f\xaf\xa2\xd8\xf8\xd6\x90\xef\x71\x4b\x89\x09\xae\xbc\xa1\xe2\x38\x0e\x45\x1a\x1b\x29\xf0\xb1\x18\xfb\xfa\x21\xe2\x88\x77\xa5\xed\x1b\x2b\x0a\x9a\x40\x10\x10\xcb\x14\xfe\xe3\xf6\xe3\xf5\xe8\x7b\x13\x63\x05\xca\x66\x5c\xb0\x2d\xbe\xe5\xea\x83\xab\x92\x19\x08\x47\xa4\x51\x7e\x7b\xcb\xa5\x87\x42\x68\x99\xa1\xf3\xc3\xb8\x1a\x5a\x77\xff\xfa\x61\xb8\x5a\xee\x90\xf1\x42\xb5\x7e\x89\x14\x0f\x00\xdb\x06\x31\xd3\xcc\xe5\xa0\x95\x49\x2a\x4d\x1a\x89\x5e\x30\xb1\x5e\x3c\x22\x98\x48\x6c\x85\xec\x14\xc6\xd0\xa3\x63\xd2\xd9\xfa\x27\x32\xac\x9f\x7b\x70\xb6\xe0\x92\x7e\x8f\x7e\xf6\xc2\x86\xcd\xc3\x31\x6a\xeb\x78\xfc\xb8\x71\x88\xef\xad\xcc\x73\x0e\xb7\xb8\x6e\x3b\x47\xed\xcf\xd9\xbf\x65\xa0\x4d\x67\xb0\x8e\xf7\x63\xed\xad\xd8\x3a\x21\xf7\xaf\x1f\x7a\x70\xb6\xca\x17\x85\xa0\xf8\x04\xaf\x9b\x9b\xb0\xd2\xa4\xe7\x75\xd6\xba\xd4\x5e\x3c\x71\x62\x30\x33\x0e\x75\xb8\x48\xf0\x26\x54\x63\x9d\xa1\xe4\x13\x95\x1a\x84\x00\x33\x85\x45\x28\xc0\xd5\xa2\x0c\x97\xc9\xa5\xb0\x7e\xed\x59\xdd\xdd\xc7\xb7\x1f\xc7\x61\x37\x52\x5b\xae\xeb\x2c\x37\x93\x5a\xa8\x58\xd6\x6e\xe2\x43\x22\xa4\x0a\x4a\xf2\x26\xa6\xb6\x75\x45\x37\xab\x7c\x65\x71\xb8\xfe\xcc\xea\xe8\x13\xbf\xed\x8d\xdb\xf6\xc3\xce\x6f\xdd\xd6\x0d\xed\xff\xf0\x25\xd9\xd1\x2c\xea\x1d\x37\xad\x9b\x2c\x5e\x77\xce\xe0\x5e\x16\x5b\x68\x26\x2e\x53\x93\x38\x62\x30\xc1\xd2\xbb\x91\x99\x13\x74\xe2\x62\xb4\x30\xf6\x51\xea\x7c\x40\x87\x6c\x10\x34\xef\x46\xec\x62\x46\x7f\xe2\x3f\xbf\x8a\x23\xf6\x53\xc7\xb3\x15\x1e\xb4\xfe\x03\x78\x63\xf7\x39\xfa\xc5\xac\xd5\xf1\xe5\x73\x3c\xc1\xcb\xdb\x3a\xf9\x5b\x9b\x4d\xe6\x12\x2e\xc2\xe3\x4b\xd7\x0e\xc2\x15\x22\x0d\x10\x28\xf4\xf2\x77\x3f\xc6\x24\x40\xce\xf1\x93\xe5\x20\x3e\x45\x1f\x08\x9d\x0e\x9a\xf8\x3a\x59\xfe\x62\x89\x55\xf2\x48\x03\xfe\x7c\xf5\xf6\x1f\x73\xb8\x2b\xf9\x2c\x6b\x0d\x55\x94\x31\x78\x5b\xd5\xd1\x9d\xf3\xc6\x8a\x1c\x57\xdb\xaa\x69\x93\x7c\xb4\x0c\xc7\xbc\x12\x7e\xfa\x99\x9b\xda\xc7\xe7\x42\x95\x33\xf1\xba\x9e\x7b\x7a\x82\x7e\x7a\x82\x7e\x7a\x82\x7e\x7a\x82\xbe\x57\xd8\x7f\xd4\x27\xe8\xa7\x27\xe4\xa7\x27\xe4\xa7\x27\xe4\xbb\xbb\x4f\x4f\xc8\x4f\x4f\xc8\x4f\x4f\xc8\xd7\xbf\xd3\x13\xf2\xd3\x13\xf2\xd3\x13\xf2\xd3\x13\xf2\x2d\xdf\x4e\x35\xfd\xff\x7e\x7c\x79\xba\x1c\xfb\x63\x5c\x8e\x9d\xae\xbb\x4e\xd7\x5d\xa7\xeb\xae\xd3\x75\xd7\x2f\x38\xf1\xa7\xeb\xae\xd3\x75\xd7\xe9\xba\xeb\x74\xdd\xf5\x4f\x7a\xdd\x95\x09\xe5\x8e\xbe\xef\xfa\xdf\x00\x00\x00\xff\xff\x25\xf1\x04\x48\x34\x4b\x00\x00") func operatorsCoreosCom_operatorgroupsYamlBytes() ([]byte, error) { return bindataRead( diff --git a/staging/api/pkg/operators/v1/operatorgroup_test.go b/staging/api/pkg/operators/v1/operatorgroup_test.go new file mode 100644 index 0000000000..7d0b243972 --- /dev/null +++ b/staging/api/pkg/operators/v1/operatorgroup_test.go @@ -0,0 +1,70 @@ +package v1 + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestUpgradeStrategy(t *testing.T) { + tests := []struct { + description string + og *OperatorGroup + expected UpgradeStrategy + }{ + { + description: "NoSpec", + og: &OperatorGroup{}, + expected: UpgradeStrategyDefault, + }, + { + description: "NoUpgradeStrategy", + og: &OperatorGroup{ + Spec: OperatorGroupSpec{}, + }, + expected: UpgradeStrategyDefault, + }, + { + description: "NoUpgradeStrategy", + og: &OperatorGroup{ + Spec: OperatorGroupSpec{ + UpgradeStrategy: "", + }, + }, + expected: UpgradeStrategyDefault, + }, + { + description: "NonSupportedUpgradeStrategy", + og: &OperatorGroup{ + Spec: OperatorGroupSpec{ + UpgradeStrategy: "foo", + }, + }, + expected: UpgradeStrategyDefault, + }, + { + description: "DefaultUpgradeStrategy", + og: &OperatorGroup{ + Spec: OperatorGroupSpec{ + UpgradeStrategy: "Default", + }, + }, + expected: UpgradeStrategyDefault, + }, + { + description: "UnsafeFailForwardUpgradeStrategy", + og: &OperatorGroup{ + Spec: OperatorGroupSpec{ + UpgradeStrategy: "TechPreviewUnsafeFailForward", + }, + }, + expected: UpgradeStrategyUnsafeFailForward, + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + require.EqualValues(t, tt.expected, tt.og.UpgradeStrategy()) + }) + } +} diff --git a/staging/api/pkg/operators/v1/operatorgroup_types.go b/staging/api/pkg/operators/v1/operatorgroup_types.go index 1706eb17e4..81ad352d4e 100644 --- a/staging/api/pkg/operators/v1/operatorgroup_types.go +++ b/staging/api/pkg/operators/v1/operatorgroup_types.go @@ -24,8 +24,29 @@ const ( MutlipleOperatorGroupCondition = "MultipleOperatorGroup" MultipleOperatorGroupsReason = "MultipleOperatorGroupsFound" OperatorGroupServiceAccountReason = "ServiceAccountNotFound" + + // UpgradeStrategyDefault configures OLM such that it will only allow + // clusterServiceVersions to move to the replacing phase to the succeeded + // phase. This effectively means that OLM will not allow operators to move + // to the next version if an installation or upgrade has failed. + UpgradeStrategyDefault UpgradeStrategy = "Default" + + // UpgradeStrategyUnsafeFailForward configures OLM such that it will allow + // clusterServiceVersions to move to the replacing phase from the succeeded + // phase or from the failed phase. Additionally, OLM will generate new + // installPlans when a subscription references a failed installPlan and the + // catalog has been updated with a new upgrade for the existing set of + // operators. + // + // WARNING: The UpgradeStrategyUnsafeFailForward upgrade strategy is unsafe + // and may result in unexpected behavior or unrecoverable data loss unless + // you have deep understanding of the set of operators being managed in the + // namespace. + UpgradeStrategyUnsafeFailForward UpgradeStrategy = "TechPreviewUnsafeFailForward" ) +type UpgradeStrategy string + // OperatorGroupSpec is the spec for an OperatorGroup resource. type OperatorGroupSpec struct { // Selector selects the OperatorGroup's target namespaces. @@ -45,6 +66,29 @@ type OperatorGroupSpec struct { // Static tells OLM not to update the OperatorGroup's providedAPIs annotation // +optional StaticProvidedAPIs bool `json:"staticProvidedAPIs,omitempty"` + + // UpgradeStrategy defines the upgrade strategy for operators in the namespace. + // There are currently two supported upgrade strategies: + // + // Default: OLM will only allow clusterServiceVersions to move to the replacing + // phase from the succeeded phase. This effectively means that OLM will not + // allow operators to move to the next version if an installation or upgrade + // has failed. + // + // TechPreviewUnsafeFailForward: OLM will allow clusterServiceVersions to move to the + // replacing phase from the succeeded phase or from the failed phase. + // Additionally, OLM will generate new installPlans when a subscription references + // a failed installPlan and the catalog has been updated with a new upgrade for + // the existing set of operators. + // + // WARNING: The TechPreviewUnsafeFailForward upgrade strategy is unsafe and may result + // in unexpected behavior or unrecoverable data loss unless you have deep + // understanding of the set of operators being managed in the namespace. + // + // +kubebuilder:validation:Enum=Default;TechPreviewUnsafeFailForward + // +kubebuilder:default=Default + // +optional + UpgradeStrategy UpgradeStrategy `json:"upgradeStrategy,omitempty"` } // OperatorGroupStatus is the status for an OperatorGroupResource. @@ -76,6 +120,7 @@ type OperatorGroup struct { metav1.ObjectMeta `json:"metadata"` // +optional + // +kubebuilder:default={upgradeStrategy:Default} Spec OperatorGroupSpec `json:"spec"` Status OperatorGroupStatus `json:"status,omitempty"` } @@ -98,6 +143,17 @@ func (o *OperatorGroup) BuildTargetNamespaces() string { return strings.Join(ns, ",") } +// UpgradeStrategy returns the UpgradeStrategy specified or the default value otherwise. +func (o *OperatorGroup) UpgradeStrategy() UpgradeStrategy { + strategyName := o.Spec.UpgradeStrategy + switch { + case strategyName == UpgradeStrategyUnsafeFailForward: + return strategyName + default: + return UpgradeStrategyDefault + } +} + // IsServiceAccountSpecified returns true if the spec has a service account name specified. func (o *OperatorGroup) IsServiceAccountSpecified() bool { if o.Spec.ServiceAccountName == "" { diff --git a/staging/operator-lifecycle-manager/CONTRIBUTING.md b/staging/operator-lifecycle-manager/CONTRIBUTING.md index a6397a04a0..02d2f50731 100644 --- a/staging/operator-lifecycle-manager/CONTRIBUTING.md +++ b/staging/operator-lifecycle-manager/CONTRIBUTING.md @@ -31,8 +31,8 @@ This is a rough outline of what a contributor's workflow looks like: - Identify or create an issue. - Create a topic branch from where to base the contribution. This is usually the master branch. - Make commits of logical units. -- Make sure commit messages are in the proper format (see below). -- Ensure all relevant commit messages contain a valid sign-off message (see below). +- Make sure commit messages are in the proper format ([see below][commit-messages]). +- Ensure all relevant commit messages contain a valid sign-off message ([see below][commit-messages]). - Push changes in a topic branch to a personal fork of the repository. - Submit a pull request to the operator-framework/operator-lifecycle-manager repository. - Wait and respond to feedback from the maintainers listed in the OWNERS file. @@ -47,7 +47,7 @@ It can be helpful after submitting a PR to self-review your changes. This allows When opening PRs that are in a rough draft or WIP state, prefix the PR description with `WIP: ...` or create a draft PR. This can help save reviewer's time by communicating the state of a PR ahead of time. Draft/WIP PRs can be a good way to get early feedback from reviewers on the implementation, focusing less on smaller details, and more on the general approach of changes. -When contributing changes that require a new dependency, check whether it's feasable to directly vendor that code [without introducing a new dependency](https://go-proverbs.github.io/). +When contributing changes that require a new dependency, check whether it's feasible to directly vendor that code [without introducing a new dependency](https://go-proverbs.github.io/). Each PR must be labeled with at least one "lgtm" label and at least one "approved" label before it can be merged. Maintainers that have approval permissions are listed in the "approvers" column in the root [OWNERS][owners] file. @@ -62,11 +62,11 @@ In addition to the linked style documentation, OLM formats Golang packages using Please follow this style to make the OLM project easier to review, maintain and develop. -### Sign-off ([DCO][DCO]) +### Commit Messages and Sign-off ([DCO][DCO]) A [sign-off][sign-off] is a line towards the end of a commit message that certifies the commit author(s). -For more information on the structuring of commit messages, read the information in the [DCO](https://github.com/apps/dco) application that the OLM projects uses. +For more information on the structuring of commit messages, read the information in the [DCO][dco] application that the OLM projects uses. ## Documentation @@ -74,7 +74,7 @@ If the contribution changes the existing APIs or user interface it must include The OLM documentation mainly lives in the [operator-framework/olm-docs][olm-docs] repository. -[operator_framework]: https://groups.google.com/forum/#!forum/operator-framework +[operator_framework]: [dco]: [owners]: [issues]: @@ -84,3 +84,4 @@ The OLM documentation mainly lives in the [operator-framework/olm-docs][olm-docs [sign-off]: [goimports]: [gofmt]: +[commit-messages]: diff --git a/staging/operator-lifecycle-manager/README.md b/staging/operator-lifecycle-manager/README.md index d34a9b4e4c..80c371c71b 100644 --- a/staging/operator-lifecycle-manager/README.md +++ b/staging/operator-lifecycle-manager/README.md @@ -10,9 +10,10 @@ User documentation can be found on the [OLM website][olm-docs]. + ## Overview -This project is a component of the [Operator Framework](https://github.com/operator-framework), an open source toolkit to manage Kubernetes native applications, called Operators, in an effective, automated, and scalable way. Read more in the [introduction blog post](https://operatorhub.io/what-is-an-operator) and learn about practical use cases at [OLM-Book](https://operator-framework.github.io/olm-book/). +This project is a component of the [Operator Framework](https://github.com/operator-framework), an open source toolkit to manage Kubernetes native applications, called Operators, in an effective, automated, and scalable way. Read more in the [introduction blog post](https://operatorhub.io/what-is-an-operator) and learn about practical use cases at the [OLM website][olm-docs]. OLM extends Kubernetes to provide a declarative way to install, manage, and upgrade Operators and their dependencies in a cluster. It provides the following features: @@ -47,13 +48,13 @@ Operators can behave like managed service providers. Their user interface on the ## Getting Started -Check the [Getting Started][olm-getting-started] section. +Check out the [Getting Started][olm-getting-started] section in the docs. ### Installation Install OLM on a Kubernetes cluster by following the [installation guide][installation-guide]. -For a complete end-to-end example of how OLM fits into the Operator Framework, see the [Operator Framework Getting Started Guide](https://github.com/operator-framework/getting-started). Also, see [Getting Started on OperatorHub.io](https://operatorhub.io/getting-started). +For a complete end-to-end example of how OLM fits into the Operator Framework, see the [Operator Framework website](https://operatorframework.io/about/) and the [Getting Started guide on OperatorHub.io](https://operatorhub.io/getting-started). ## User Interface (Running the console Locally) @@ -96,13 +97,14 @@ Learn more about the components used by OLM by reading about the [architecture] OLM standardizes interactions with operators by requiring that the interface to an operator be via the Kubernetes API. Because we expect users to define the interfaces to their applications, OLM currently uses CRDs to define the Kubernetes API interactions. -Examples: [EtcdCluster CRD](https://github.com/operator-framework/community-operators/blob/master/community-operators/etcd/0.9.4/etcdclusters.etcd.database.coreos.com.crd.yaml), [EtcdBackup CRD](https://github.com/operator-framework/community-operators/blob/master/community-operators/etcd/0.9.4/etcdbackups.etcd.database.coreos.com.crd.yaml) +Examples: [EtcdCluster CRD](https://github.com/redhat-openshift-ecosystem/community-operators-prod/blob/main/operators/etcd/0.9.4/etcdclusters.etcd.database.coreos.com.crd.yaml), +[EtcdBackup CRD](https://github.com/redhat-openshift-ecosystem/community-operators-prod/blob/main/operators/etcd/0.9.4/etcdbackups.etcd.database.coreos.com.crd.yaml) ## Descriptors OLM introduces the notion of “descriptors” of both `spec` and `status` fields in kubernetes API responses. Descriptors are intended to indicate various properties of a field in order to make decisions about their content. For example, this can drive connecting two operators together (e.g. connecting the connection string from a mysql instance to a consuming application) and be used to drive rich interactions in a UI. -[See an example of a ClusterServiceVersion with descriptors](https://github.com/operator-framework/community-operators/blob/master/community-operators/etcd/0.9.2/etcdoperator.v0.9.2.clusterserviceversion.yaml) +[See an example of a ClusterServiceVersion with descriptors](https://github.com/redhat-openshift-ecosystem/community-operators-prod/blob/main/operators/etcd/0.9.2/etcdoperator.v0.9.2.clusterserviceversion.yaml) ## Dependency Resolution @@ -129,7 +131,7 @@ OLM has the concept of catalogs, which are repositories of application definitio Catalogs contain a set of Packages, which map “channels” to a particular application definition. Channels allow package authors to write different upgrade paths for different users (e.g. alpha vs. stable). -Example: [etcd package](https://github.com/operator-framework/community-operators/blob/master/community-operators/etcd/etcd.package.yaml) +Example: [etcd package](https://github.com/redhat-openshift-ecosystem/community-operators-prod/blob/main/operators/etcd/etcd.package.yaml) Users can subscribe to channels and have their operators automatically updated when new versions are released. @@ -154,7 +156,7 @@ Catalogs are served internally over a grpc interface to OLM from [operator-regis ## Samples -To explore any operator samples using the OLM, see the [https://operatorhub.io/](https://operatorhub.io/) and its resources in [Community Operators](https://github.com/operator-framework/community-operators/tree/master/upstream-community-operators). +To explore any operator samples using the OLM, see the [https://operatorhub.io/](https://operatorhub.io/) and its resources in [Community Operators](https://github.com/k8s-operatorhub/community-operators/tree/main/operators). ## Community and how to get involved @@ -194,6 +196,6 @@ Operator Lifecycle Manager is under Apache 2.0 license. See the [LICENSE][licens [operator-framework-community]: https://github.com/operator-framework/community [operator-framework-communication]: https://github.com/operator-framework/community#get-involved [operator-framework-meetings]: https://github.com/operator-framework/community#meetings -[contributor-documentation]: https://olm.operatorframework.io/docs/contribution-guidelines/ +[contributor-documentation]: ./CONTRIBUTING.md [olm-getting-started]: https://olm.operatorframework.io/docs/getting-started/ [installation-guide]: doc/install/install.md diff --git a/staging/operator-lifecycle-manager/deploy/chart/crds/0000_50_olm_00-operatorgroups.crd.yaml b/staging/operator-lifecycle-manager/deploy/chart/crds/0000_50_olm_00-operatorgroups.crd.yaml index 56ef0b5c4f..b62f3996a9 100644 --- a/staging/operator-lifecycle-manager/deploy/chart/crds/0000_50_olm_00-operatorgroups.crd.yaml +++ b/staging/operator-lifecycle-manager/deploy/chart/crds/0000_50_olm_00-operatorgroups.crd.yaml @@ -37,6 +37,8 @@ spec: spec: description: OperatorGroupSpec is the spec for an OperatorGroup resource. type: object + default: + upgradeStrategy: Default properties: selector: description: Selector selects the OperatorGroup's target namespaces. @@ -80,6 +82,13 @@ spec: items: type: string x-kubernetes-list-type: set + upgradeStrategy: + description: "UpgradeStrategy defines the upgrade strategy for operators in the namespace. There are currently two supported upgrade strategies: \n Default: OLM will only allow clusterServiceVersions to move to the replacing phase from the succeeded phase. This effectively means that OLM will not allow operators to move to the next version if an installation or upgrade has failed. \n TechPreviewUnsafeFailForward: OLM will allow clusterServiceVersions to move to the replacing phase from the succeeded phase or from the failed phase. Additionally, OLM will generate new installPlans when a subscription references a failed installPlan and the catalog has been updated with a new upgrade for the existing set of operators. \n WARNING: The TechPreviewUnsafeFailForward upgrade strategy is unsafe and may result in unexpected behavior or unrecoverable data loss unless you have deep understanding of the set of operators being managed in the namespace." + type: string + default: Default + enum: + - Default + - TechPreviewUnsafeFailForward status: description: OperatorGroupStatus is the status for an OperatorGroupResource. type: object diff --git a/staging/operator-lifecycle-manager/doc/dev/reporting_flakes.md b/staging/operator-lifecycle-manager/doc/dev/reporting_flakes.md index 8d91d9b4c3..43e259e7d9 100644 --- a/staging/operator-lifecycle-manager/doc/dev/reporting_flakes.md +++ b/staging/operator-lifecycle-manager/doc/dev/reporting_flakes.md @@ -1,7 +1,7 @@ # Reporting flakes If you are struggling to get your PR through because unrelated e2e or unit tests are randomly failing, it's likely -you are being plagued by a flaky test 😱, a test that wasn't constructed as carefully as it should have been as is +you are being plagued by a flaky test 😱, a test that wasn't constructed as carefully as it should have been and is failing even when it should be succeeding. When this happens, check our [issues](https://github.com/operator-framework/operator-lifecycle-manager/issues) to see if it has been filed before. Search also in the `closed issues`. If you find one, re-open it if necessary. Otherwise, [file](https://github.com/operator-framework/operator-lifecycle-manager/issues/new) a flaky test issue. diff --git a/staging/operator-lifecycle-manager/go.mod b/staging/operator-lifecycle-manager/go.mod index 89e954f815..8a3d38a13e 100644 --- a/staging/operator-lifecycle-manager/go.mod +++ b/staging/operator-lifecycle-manager/go.mod @@ -24,7 +24,7 @@ require ( github.com/onsi/gomega v1.17.0 github.com/openshift/api v0.0.0-20200331152225-585af27e34fd github.com/openshift/client-go v0.0.0-20200326155132-2a6cd50aedd0 - github.com/operator-framework/api v0.11.2-0.20220128160316-8e593f1c42b9 + github.com/operator-framework/api v0.14.1-0.20220413143725-33310d6154f3 github.com/operator-framework/operator-registry v1.17.5 github.com/otiai10/copy v1.2.0 github.com/pkg/errors v0.9.1 @@ -240,4 +240,6 @@ replace ( // controller runtime github.com/openshift/api => github.com/openshift/api v0.0.0-20211014063134-be2a7fb8aa44 github.com/openshift/client-go => github.com/openshift/client-go v0.0.0-20200326155132-2a6cd50aedd0 // release-4.5 + // TODO(fail-forward): Remove when api changes are released + github.com/operator-framework/api => github.com/operator-framework/api v0.14.1-0.20220413143725-33310d6154f3 ) diff --git a/staging/operator-lifecycle-manager/go.sum b/staging/operator-lifecycle-manager/go.sum index fa857d29bf..af7b533dfb 100644 --- a/staging/operator-lifecycle-manager/go.sum +++ b/staging/operator-lifecycle-manager/go.sum @@ -163,7 +163,6 @@ github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932/go.mod h1:NOuUCS github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA= github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= -github.com/blang/semver v3.5.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ= github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= @@ -242,7 +241,6 @@ github.com/coreos/go-systemd/v22 v22.0.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+ github.com/coreos/go-systemd/v22 v22.3.2 h1:D9/bQk5vlXQFZ6Kwuu6zaiXJ9oTPe68++AzAJc1DzSI= github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= -github.com/coreos/pkg v0.0.0-20180108230652-97fdf19511ea/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF22jk= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= @@ -294,7 +292,6 @@ github.com/docker/distribution v2.7.0+incompatible/go.mod h1:J2gT2udsDAN96Uj4Kfc github.com/docker/distribution v2.7.1+incompatible h1:a5mlkVzth6W5A4fOsS3D2EO5BUmsJpcB+cRlLU7cSug= github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/docker v0.7.3-0.20190103212154-2b7e084dc98b/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/docker v0.7.3-0.20190327010347-be7ac8be2ae0/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker v0.7.3-0.20190817195342-4760db040282/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker v1.4.2-0.20200203170920-46ec8731fbce/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker v17.12.0-ce-rc1.0.20200618181300-9dc6525e6118+incompatible h1:iWPIG7pWIsCwT6ZtHnTUpoVMnete7O/pzd9HFE3+tn8= @@ -467,7 +464,6 @@ github.com/go-openapi/swag v0.19.14 h1:gm3vOOXfiuw5i9p5N9xJvfjvuofpyvLA9Wr6QfK5F github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= github.com/go-openapi/validate v0.18.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4= github.com/go-openapi/validate v0.19.2/go.mod h1:1tRCw7m3jtI8eNWEEliiAqUIcBztB2KDnRCRMUi7GTA= -github.com/go-openapi/validate v0.19.5/go.mod h1:8DJv2CVJQ6kGNpFW6eV9N3JviE1C85nY1c2z52x1Gk4= github.com/go-openapi/validate v0.19.8/go.mod h1:8DJv2CVJQ6kGNpFW6eV9N3JviE1C85nY1c2z52x1Gk4= github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= @@ -481,7 +477,6 @@ github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg78 github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= github.com/gobuffalo/envy v1.7.1 h1:OQl5ys5MBea7OGCdvPbBJWRgnhC/fGona6QKfvFeau8= github.com/gobuffalo/envy v1.7.1/go.mod h1:FurDp9+EDPE4aIUS3ZLyD+7/9fpx7YRt/ukY6jIHf0w= -github.com/gobuffalo/flect v0.2.0/go.mod h1:W3K3X9ksuZfir8f/LrfVtWmCDQFfayuylOJ7sz/Fj80= github.com/gobuffalo/flect v0.2.3 h1:f/ZukRnSNA/DUpSNDadko7Qc0PhGvsew35p/2tu+CRY= github.com/gobuffalo/flect v0.2.3/go.mod h1:vmkQwuZYhN5Pc4ljYQZzP+1sq+NEkK+lh20jmEmX3jc= github.com/gobuffalo/here v0.6.0/go.mod h1:wAG085dHOYqUpf+Ap+WOdrPTp5IYcDAs/x7PLa8Y5fM= @@ -787,7 +782,6 @@ github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJ github.com/markbates/pkger v0.17.1/go.mod h1:0JoVlrol20BSywW79rN3kdFFsE5xYM+rSCQDXbLhiuI= github.com/marstr/guid v1.1.0/go.mod h1:74gB1z2wpxxInTG6yaqA7KrtM0NZ+RbrcqDvYHefzho= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= -github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= @@ -903,7 +897,6 @@ github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1Cpa github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= -github.com/onsi/gomega v1.8.1/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.10.2/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.17.0 h1:9Luw4uT5HTjHTN8+aNcSThgH1vdXnmdJ8xIfZ4wyTRE= @@ -937,9 +930,8 @@ github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxS github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= -github.com/operator-framework/api v0.7.1/go.mod h1:L7IvLd/ckxJEJg/t4oTTlnHKAJIP/p51AvEslW3wYdY= -github.com/operator-framework/api v0.11.2-0.20220128160316-8e593f1c42b9 h1:aOF1+e6amntViIbc9BrpD07cwmgcVFG4wxPbj/VGCjA= -github.com/operator-framework/api v0.11.2-0.20220128160316-8e593f1c42b9/go.mod h1:r/erkmp9Kc1Al4dnxmRkJYc0uCtD5FohN9VuJ5nTxz0= +github.com/operator-framework/api v0.14.1-0.20220413143725-33310d6154f3 h1:su4h7uvJ+2xfvUDdSUxzsqtnlKqE5u0zmnUyj19x5PA= +github.com/operator-framework/api v0.14.1-0.20220413143725-33310d6154f3/go.mod h1:r/erkmp9Kc1Al4dnxmRkJYc0uCtD5FohN9VuJ5nTxz0= github.com/operator-framework/operator-registry v1.17.5 h1:LR8m1rFz5Gcyje8WK6iYt+gIhtzqo52zMRALdmTYHT0= github.com/operator-framework/operator-registry v1.17.5/go.mod h1:sRQIgDMZZdUcmHltzyCnM6RUoDF+WS8Arj1BQIARDS8= github.com/otiai10/copy v1.2.0 h1:HvG945u96iNadPoG2/Ja2+AUJeW5YuFQMixq9yirC+k= @@ -1580,7 +1572,6 @@ golang.org/x/tools v0.0.0-20200509030707-2212a7e161a5/go.mod h1:EkVYQZoAsY45+roY golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200616133436-c1934b75d054/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200616195046-dc31b401abb5/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= @@ -1782,7 +1773,6 @@ gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= -gopkg.in/yaml.v3 v3.0.0-20190905181640-827449938966/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= @@ -1804,7 +1794,6 @@ honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= k8s.io/api v0.18.0/go.mod h1:q2HRQkfDzHMBZL9l/y9rH63PkQl4vae0xRT+8prbrK8= -k8s.io/api v0.18.2/go.mod h1:SJCWI7OLzhZSvbY7U8zwNl9UA4o1fizoug34OV/2r78= k8s.io/api v0.20.1/go.mod h1:KqwcCVogGxQY3nBlRpwt+wpAMF/KjaCc7RpywacvqUo= k8s.io/api v0.20.6/go.mod h1:X9e8Qag6JV/bL5G6bU8sdVRltWKmdHsFUGS3eVndqE8= k8s.io/api v0.21.0/go.mod h1:+YbrhBBGgsxbF6o6Kj4KJPJnBmAKuXDeS3E18bgHNVU= @@ -1813,15 +1802,12 @@ k8s.io/api v0.22.1/go.mod h1:bh13rkTp3F1XEaLGykbyRD2QaTTzPm0e/BMd8ptFONY= k8s.io/api v0.23.0/go.mod h1:8wmDdLBHBNxtOIytwLstXt5E9PddnZb0GaMcqsvDBpg= k8s.io/api v0.23.1 h1:ncu/qfBfUoClqwkTGbeRqqOqBCRoUAflMuOaOD7J0c8= k8s.io/api v0.23.1/go.mod h1:WfXnOnwSqNtG62Y1CdjoMxh7r7u9QXGCkA1u0na2jgo= -k8s.io/apiextensions-apiserver v0.18.2/go.mod h1:q3faSnRGmYimiocj6cHQ1I3WpLqmDgJFlKL37fC4ZvY= -k8s.io/apiextensions-apiserver v0.18.2/go.mod h1:q3faSnRGmYimiocj6cHQ1I3WpLqmDgJFlKL37fC4ZvY= k8s.io/apiextensions-apiserver v0.20.1/go.mod h1:ntnrZV+6a3dB504qwC5PN/Yg9PBiDNt1EVqbW2kORVk= k8s.io/apiextensions-apiserver v0.20.6/go.mod h1:qO8YMqeMmZH+lV21LUNzV41vfpoE9QVAJRA+MNqj0mo= k8s.io/apiextensions-apiserver v0.21.0/go.mod h1:gsQGNtGkc/YoDG9loKI0V+oLZM4ljRPjc/sql5tmvzc= k8s.io/apiextensions-apiserver v0.23.0 h1:uii8BYmHYiT2ZTAJxmvc3X8UhNYMxl2A0z0Xq3Pm+WY= k8s.io/apiextensions-apiserver v0.23.0/go.mod h1:xIFAEEDlAZgpVBl/1VSjGDmLoXAWRG40+GsWhKhAxY4= k8s.io/apimachinery v0.18.0/go.mod h1:9SnR/e11v5IbyPCGbvJViimtJ0SwHG4nfZFjU77ftcA= -k8s.io/apimachinery v0.18.2/go.mod h1:9SnR/e11v5IbyPCGbvJViimtJ0SwHG4nfZFjU77ftcA= k8s.io/apimachinery v0.20.1/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU= k8s.io/apimachinery v0.20.2/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU= k8s.io/apimachinery v0.20.6/go.mod h1:ejZXtW1Ra6V1O5H8xPBGz+T3+4gfkTCeExAHKU57MAc= @@ -1831,8 +1817,6 @@ k8s.io/apimachinery v0.22.1/go.mod h1:O3oNtNadZdeOMxHFVxOreoznohCpy0z6mocxbZr7oJ k8s.io/apimachinery v0.23.0/go.mod h1:fFCTTBKvKcwTPFzjlcxp91uPFZr+JA0FubU4fLzzFYc= k8s.io/apimachinery v0.23.1 h1:sfBjlDFwj2onG0Ijx5C+SrAoeUscPrmghm7wHP+uXlo= k8s.io/apimachinery v0.23.1/go.mod h1:SADt2Kl8/sttJ62RRsi9MIV4o8f5S3coArm0Iu3fBno= -k8s.io/apiserver v0.18.2/go.mod h1:Xbh066NqrZO8cbsoenCwyDJ1OSi8Ag8I2lezeHxzwzw= -k8s.io/apiserver v0.18.2/go.mod h1:Xbh066NqrZO8cbsoenCwyDJ1OSi8Ag8I2lezeHxzwzw= k8s.io/apiserver v0.20.1/go.mod h1:ro5QHeQkgMS7ZGpvf4tSMx6bBOgPfE+f52KwvXfScaU= k8s.io/apiserver v0.20.6/go.mod h1:QIJXNt6i6JB+0YQRNcS0hdRHJlMhflFmsBDeSgT1r8Q= k8s.io/apiserver v0.21.0/go.mod h1:w2YSn4/WIwYuxG5zJmcqtRdtqgW/J2JRgFAqps3bBpg= @@ -1844,7 +1828,6 @@ k8s.io/cli-runtime v0.21.0/go.mod h1:XoaHP93mGPF37MkLbjGVYqg3S1MnsFdKtiA/RZzzxOo k8s.io/cli-runtime v0.23.1 h1:vHUZrq1Oejs0WaJnxs09mLHKScvIIl2hMSthhS8o8Yo= k8s.io/cli-runtime v0.23.1/go.mod h1:r9r8H/qfXo9w+69vwUL7LokKlLRKW5D6A8vUKCx+YL0= k8s.io/client-go v0.18.0/go.mod h1:uQSYDYs4WhVZ9i6AIoEZuwUggLVEF64HOD37boKAtF8= -k8s.io/client-go v0.18.2/go.mod h1:Xcm5wVGXX9HAA2JJ2sSBUn3tCJ+4SVlCbl2MNNv+CIU= k8s.io/client-go v0.20.1/go.mod h1:/zcHdt1TeWSd5HoUe6elJmHSQ6uLLgp4bIJHVEuy+/Y= k8s.io/client-go v0.20.6/go.mod h1:nNQMnOvEUEsOzRRFIIkdmYOjAZrC8bgq0ExboWSU1I0= k8s.io/client-go v0.21.0/go.mod h1:nNBytTF9qPFDEhoqgEPaarobC8QPae13bElIVHzIglA= @@ -1853,7 +1836,6 @@ k8s.io/client-go v0.23.0/go.mod h1:hrDnpnK1mSr65lHHcUuIZIXDgEbzc7/683c6hyG4jTA= k8s.io/client-go v0.23.1 h1:Ma4Fhf/p07Nmj9yAB1H7UwbFHEBrSPg8lviR24U2GiQ= k8s.io/client-go v0.23.1/go.mod h1:6QSI8fEuqD4zgFK0xbdwfB/PthBsIxCJMa3s17WlcO0= k8s.io/code-generator v0.18.0/go.mod h1:+UHX5rSbxmR8kzS+FAv7um6dtYrZokQvjHpDSYRVkTc= -k8s.io/code-generator v0.18.2/go.mod h1:+UHX5rSbxmR8kzS+FAv7um6dtYrZokQvjHpDSYRVkTc= k8s.io/code-generator v0.20.1/go.mod h1:UsqdF+VX4PU2g46NC2JRs4gc+IfrctnwHb76RNbWHJg= k8s.io/code-generator v0.20.6/go.mod h1:i6FmG+QxaLxvJsezvZp0q/gAEzzOz3U53KFibghWToU= k8s.io/code-generator v0.21.0/go.mod h1:hUlps5+9QaTrKx+jiM4rmq7YmH8wPOIko64uZCHDh6Q= @@ -1862,8 +1844,6 @@ k8s.io/code-generator v0.22.1/go.mod h1:eV77Y09IopzeXOJzndrDyCI88UBok2h6WxAlBwpx k8s.io/code-generator v0.23.0/go.mod h1:vQvOhDXhuzqiVfM/YHp+dmg10WDZCchJVObc9MvowsE= k8s.io/code-generator v0.23.1 h1:ViFOlP/0bYD7VrnUDS+ch5ej5EIuMawFmHcRuv9Yxyw= k8s.io/code-generator v0.23.1/go.mod h1:V7yn6VNTCWW8GqodYCESVo95fuiEg713S8B7WacWZDA= -k8s.io/component-base v0.18.2/go.mod h1:kqLlMuhJNHQ9lz8Z7V5bxUUtjFZnrypArGl58gmDfUM= -k8s.io/component-base v0.18.2/go.mod h1:kqLlMuhJNHQ9lz8Z7V5bxUUtjFZnrypArGl58gmDfUM= k8s.io/component-base v0.20.1/go.mod h1:guxkoJnNoh8LNrbtiQOlyp2Y2XFCZQmrcg2n/DeYNLk= k8s.io/component-base v0.20.6/go.mod h1:6f1MPBAeI+mvuts3sIdtpjljHWBQ2cIy38oBIWMYnrM= k8s.io/component-base v0.21.0/go.mod h1:qvtjz6X0USWXbgmbfXR+Agik4RZ3jv2Bgr5QnZzdPYw= @@ -1918,7 +1898,6 @@ rsc.io/letsencrypt v0.0.3 h1:H7xDfhkaFFSYEJlKeq38RwX2jYcnTeHuDQyT+mMNMwM= rsc.io/letsencrypt v0.0.3/go.mod h1:buyQKZ6IXrRnB7TdkHP0RyEybLx18HHyOSoTyoOLqNY= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= -sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.7/go.mod h1:PHgbrJT7lCHcxMU+mDHEm+nx46H4zuuHZkDP6icnhu0= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.14/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.15/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.22/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= @@ -1928,7 +1907,6 @@ sigs.k8s.io/controller-runtime v0.8.0/go.mod h1:v9Lbj5oX443uR7GXYY46E0EE2o7k2YxQ sigs.k8s.io/controller-runtime v0.11.0/go.mod h1:KKwLiTooNGu+JmLZGn9Sl3Gjmfj66eMbCQznLP5zcqA= sigs.k8s.io/controller-runtime v0.11.1 h1:7YIHT2QnHJArj/dk9aUkYhfqfK5cIxPOX5gPECfdZLU= sigs.k8s.io/controller-runtime v0.11.1/go.mod h1:KKwLiTooNGu+JmLZGn9Sl3Gjmfj66eMbCQznLP5zcqA= -sigs.k8s.io/controller-tools v0.4.1/go.mod h1:G9rHdZMVlBDocIxGkK3jHLWqcTMNvveypYJwrvYKjWU= sigs.k8s.io/controller-tools v0.8.0 h1:uUkfTGEwrguqYYfcI2RRGUnC8mYdCFDqfwPKUcNJh1o= sigs.k8s.io/controller-tools v0.8.0/go.mod h1:qE2DXhVOiEq5ijmINcFbqi9GZrrUjzB1TuJU0xa6eoY= sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6 h1:fD1pz4yfdADVNfFmcP2aBEtudwUQ1AlLnRBALr33v3s= diff --git a/staging/operator-lifecycle-manager/pkg/controller/operators/catalog/operator.go b/staging/operator-lifecycle-manager/pkg/controller/operators/catalog/operator.go index 455d9f4646..da2994ac3c 100644 --- a/staging/operator-lifecycle-manager/pkg/controller/operators/catalog/operator.go +++ b/staging/operator-lifecycle-manager/pkg/controller/operators/catalog/operator.go @@ -42,6 +42,7 @@ import ( "k8s.io/client-go/util/workqueue" "github.com/operator-framework/api/pkg/operators/reference" + operatorsv1 "github.com/operator-framework/api/pkg/operators/v1" "github.com/operator-framework/api/pkg/operators/v1alpha1" "github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/clientset/versioned" "github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/informers/externalversions" @@ -102,6 +103,7 @@ type Operator struct { catsrcQueueSet *queueinformer.ResourceQueueSet subQueueSet *queueinformer.ResourceQueueSet ipQueueSet *queueinformer.ResourceQueueSet + ogQueueSet *queueinformer.ResourceQueueSet nsResolveQueue workqueue.RateLimitingInterface namespace string recorder record.EventRecorder @@ -181,6 +183,7 @@ func NewOperator(ctx context.Context, kubeconfigPath string, clock utilclock.Clo catsrcQueueSet: queueinformer.NewEmptyResourceQueueSet(), subQueueSet: queueinformer.NewEmptyResourceQueueSet(), ipQueueSet: queueinformer.NewEmptyResourceQueueSet(), + ogQueueSet: queueinformer.NewEmptyResourceQueueSet(), catalogSubscriberIndexer: map[string]cache.Indexer{}, serviceAccountQuerier: scoped.NewUserDefinedServiceAccountQuerier(logger, crClient), clientAttenuator: scoped.NewClientAttenuator(logger, config, opClient), @@ -255,6 +258,24 @@ func NewOperator(ctx context.Context, kubeconfigPath string, clock utilclock.Clo return nil, err } + operatorGroupInformer := crInformerFactory.Operators().V1().OperatorGroups() + op.lister.OperatorsV1().RegisterOperatorGroupLister(metav1.NamespaceAll, operatorGroupInformer.Lister()) + ogQueue := workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "ogs") + op.ogQueueSet.Set(metav1.NamespaceAll, ogQueue) + operatorGroupQueueInformer, err := queueinformer.NewQueueInformer( + ctx, + queueinformer.WithLogger(op.logger), + queueinformer.WithQueue(ogQueue), + queueinformer.WithInformer(operatorGroupInformer.Informer()), + queueinformer.WithSyncer(queueinformer.LegacySyncHandler(op.syncResolvingNamespace).ToSyncer()), + ) + if err != nil { + return nil, err + } + if err := op.RegisterQueueInformer(operatorGroupQueueInformer); err != nil { + return nil, err + } + // Wire CatalogSources catsrcInformer := crInformerFactory.Operators().V1alpha1().CatalogSources() op.lister.OperatorsV1alpha1().RegisterCatalogSourceLister(metav1.NamespaceAll, catsrcInformer.Lister()) @@ -882,6 +903,20 @@ func (o *Operator) syncCatalogSources(obj interface{}) (syncError error) { return } +func (o *Operator) isFailForwardEnabled(namespace string) (bool, error) { + ogs, err := o.lister.OperatorsV1().OperatorGroupLister().OperatorGroups(namespace).List(labels.Everything()) + if err != nil { + o.logger.Debugf("failed to list operatorgroups in the %s namespace: %v", namespace, err) + // Couldn't list operatorGroups, assuming default upgradeStrategy + // so existing behavior is observed for failed CSVs. + return false, nil + } + if len(ogs) != 1 { + return false, fmt.Errorf("found %d operatorGroups in namespace %s, expected 1", len(ogs), namespace) + } + return ogs[0].UpgradeStrategy() == operatorsv1.UpgradeStrategyUnsafeFailForward, nil +} + func (o *Operator) syncResolvingNamespace(obj interface{}) error { ns, ok := obj.(*corev1.Namespace) if !ok { @@ -908,6 +943,11 @@ func (o *Operator) syncResolvingNamespace(obj interface{}) error { return err } + failForwardEnabled, err := o.isFailForwardEnabled(namespace) + if err != nil { + return err + } + // TODO: parallel maxGeneration := 0 subscriptionUpdated := false @@ -924,7 +964,7 @@ func (o *Operator) syncResolvingNamespace(obj interface{}) error { } // ensure the installplan reference is correct - sub, changedIP, err := o.ensureSubscriptionInstallPlanState(logger, sub) + sub, changedIP, err := o.ensureSubscriptionInstallPlanState(logger, sub, failForwardEnabled) if err != nil { logger.Debugf("error ensuring installplan state: %v", err) return err @@ -932,7 +972,7 @@ func (o *Operator) syncResolvingNamespace(obj interface{}) error { subscriptionUpdated = subscriptionUpdated || changedIP // record the current state of the desired corresponding CSV in the status. no-op if we don't know the csv yet. - sub, changedCSV, err := o.ensureSubscriptionCSVState(logger, sub) + sub, changedCSV, err := o.ensureSubscriptionCSVState(logger, sub, failForwardEnabled) if err != nil { logger.Debugf("error recording current state of CSV in status: %v", err) return err @@ -958,7 +998,7 @@ func (o *Operator) syncResolvingNamespace(obj interface{}) error { logger.Debug("resolving subscriptions in namespace") // resolve a set of steps to apply to a cluster, a set of subscriptions to create/update, and any errors - steps, bundleLookups, updatedSubs, err := o.resolver.ResolveSteps(namespace) + steps, bundleLookups, updatedSubs, err := o.resolver.ResolveSteps(namespace, failForwardEnabled) if err != nil { go o.recorder.Event(ns, corev1.EventTypeWarning, "ResolutionFailed", err.Error()) // If the error is constraints not satisfiable, then simply project the @@ -1080,7 +1120,7 @@ func (o *Operator) nothingToUpdate(logger *logrus.Entry, sub *v1alpha1.Subscript return false } -func (o *Operator) ensureSubscriptionInstallPlanState(logger *logrus.Entry, sub *v1alpha1.Subscription) (*v1alpha1.Subscription, bool, error) { +func (o *Operator) ensureSubscriptionInstallPlanState(logger *logrus.Entry, sub *v1alpha1.Subscription, failForwardEnabled bool) (*v1alpha1.Subscription, bool, error) { if sub.Status.InstallPlanRef != nil || sub.Status.Install != nil { return sub, false, nil } @@ -1110,13 +1150,16 @@ func (o *Operator) ensureSubscriptionInstallPlanState(logger *logrus.Entry, sub out.Status.InstallPlanRef = ref out.Status.Install = v1alpha1.NewInstallPlanReference(ref) out.Status.State = v1alpha1.SubscriptionStateUpgradePending + if failForwardEnabled && ip.Status.Phase == v1alpha1.InstallPlanPhaseFailed { + out.Status.State = v1alpha1.SubscriptionStateFailed + } out.Status.CurrentCSV = out.Spec.StartingCSV out.Status.LastUpdated = o.now() return out, true, nil } -func (o *Operator) ensureSubscriptionCSVState(logger *logrus.Entry, sub *v1alpha1.Subscription) (*v1alpha1.Subscription, bool, error) { +func (o *Operator) ensureSubscriptionCSVState(logger *logrus.Entry, sub *v1alpha1.Subscription, failForwardEnabled bool) (*v1alpha1.Subscription, bool, error) { if sub.Status.CurrentCSV == "" { return sub, false, nil } @@ -1126,6 +1169,14 @@ func (o *Operator) ensureSubscriptionCSVState(logger *logrus.Entry, sub *v1alpha if err != nil { logger.WithError(err).WithField("currentCSV", sub.Status.CurrentCSV).Debug("error fetching csv listed in subscription status") out.Status.State = v1alpha1.SubscriptionStateUpgradePending + if failForwardEnabled && sub.Status.InstallPlanRef != nil { + ip, err := o.client.OperatorsV1alpha1().InstallPlans(sub.GetNamespace()).Get(context.TODO(), sub.Status.InstallPlanRef.Name, metav1.GetOptions{}) + if err != nil { + logger.WithError(err).WithField("currentCSV", sub.Status.CurrentCSV).Debug("error fetching installplan listed in subscription status") + } else if ip.Status.Phase == v1alpha1.InstallPlanPhaseFailed { + out.Status.State = v1alpha1.SubscriptionStateFailed + } + } } else { out.Status.State = v1alpha1.SubscriptionStateAtLatest out.Status.InstalledCSV = sub.Status.CurrentCSV diff --git a/staging/operator-lifecycle-manager/pkg/controller/operators/catalog/operator_test.go b/staging/operator-lifecycle-manager/pkg/controller/operators/catalog/operator_test.go index 5518bf1d3d..80d3bdfe40 100644 --- a/staging/operator-lifecycle-manager/pkg/controller/operators/catalog/operator_test.go +++ b/staging/operator-lifecycle-manager/pkg/controller/operators/catalog/operator_test.go @@ -1254,7 +1254,7 @@ func TestSyncResolvingNamespace(t *testing.T) { o.sourcesLastUpdate.Set(tt.fields.sourcesLastUpdate.Time) o.resolver = &fakes.FakeStepResolver{ - ResolveStepsStub: func(string) ([]*v1alpha1.Step, []v1alpha1.BundleLookup, []*v1alpha1.Subscription, error) { + ResolveStepsStub: func(string, bool) ([]*v1alpha1.Step, []v1alpha1.BundleLookup, []*v1alpha1.Subscription, error) { return nil, nil, nil, tt.fields.resolveErr }, } diff --git a/staging/operator-lifecycle-manager/pkg/controller/operators/catalog/subscriptions_test.go b/staging/operator-lifecycle-manager/pkg/controller/operators/catalog/subscriptions_test.go index 6a813d88df..028d944c35 100644 --- a/staging/operator-lifecycle-manager/pkg/controller/operators/catalog/subscriptions_test.go +++ b/staging/operator-lifecycle-manager/pkg/controller/operators/catalog/subscriptions_test.go @@ -1031,7 +1031,7 @@ func TestSyncSubscriptions(t *testing.T) { o.sourcesLastUpdate.Set(tt.fields.sourcesLastUpdate.Time) o.resolver = &fakes.FakeStepResolver{ - ResolveStepsStub: func(string) ([]*v1alpha1.Step, []v1alpha1.BundleLookup, []*v1alpha1.Subscription, error) { + ResolveStepsStub: func(string, bool) ([]*v1alpha1.Step, []v1alpha1.BundleLookup, []*v1alpha1.Subscription, error) { return tt.fields.resolveSteps, tt.fields.bundleLookups, tt.fields.resolveSubs, tt.fields.resolveErr }, } @@ -1168,7 +1168,7 @@ func BenchmarkSyncResolvingNamespace(b *testing.B) { }, }, resolver: &fakes.FakeStepResolver{ - ResolveStepsStub: func(string) ([]*v1alpha1.Step, []v1alpha1.BundleLookup, []*v1alpha1.Subscription, error) { + ResolveStepsStub: func(string, bool) ([]*v1alpha1.Step, []v1alpha1.BundleLookup, []*v1alpha1.Subscription, error) { steps := []*v1alpha1.Step{ { Resolving: "csv.v.2", 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 7efa2746d6..194843361a 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/install" "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/operators/internal/pruning" "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/operators/olm/overrides" + resolver "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver" "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" @@ -1996,6 +1997,15 @@ func (a *Operator) transitionCSVState(in v1alpha1.ClusterServiceVersion) (out *v } case v1alpha1.CSVPhaseFailed: + // Transition to the replacing phase if FailForward is enabled and a CSV exists that replaces the operator. + if operatorGroup.UpgradeStrategy() == operatorsv1.UpgradeStrategyUnsafeFailForward { + if replacement := a.isBeingReplaced(out, a.csvSet(out.GetNamespace(), v1alpha1.CSVPhaseAny)); replacement != nil { + msg := fmt.Sprintf("Fail Forward is enabled, allowing %s csv to be replaced by csv: %s", out.Status.Phase, replacement.GetName()) + out.SetPhaseWithEvent(v1alpha1.CSVPhaseReplacing, v1alpha1.CSVReasonBeingReplaced, msg, a.now(), a.recorder) + metrics.CSVUpgradeCount.Inc() + return + } + } installer, strategy := a.parseStrategiesAndUpdateStatus(out) if strategy == nil { return @@ -2087,7 +2097,29 @@ func (a *Operator) transitionCSVState(in v1alpha1.ClusterServiceVersion) (out *v } // If there is a succeeded replacement, mark this for deletion - if next := a.isBeingReplaced(out, a.csvSet(out.GetNamespace(), v1alpha1.CSVPhaseAny)); next != nil { + next := a.isBeingReplaced(out, a.csvSet(out.GetNamespace(), v1alpha1.CSVPhaseAny)) + // Get the newest CSV in the replacement chain if fail forward upgrades are enabled. + if operatorGroup.UpgradeStrategy() == operatorsv1.UpgradeStrategyUnsafeFailForward { + csvs, err := a.lister.OperatorsV1alpha1().ClusterServiceVersionLister().ClusterServiceVersions(next.GetNamespace()).List(labels.Everything()) + if err != nil { + syncError = err + return + } + + lastCSVInChain, err := resolver.WalkReplacementChain(next, resolver.ReplacementMapping(csvs), resolver.WithUniqueCSVs()) + if err != nil { + syncError = err + return + } + + if lastCSVInChain == nil { + syncError = fmt.Errorf("fail forward upgrades enabled, unable to identify last CSV in replacement chain") + return + } + + next = lastCSVInChain + } + if next != nil { if next.Status.Phase == v1alpha1.CSVPhaseSucceeded { out.SetPhaseWithEvent(v1alpha1.CSVPhaseDeleting, v1alpha1.CSVReasonReplaced, "has been replaced by a newer ClusterServiceVersion that has successfully installed.", now, a.recorder) } else { 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 a7a2b8dd91..a1d7cee10e 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 @@ -3091,6 +3091,289 @@ func TestTransitionCSV(t *testing.T) { } } +// TODO: Merge the following set of tests with those defined in TestTransitionCSV +// once those tests are updated to include validation against CSV phases. +func TestTransitionCSVFailForward(t *testing.T) { + logrus.SetLevel(logrus.DebugLevel) + namespace := "ns" + + defaultOperatorGroup := &operatorsv1.OperatorGroup{ + TypeMeta: metav1.TypeMeta{ + Kind: "OperatorGroup", + APIVersion: operatorsv1.SchemeGroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "default", + Namespace: namespace, + Annotations: map[string]string{ + "olm.providedAPIs": "c1.v1.g1", + }, + }, + Spec: operatorsv1.OperatorGroupSpec{}, + Status: operatorsv1.OperatorGroupStatus{ + Namespaces: []string{namespace}, + }, + } + + defaultTemplateAnnotations := map[string]string{ + operatorsv1.OperatorGroupTargetsAnnotationKey: namespace, + operatorsv1.OperatorGroupNamespaceAnnotationKey: namespace, + operatorsv1.OperatorGroupAnnotationKey: defaultOperatorGroup.GetName(), + } + + type csvState struct { + exists bool + phase v1alpha1.ClusterServiceVersionPhase + reason v1alpha1.ConditionReason + } + type operatorConfig struct { + apiReconciler APIIntersectionReconciler + apiLabeler labeler.Labeler + } + type initial struct { + csvs []runtime.Object + clientObjs []runtime.Object + crds []runtime.Object + objs []runtime.Object + apis []runtime.Object + } + type expected struct { + csvStates map[string]csvState + objs []runtime.Object + err map[string]error + } + tests := []struct { + name string + config operatorConfig + initial initial + expected expected + }{ + { + name: "FailForwardEnabled/CSV1/FailedToReplacing", + initial: initial{ + csvs: []runtime.Object{ + csvWithAnnotations(csv("csv1", + namespace, + "1.0.0", + "", + installStrategy("csv1-dep1", nil, nil), + []*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")}, + []*apiextensionsv1.CustomResourceDefinition{}, + v1alpha1.CSVPhaseFailed, + ), addAnnotations(defaultTemplateAnnotations, map[string]string{})), + csv("csv2", + namespace, + "2.0.0", + "csv1", + installStrategy("csv2-dep1", nil, nil), + []*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")}, + []*apiextensionsv1.CustomResourceDefinition{}, + v1alpha1.CSVPhaseNone, + ), + }, + clientObjs: []runtime.Object{ + func() *operatorsv1.OperatorGroup { + og := defaultOperatorGroup.DeepCopy() + og.Spec.UpgradeStrategy = operatorsv1.UpgradeStrategyUnsafeFailForward + return og + }(), + }, + }, + expected: expected{ + csvStates: map[string]csvState{ + "csv1": {exists: true, phase: v1alpha1.CSVPhaseReplacing}, + "csv2": {exists: true, phase: v1alpha1.CSVPhaseNone}, + }, + }, + }, + { + name: "FailForwardDisabled/CSV1/FailedToPending", + initial: initial{ + csvs: []runtime.Object{ + csvWithAnnotations(csv("csv1", + namespace, + "1.0.0", + "", + installStrategy("csv1-dep1", nil, nil), + []*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")}, + []*apiextensionsv1.CustomResourceDefinition{}, + v1alpha1.CSVPhaseFailed, + ), addAnnotations(defaultTemplateAnnotations, map[string]string{})), + csv("csv2", + namespace, + "2.0.0", + "csv1", + installStrategy("csv2-dep1", nil, nil), + []*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")}, + []*apiextensionsv1.CustomResourceDefinition{}, + v1alpha1.CSVPhaseNone, + ), + }, + clientObjs: []runtime.Object{ + func() *operatorsv1.OperatorGroup { + og := defaultOperatorGroup.DeepCopy() + og.Spec.UpgradeStrategy = operatorsv1.UpgradeStrategyDefault + return og + }(), + }, + }, + expected: expected{ + csvStates: map[string]csvState{ + "csv1": {exists: true, phase: v1alpha1.CSVPhasePending}, + "csv2": {exists: true, phase: v1alpha1.CSVPhaseNone}, + }, + }, + }, + { + name: "FailForwardEnabled/ReplacementChain/CSV2/FailedToReplacing", + initial: initial{ + csvs: []runtime.Object{ + csvWithAnnotations(csv("csv1", + namespace, + "1.0.0", + "", + installStrategy("csv1-dep1", nil, nil), + []*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")}, + []*apiextensionsv1.CustomResourceDefinition{}, + v1alpha1.CSVPhaseReplacing, + ), addAnnotations(defaultTemplateAnnotations, map[string]string{})), + csvWithAnnotations(csv("csv2", + namespace, + "2.0.0", + "csv1", + installStrategy("csv2-dep1", nil, nil), + []*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")}, + []*apiextensionsv1.CustomResourceDefinition{}, + v1alpha1.CSVPhaseFailed, + ), addAnnotations(defaultTemplateAnnotations, map[string]string{})), + csv("csv3", + namespace, + "3.0.0", + "csv2", + installStrategy("csv3-dep1", nil, nil), + []*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")}, + []*apiextensionsv1.CustomResourceDefinition{}, + v1alpha1.CSVPhaseNone, + ), + }, + clientObjs: []runtime.Object{ + func() *operatorsv1.OperatorGroup { + og := defaultOperatorGroup.DeepCopy() + og.Spec.UpgradeStrategy = operatorsv1.UpgradeStrategyUnsafeFailForward + return og + }(), + }, + }, + expected: expected{ + csvStates: map[string]csvState{ + "csv1": {exists: true, phase: v1alpha1.CSVPhaseReplacing}, + "csv2": {exists: true, phase: v1alpha1.CSVPhaseReplacing}, + "csv3": {exists: true, phase: v1alpha1.CSVPhaseNone}, + }, + }, + }, + { + name: "FailForwardDisabled/ReplacementChain/CSV2/FailedToPending", + initial: initial{ + csvs: []runtime.Object{ + csvWithAnnotations(csv("csv1", + namespace, + "1.0.0", + "", + installStrategy("csv1-dep1", nil, nil), + []*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")}, + []*apiextensionsv1.CustomResourceDefinition{}, + v1alpha1.CSVPhaseReplacing, + ), addAnnotations(defaultTemplateAnnotations, map[string]string{})), + csvWithAnnotations(csv("csv2", + namespace, + "2.0.0", + "csv1", + installStrategy("csv2-dep1", nil, nil), + []*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")}, + []*apiextensionsv1.CustomResourceDefinition{}, + v1alpha1.CSVPhaseFailed, + ), addAnnotations(defaultTemplateAnnotations, map[string]string{})), + csv("csv3", + namespace, + "3.0.0", + "csv2", + installStrategy("csv3-dep1", nil, nil), + []*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")}, + []*apiextensionsv1.CustomResourceDefinition{}, + v1alpha1.CSVPhaseNone, + ), + }, + clientObjs: []runtime.Object{ + func() *operatorsv1.OperatorGroup { + og := defaultOperatorGroup.DeepCopy() + og.Spec.UpgradeStrategy = operatorsv1.UpgradeStrategyDefault + return og + }(), + }, + }, + expected: expected{ + csvStates: map[string]csvState{ + "csv1": {exists: true, phase: v1alpha1.CSVPhaseReplacing}, + "csv2": {exists: true, phase: v1alpha1.CSVPhasePending}, + "csv3": {exists: true, phase: v1alpha1.CSVPhaseNone}, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Create test operator + ctx, cancel := context.WithCancel(context.TODO()) + defer cancel() + op, err := NewFakeOperator( + ctx, + withNamespaces(namespace, "kube-system"), + withClientObjs(append(tt.initial.csvs, tt.initial.clientObjs...)...), + withK8sObjs(tt.initial.objs...), + withExtObjs(tt.initial.crds...), + withRegObjs(tt.initial.apis...), + withOperatorNamespace(namespace), + withAPIReconciler(tt.config.apiReconciler), + withAPILabeler(tt.config.apiLabeler), + ) + require.NoError(t, err) + + // run csv sync for each CSV + for _, csv := range tt.initial.csvs { + err := op.syncClusterServiceVersion(csv) + expectedErr := tt.expected.err[csv.(*v1alpha1.ClusterServiceVersion).Name] + require.Equal(t, expectedErr, err) + } + + // get csvs in the cluster + outCSVMap := map[string]*v1alpha1.ClusterServiceVersion{} + outCSVs, err := op.client.OperatorsV1alpha1().ClusterServiceVersions(namespace).List(context.TODO(), metav1.ListOptions{}) + require.NoError(t, err) + for _, csv := range outCSVs.Items { + outCSVMap[csv.GetName()] = csv.DeepCopy() + } + + // verify expectations of csvs in cluster + for csvName, csvState := range tt.expected.csvStates { + csv, ok := outCSVMap[csvName] + require.Equal(t, ok, csvState.exists, "%s existence should be %t", csvName, csvState.exists) + if csvState.exists { + if csvState.reason != "" { + require.EqualValues(t, string(csvState.reason), string(csv.Status.Reason), "%s had incorrect condition reason - %v", csvName, csv) + } + require.Equal(t, csvState.phase, csv.Status.Phase) + } + } + + // Verify other objects + if tt.expected.objs != nil { + RequireObjectsInNamespace(t, op.opClient, op.client, namespace, tt.expected.objs) + } + }) + } +} + func TestWebhookCABundleRetrieval(t *testing.T) { logrus.SetLevel(logrus.DebugLevel) namespace := "ns" diff --git a/staging/operator-lifecycle-manager/pkg/controller/registry/resolver/fakes/fake_registry_client_interface.go b/staging/operator-lifecycle-manager/pkg/controller/registry/resolver/fakes/fake_registry_client_interface.go new file mode 100644 index 0000000000..48a508baac --- /dev/null +++ b/staging/operator-lifecycle-manager/pkg/controller/registry/resolver/fakes/fake_registry_client_interface.go @@ -0,0 +1,847 @@ +// Code generated by counterfeiter. DO NOT EDIT. +package fakes + +import ( + "context" + "sync" + "time" + + "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry" + "github.com/operator-framework/operator-registry/pkg/api" + "github.com/operator-framework/operator-registry/pkg/client" +) + +type FakeClientInterface struct { + CloseStub func() error + closeMutex sync.RWMutex + closeArgsForCall []struct { + } + closeReturns struct { + result1 error + } + closeReturnsOnCall map[int]struct { + result1 error + } + FindBundleThatProvidesStub func(context.Context, string, string, string, map[string]struct{}) (*api.Bundle, error) + findBundleThatProvidesMutex sync.RWMutex + findBundleThatProvidesArgsForCall []struct { + arg1 context.Context + arg2 string + arg3 string + arg4 string + arg5 map[string]struct{} + } + findBundleThatProvidesReturns struct { + result1 *api.Bundle + result2 error + } + findBundleThatProvidesReturnsOnCall map[int]struct { + result1 *api.Bundle + result2 error + } + GetBundleStub func(context.Context, string, string, string) (*api.Bundle, error) + getBundleMutex sync.RWMutex + getBundleArgsForCall []struct { + arg1 context.Context + arg2 string + arg3 string + arg4 string + } + getBundleReturns struct { + result1 *api.Bundle + result2 error + } + getBundleReturnsOnCall map[int]struct { + result1 *api.Bundle + result2 error + } + GetBundleInPackageChannelStub func(context.Context, string, string) (*api.Bundle, error) + getBundleInPackageChannelMutex sync.RWMutex + getBundleInPackageChannelArgsForCall []struct { + arg1 context.Context + arg2 string + arg3 string + } + getBundleInPackageChannelReturns struct { + result1 *api.Bundle + result2 error + } + getBundleInPackageChannelReturnsOnCall map[int]struct { + result1 *api.Bundle + result2 error + } + GetBundleThatProvidesStub func(context.Context, string, string, string) (*api.Bundle, error) + getBundleThatProvidesMutex sync.RWMutex + getBundleThatProvidesArgsForCall []struct { + arg1 context.Context + arg2 string + arg3 string + arg4 string + } + getBundleThatProvidesReturns struct { + result1 *api.Bundle + result2 error + } + getBundleThatProvidesReturnsOnCall map[int]struct { + result1 *api.Bundle + result2 error + } + GetLatestChannelEntriesThatProvideStub func(context.Context, string, string, string) (*registry.ChannelEntryIterator, error) + getLatestChannelEntriesThatProvideMutex sync.RWMutex + getLatestChannelEntriesThatProvideArgsForCall []struct { + arg1 context.Context + arg2 string + arg3 string + arg4 string + } + getLatestChannelEntriesThatProvideReturns struct { + result1 *registry.ChannelEntryIterator + result2 error + } + getLatestChannelEntriesThatProvideReturnsOnCall map[int]struct { + result1 *registry.ChannelEntryIterator + result2 error + } + GetPackageStub func(context.Context, string) (*api.Package, error) + getPackageMutex sync.RWMutex + getPackageArgsForCall []struct { + arg1 context.Context + arg2 string + } + getPackageReturns struct { + result1 *api.Package + result2 error + } + getPackageReturnsOnCall map[int]struct { + result1 *api.Package + result2 error + } + GetReplacementBundleInPackageChannelStub func(context.Context, string, string, string) (*api.Bundle, error) + getReplacementBundleInPackageChannelMutex sync.RWMutex + getReplacementBundleInPackageChannelArgsForCall []struct { + arg1 context.Context + arg2 string + arg3 string + arg4 string + } + getReplacementBundleInPackageChannelReturns struct { + result1 *api.Bundle + result2 error + } + getReplacementBundleInPackageChannelReturnsOnCall map[int]struct { + result1 *api.Bundle + result2 error + } + HealthCheckStub func(context.Context, time.Duration) (bool, error) + healthCheckMutex sync.RWMutex + healthCheckArgsForCall []struct { + arg1 context.Context + arg2 time.Duration + } + healthCheckReturns struct { + result1 bool + result2 error + } + healthCheckReturnsOnCall map[int]struct { + result1 bool + result2 error + } + ListBundlesStub func(context.Context) (*client.BundleIterator, error) + listBundlesMutex sync.RWMutex + listBundlesArgsForCall []struct { + arg1 context.Context + } + listBundlesReturns struct { + result1 *client.BundleIterator + result2 error + } + listBundlesReturnsOnCall map[int]struct { + result1 *client.BundleIterator + result2 error + } + invocations map[string][][]interface{} + invocationsMutex sync.RWMutex +} + +func (fake *FakeClientInterface) Close() error { + fake.closeMutex.Lock() + ret, specificReturn := fake.closeReturnsOnCall[len(fake.closeArgsForCall)] + fake.closeArgsForCall = append(fake.closeArgsForCall, struct { + }{}) + fake.recordInvocation("Close", []interface{}{}) + fake.closeMutex.Unlock() + if fake.CloseStub != nil { + return fake.CloseStub() + } + if specificReturn { + return ret.result1 + } + fakeReturns := fake.closeReturns + return fakeReturns.result1 +} + +func (fake *FakeClientInterface) CloseCallCount() int { + fake.closeMutex.RLock() + defer fake.closeMutex.RUnlock() + return len(fake.closeArgsForCall) +} + +func (fake *FakeClientInterface) CloseCalls(stub func() error) { + fake.closeMutex.Lock() + defer fake.closeMutex.Unlock() + fake.CloseStub = stub +} + +func (fake *FakeClientInterface) CloseReturns(result1 error) { + fake.closeMutex.Lock() + defer fake.closeMutex.Unlock() + fake.CloseStub = nil + fake.closeReturns = struct { + result1 error + }{result1} +} + +func (fake *FakeClientInterface) CloseReturnsOnCall(i int, result1 error) { + fake.closeMutex.Lock() + defer fake.closeMutex.Unlock() + fake.CloseStub = nil + if fake.closeReturnsOnCall == nil { + fake.closeReturnsOnCall = make(map[int]struct { + result1 error + }) + } + fake.closeReturnsOnCall[i] = struct { + result1 error + }{result1} +} + +func (fake *FakeClientInterface) FindBundleThatProvides(arg1 context.Context, arg2 string, arg3 string, arg4 string, arg5 map[string]struct{}) (*api.Bundle, error) { + fake.findBundleThatProvidesMutex.Lock() + ret, specificReturn := fake.findBundleThatProvidesReturnsOnCall[len(fake.findBundleThatProvidesArgsForCall)] + fake.findBundleThatProvidesArgsForCall = append(fake.findBundleThatProvidesArgsForCall, struct { + arg1 context.Context + arg2 string + arg3 string + arg4 string + arg5 map[string]struct{} + }{arg1, arg2, arg3, arg4, arg5}) + fake.recordInvocation("FindBundleThatProvides", []interface{}{arg1, arg2, arg3, arg4, arg5}) + fake.findBundleThatProvidesMutex.Unlock() + if fake.FindBundleThatProvidesStub != nil { + return fake.FindBundleThatProvidesStub(arg1, arg2, arg3, arg4, arg5) + } + if specificReturn { + return ret.result1, ret.result2 + } + fakeReturns := fake.findBundleThatProvidesReturns + return fakeReturns.result1, fakeReturns.result2 +} + +func (fake *FakeClientInterface) FindBundleThatProvidesCallCount() int { + fake.findBundleThatProvidesMutex.RLock() + defer fake.findBundleThatProvidesMutex.RUnlock() + return len(fake.findBundleThatProvidesArgsForCall) +} + +func (fake *FakeClientInterface) FindBundleThatProvidesCalls(stub func(context.Context, string, string, string, map[string]struct{}) (*api.Bundle, error)) { + fake.findBundleThatProvidesMutex.Lock() + defer fake.findBundleThatProvidesMutex.Unlock() + fake.FindBundleThatProvidesStub = stub +} + +func (fake *FakeClientInterface) FindBundleThatProvidesArgsForCall(i int) (context.Context, string, string, string, map[string]struct{}) { + fake.findBundleThatProvidesMutex.RLock() + defer fake.findBundleThatProvidesMutex.RUnlock() + argsForCall := fake.findBundleThatProvidesArgsForCall[i] + return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3, argsForCall.arg4, argsForCall.arg5 +} + +func (fake *FakeClientInterface) FindBundleThatProvidesReturns(result1 *api.Bundle, result2 error) { + fake.findBundleThatProvidesMutex.Lock() + defer fake.findBundleThatProvidesMutex.Unlock() + fake.FindBundleThatProvidesStub = nil + fake.findBundleThatProvidesReturns = struct { + result1 *api.Bundle + result2 error + }{result1, result2} +} + +func (fake *FakeClientInterface) FindBundleThatProvidesReturnsOnCall(i int, result1 *api.Bundle, result2 error) { + fake.findBundleThatProvidesMutex.Lock() + defer fake.findBundleThatProvidesMutex.Unlock() + fake.FindBundleThatProvidesStub = nil + if fake.findBundleThatProvidesReturnsOnCall == nil { + fake.findBundleThatProvidesReturnsOnCall = make(map[int]struct { + result1 *api.Bundle + result2 error + }) + } + fake.findBundleThatProvidesReturnsOnCall[i] = struct { + result1 *api.Bundle + result2 error + }{result1, result2} +} + +func (fake *FakeClientInterface) GetBundle(arg1 context.Context, arg2 string, arg3 string, arg4 string) (*api.Bundle, error) { + fake.getBundleMutex.Lock() + ret, specificReturn := fake.getBundleReturnsOnCall[len(fake.getBundleArgsForCall)] + fake.getBundleArgsForCall = append(fake.getBundleArgsForCall, struct { + arg1 context.Context + arg2 string + arg3 string + arg4 string + }{arg1, arg2, arg3, arg4}) + fake.recordInvocation("GetBundle", []interface{}{arg1, arg2, arg3, arg4}) + fake.getBundleMutex.Unlock() + if fake.GetBundleStub != nil { + return fake.GetBundleStub(arg1, arg2, arg3, arg4) + } + if specificReturn { + return ret.result1, ret.result2 + } + fakeReturns := fake.getBundleReturns + return fakeReturns.result1, fakeReturns.result2 +} + +func (fake *FakeClientInterface) GetBundleCallCount() int { + fake.getBundleMutex.RLock() + defer fake.getBundleMutex.RUnlock() + return len(fake.getBundleArgsForCall) +} + +func (fake *FakeClientInterface) GetBundleCalls(stub func(context.Context, string, string, string) (*api.Bundle, error)) { + fake.getBundleMutex.Lock() + defer fake.getBundleMutex.Unlock() + fake.GetBundleStub = stub +} + +func (fake *FakeClientInterface) GetBundleArgsForCall(i int) (context.Context, string, string, string) { + fake.getBundleMutex.RLock() + defer fake.getBundleMutex.RUnlock() + argsForCall := fake.getBundleArgsForCall[i] + return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3, argsForCall.arg4 +} + +func (fake *FakeClientInterface) GetBundleReturns(result1 *api.Bundle, result2 error) { + fake.getBundleMutex.Lock() + defer fake.getBundleMutex.Unlock() + fake.GetBundleStub = nil + fake.getBundleReturns = struct { + result1 *api.Bundle + result2 error + }{result1, result2} +} + +func (fake *FakeClientInterface) GetBundleReturnsOnCall(i int, result1 *api.Bundle, result2 error) { + fake.getBundleMutex.Lock() + defer fake.getBundleMutex.Unlock() + fake.GetBundleStub = nil + if fake.getBundleReturnsOnCall == nil { + fake.getBundleReturnsOnCall = make(map[int]struct { + result1 *api.Bundle + result2 error + }) + } + fake.getBundleReturnsOnCall[i] = struct { + result1 *api.Bundle + result2 error + }{result1, result2} +} + +func (fake *FakeClientInterface) GetBundleInPackageChannel(arg1 context.Context, arg2 string, arg3 string) (*api.Bundle, error) { + fake.getBundleInPackageChannelMutex.Lock() + ret, specificReturn := fake.getBundleInPackageChannelReturnsOnCall[len(fake.getBundleInPackageChannelArgsForCall)] + fake.getBundleInPackageChannelArgsForCall = append(fake.getBundleInPackageChannelArgsForCall, struct { + arg1 context.Context + arg2 string + arg3 string + }{arg1, arg2, arg3}) + fake.recordInvocation("GetBundleInPackageChannel", []interface{}{arg1, arg2, arg3}) + fake.getBundleInPackageChannelMutex.Unlock() + if fake.GetBundleInPackageChannelStub != nil { + return fake.GetBundleInPackageChannelStub(arg1, arg2, arg3) + } + if specificReturn { + return ret.result1, ret.result2 + } + fakeReturns := fake.getBundleInPackageChannelReturns + return fakeReturns.result1, fakeReturns.result2 +} + +func (fake *FakeClientInterface) GetBundleInPackageChannelCallCount() int { + fake.getBundleInPackageChannelMutex.RLock() + defer fake.getBundleInPackageChannelMutex.RUnlock() + return len(fake.getBundleInPackageChannelArgsForCall) +} + +func (fake *FakeClientInterface) GetBundleInPackageChannelCalls(stub func(context.Context, string, string) (*api.Bundle, error)) { + fake.getBundleInPackageChannelMutex.Lock() + defer fake.getBundleInPackageChannelMutex.Unlock() + fake.GetBundleInPackageChannelStub = stub +} + +func (fake *FakeClientInterface) GetBundleInPackageChannelArgsForCall(i int) (context.Context, string, string) { + fake.getBundleInPackageChannelMutex.RLock() + defer fake.getBundleInPackageChannelMutex.RUnlock() + argsForCall := fake.getBundleInPackageChannelArgsForCall[i] + return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3 +} + +func (fake *FakeClientInterface) GetBundleInPackageChannelReturns(result1 *api.Bundle, result2 error) { + fake.getBundleInPackageChannelMutex.Lock() + defer fake.getBundleInPackageChannelMutex.Unlock() + fake.GetBundleInPackageChannelStub = nil + fake.getBundleInPackageChannelReturns = struct { + result1 *api.Bundle + result2 error + }{result1, result2} +} + +func (fake *FakeClientInterface) GetBundleInPackageChannelReturnsOnCall(i int, result1 *api.Bundle, result2 error) { + fake.getBundleInPackageChannelMutex.Lock() + defer fake.getBundleInPackageChannelMutex.Unlock() + fake.GetBundleInPackageChannelStub = nil + if fake.getBundleInPackageChannelReturnsOnCall == nil { + fake.getBundleInPackageChannelReturnsOnCall = make(map[int]struct { + result1 *api.Bundle + result2 error + }) + } + fake.getBundleInPackageChannelReturnsOnCall[i] = struct { + result1 *api.Bundle + result2 error + }{result1, result2} +} + +func (fake *FakeClientInterface) GetBundleThatProvides(arg1 context.Context, arg2 string, arg3 string, arg4 string) (*api.Bundle, error) { + fake.getBundleThatProvidesMutex.Lock() + ret, specificReturn := fake.getBundleThatProvidesReturnsOnCall[len(fake.getBundleThatProvidesArgsForCall)] + fake.getBundleThatProvidesArgsForCall = append(fake.getBundleThatProvidesArgsForCall, struct { + arg1 context.Context + arg2 string + arg3 string + arg4 string + }{arg1, arg2, arg3, arg4}) + fake.recordInvocation("GetBundleThatProvides", []interface{}{arg1, arg2, arg3, arg4}) + fake.getBundleThatProvidesMutex.Unlock() + if fake.GetBundleThatProvidesStub != nil { + return fake.GetBundleThatProvidesStub(arg1, arg2, arg3, arg4) + } + if specificReturn { + return ret.result1, ret.result2 + } + fakeReturns := fake.getBundleThatProvidesReturns + return fakeReturns.result1, fakeReturns.result2 +} + +func (fake *FakeClientInterface) GetBundleThatProvidesCallCount() int { + fake.getBundleThatProvidesMutex.RLock() + defer fake.getBundleThatProvidesMutex.RUnlock() + return len(fake.getBundleThatProvidesArgsForCall) +} + +func (fake *FakeClientInterface) GetBundleThatProvidesCalls(stub func(context.Context, string, string, string) (*api.Bundle, error)) { + fake.getBundleThatProvidesMutex.Lock() + defer fake.getBundleThatProvidesMutex.Unlock() + fake.GetBundleThatProvidesStub = stub +} + +func (fake *FakeClientInterface) GetBundleThatProvidesArgsForCall(i int) (context.Context, string, string, string) { + fake.getBundleThatProvidesMutex.RLock() + defer fake.getBundleThatProvidesMutex.RUnlock() + argsForCall := fake.getBundleThatProvidesArgsForCall[i] + return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3, argsForCall.arg4 +} + +func (fake *FakeClientInterface) GetBundleThatProvidesReturns(result1 *api.Bundle, result2 error) { + fake.getBundleThatProvidesMutex.Lock() + defer fake.getBundleThatProvidesMutex.Unlock() + fake.GetBundleThatProvidesStub = nil + fake.getBundleThatProvidesReturns = struct { + result1 *api.Bundle + result2 error + }{result1, result2} +} + +func (fake *FakeClientInterface) GetBundleThatProvidesReturnsOnCall(i int, result1 *api.Bundle, result2 error) { + fake.getBundleThatProvidesMutex.Lock() + defer fake.getBundleThatProvidesMutex.Unlock() + fake.GetBundleThatProvidesStub = nil + if fake.getBundleThatProvidesReturnsOnCall == nil { + fake.getBundleThatProvidesReturnsOnCall = make(map[int]struct { + result1 *api.Bundle + result2 error + }) + } + fake.getBundleThatProvidesReturnsOnCall[i] = struct { + result1 *api.Bundle + result2 error + }{result1, result2} +} + +func (fake *FakeClientInterface) GetLatestChannelEntriesThatProvide(arg1 context.Context, arg2 string, arg3 string, arg4 string) (*registry.ChannelEntryIterator, error) { + fake.getLatestChannelEntriesThatProvideMutex.Lock() + ret, specificReturn := fake.getLatestChannelEntriesThatProvideReturnsOnCall[len(fake.getLatestChannelEntriesThatProvideArgsForCall)] + fake.getLatestChannelEntriesThatProvideArgsForCall = append(fake.getLatestChannelEntriesThatProvideArgsForCall, struct { + arg1 context.Context + arg2 string + arg3 string + arg4 string + }{arg1, arg2, arg3, arg4}) + fake.recordInvocation("GetLatestChannelEntriesThatProvide", []interface{}{arg1, arg2, arg3, arg4}) + fake.getLatestChannelEntriesThatProvideMutex.Unlock() + if fake.GetLatestChannelEntriesThatProvideStub != nil { + return fake.GetLatestChannelEntriesThatProvideStub(arg1, arg2, arg3, arg4) + } + if specificReturn { + return ret.result1, ret.result2 + } + fakeReturns := fake.getLatestChannelEntriesThatProvideReturns + return fakeReturns.result1, fakeReturns.result2 +} + +func (fake *FakeClientInterface) GetLatestChannelEntriesThatProvideCallCount() int { + fake.getLatestChannelEntriesThatProvideMutex.RLock() + defer fake.getLatestChannelEntriesThatProvideMutex.RUnlock() + return len(fake.getLatestChannelEntriesThatProvideArgsForCall) +} + +func (fake *FakeClientInterface) GetLatestChannelEntriesThatProvideCalls(stub func(context.Context, string, string, string) (*registry.ChannelEntryIterator, error)) { + fake.getLatestChannelEntriesThatProvideMutex.Lock() + defer fake.getLatestChannelEntriesThatProvideMutex.Unlock() + fake.GetLatestChannelEntriesThatProvideStub = stub +} + +func (fake *FakeClientInterface) GetLatestChannelEntriesThatProvideArgsForCall(i int) (context.Context, string, string, string) { + fake.getLatestChannelEntriesThatProvideMutex.RLock() + defer fake.getLatestChannelEntriesThatProvideMutex.RUnlock() + argsForCall := fake.getLatestChannelEntriesThatProvideArgsForCall[i] + return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3, argsForCall.arg4 +} + +func (fake *FakeClientInterface) GetLatestChannelEntriesThatProvideReturns(result1 *registry.ChannelEntryIterator, result2 error) { + fake.getLatestChannelEntriesThatProvideMutex.Lock() + defer fake.getLatestChannelEntriesThatProvideMutex.Unlock() + fake.GetLatestChannelEntriesThatProvideStub = nil + fake.getLatestChannelEntriesThatProvideReturns = struct { + result1 *registry.ChannelEntryIterator + result2 error + }{result1, result2} +} + +func (fake *FakeClientInterface) GetLatestChannelEntriesThatProvideReturnsOnCall(i int, result1 *registry.ChannelEntryIterator, result2 error) { + fake.getLatestChannelEntriesThatProvideMutex.Lock() + defer fake.getLatestChannelEntriesThatProvideMutex.Unlock() + fake.GetLatestChannelEntriesThatProvideStub = nil + if fake.getLatestChannelEntriesThatProvideReturnsOnCall == nil { + fake.getLatestChannelEntriesThatProvideReturnsOnCall = make(map[int]struct { + result1 *registry.ChannelEntryIterator + result2 error + }) + } + fake.getLatestChannelEntriesThatProvideReturnsOnCall[i] = struct { + result1 *registry.ChannelEntryIterator + result2 error + }{result1, result2} +} + +func (fake *FakeClientInterface) GetPackage(arg1 context.Context, arg2 string) (*api.Package, error) { + fake.getPackageMutex.Lock() + ret, specificReturn := fake.getPackageReturnsOnCall[len(fake.getPackageArgsForCall)] + fake.getPackageArgsForCall = append(fake.getPackageArgsForCall, struct { + arg1 context.Context + arg2 string + }{arg1, arg2}) + fake.recordInvocation("GetPackage", []interface{}{arg1, arg2}) + fake.getPackageMutex.Unlock() + if fake.GetPackageStub != nil { + return fake.GetPackageStub(arg1, arg2) + } + if specificReturn { + return ret.result1, ret.result2 + } + fakeReturns := fake.getPackageReturns + return fakeReturns.result1, fakeReturns.result2 +} + +func (fake *FakeClientInterface) GetPackageCallCount() int { + fake.getPackageMutex.RLock() + defer fake.getPackageMutex.RUnlock() + return len(fake.getPackageArgsForCall) +} + +func (fake *FakeClientInterface) GetPackageCalls(stub func(context.Context, string) (*api.Package, error)) { + fake.getPackageMutex.Lock() + defer fake.getPackageMutex.Unlock() + fake.GetPackageStub = stub +} + +func (fake *FakeClientInterface) GetPackageArgsForCall(i int) (context.Context, string) { + fake.getPackageMutex.RLock() + defer fake.getPackageMutex.RUnlock() + argsForCall := fake.getPackageArgsForCall[i] + return argsForCall.arg1, argsForCall.arg2 +} + +func (fake *FakeClientInterface) GetPackageReturns(result1 *api.Package, result2 error) { + fake.getPackageMutex.Lock() + defer fake.getPackageMutex.Unlock() + fake.GetPackageStub = nil + fake.getPackageReturns = struct { + result1 *api.Package + result2 error + }{result1, result2} +} + +func (fake *FakeClientInterface) GetPackageReturnsOnCall(i int, result1 *api.Package, result2 error) { + fake.getPackageMutex.Lock() + defer fake.getPackageMutex.Unlock() + fake.GetPackageStub = nil + if fake.getPackageReturnsOnCall == nil { + fake.getPackageReturnsOnCall = make(map[int]struct { + result1 *api.Package + result2 error + }) + } + fake.getPackageReturnsOnCall[i] = struct { + result1 *api.Package + result2 error + }{result1, result2} +} + +func (fake *FakeClientInterface) GetReplacementBundleInPackageChannel(arg1 context.Context, arg2 string, arg3 string, arg4 string) (*api.Bundle, error) { + fake.getReplacementBundleInPackageChannelMutex.Lock() + ret, specificReturn := fake.getReplacementBundleInPackageChannelReturnsOnCall[len(fake.getReplacementBundleInPackageChannelArgsForCall)] + fake.getReplacementBundleInPackageChannelArgsForCall = append(fake.getReplacementBundleInPackageChannelArgsForCall, struct { + arg1 context.Context + arg2 string + arg3 string + arg4 string + }{arg1, arg2, arg3, arg4}) + fake.recordInvocation("GetReplacementBundleInPackageChannel", []interface{}{arg1, arg2, arg3, arg4}) + fake.getReplacementBundleInPackageChannelMutex.Unlock() + if fake.GetReplacementBundleInPackageChannelStub != nil { + return fake.GetReplacementBundleInPackageChannelStub(arg1, arg2, arg3, arg4) + } + if specificReturn { + return ret.result1, ret.result2 + } + fakeReturns := fake.getReplacementBundleInPackageChannelReturns + return fakeReturns.result1, fakeReturns.result2 +} + +func (fake *FakeClientInterface) GetReplacementBundleInPackageChannelCallCount() int { + fake.getReplacementBundleInPackageChannelMutex.RLock() + defer fake.getReplacementBundleInPackageChannelMutex.RUnlock() + return len(fake.getReplacementBundleInPackageChannelArgsForCall) +} + +func (fake *FakeClientInterface) GetReplacementBundleInPackageChannelCalls(stub func(context.Context, string, string, string) (*api.Bundle, error)) { + fake.getReplacementBundleInPackageChannelMutex.Lock() + defer fake.getReplacementBundleInPackageChannelMutex.Unlock() + fake.GetReplacementBundleInPackageChannelStub = stub +} + +func (fake *FakeClientInterface) GetReplacementBundleInPackageChannelArgsForCall(i int) (context.Context, string, string, string) { + fake.getReplacementBundleInPackageChannelMutex.RLock() + defer fake.getReplacementBundleInPackageChannelMutex.RUnlock() + argsForCall := fake.getReplacementBundleInPackageChannelArgsForCall[i] + return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3, argsForCall.arg4 +} + +func (fake *FakeClientInterface) GetReplacementBundleInPackageChannelReturns(result1 *api.Bundle, result2 error) { + fake.getReplacementBundleInPackageChannelMutex.Lock() + defer fake.getReplacementBundleInPackageChannelMutex.Unlock() + fake.GetReplacementBundleInPackageChannelStub = nil + fake.getReplacementBundleInPackageChannelReturns = struct { + result1 *api.Bundle + result2 error + }{result1, result2} +} + +func (fake *FakeClientInterface) GetReplacementBundleInPackageChannelReturnsOnCall(i int, result1 *api.Bundle, result2 error) { + fake.getReplacementBundleInPackageChannelMutex.Lock() + defer fake.getReplacementBundleInPackageChannelMutex.Unlock() + fake.GetReplacementBundleInPackageChannelStub = nil + if fake.getReplacementBundleInPackageChannelReturnsOnCall == nil { + fake.getReplacementBundleInPackageChannelReturnsOnCall = make(map[int]struct { + result1 *api.Bundle + result2 error + }) + } + fake.getReplacementBundleInPackageChannelReturnsOnCall[i] = struct { + result1 *api.Bundle + result2 error + }{result1, result2} +} + +func (fake *FakeClientInterface) HealthCheck(arg1 context.Context, arg2 time.Duration) (bool, error) { + fake.healthCheckMutex.Lock() + ret, specificReturn := fake.healthCheckReturnsOnCall[len(fake.healthCheckArgsForCall)] + fake.healthCheckArgsForCall = append(fake.healthCheckArgsForCall, struct { + arg1 context.Context + arg2 time.Duration + }{arg1, arg2}) + fake.recordInvocation("HealthCheck", []interface{}{arg1, arg2}) + fake.healthCheckMutex.Unlock() + if fake.HealthCheckStub != nil { + return fake.HealthCheckStub(arg1, arg2) + } + if specificReturn { + return ret.result1, ret.result2 + } + fakeReturns := fake.healthCheckReturns + return fakeReturns.result1, fakeReturns.result2 +} + +func (fake *FakeClientInterface) HealthCheckCallCount() int { + fake.healthCheckMutex.RLock() + defer fake.healthCheckMutex.RUnlock() + return len(fake.healthCheckArgsForCall) +} + +func (fake *FakeClientInterface) HealthCheckCalls(stub func(context.Context, time.Duration) (bool, error)) { + fake.healthCheckMutex.Lock() + defer fake.healthCheckMutex.Unlock() + fake.HealthCheckStub = stub +} + +func (fake *FakeClientInterface) HealthCheckArgsForCall(i int) (context.Context, time.Duration) { + fake.healthCheckMutex.RLock() + defer fake.healthCheckMutex.RUnlock() + argsForCall := fake.healthCheckArgsForCall[i] + return argsForCall.arg1, argsForCall.arg2 +} + +func (fake *FakeClientInterface) HealthCheckReturns(result1 bool, result2 error) { + fake.healthCheckMutex.Lock() + defer fake.healthCheckMutex.Unlock() + fake.HealthCheckStub = nil + fake.healthCheckReturns = struct { + result1 bool + result2 error + }{result1, result2} +} + +func (fake *FakeClientInterface) HealthCheckReturnsOnCall(i int, result1 bool, result2 error) { + fake.healthCheckMutex.Lock() + defer fake.healthCheckMutex.Unlock() + fake.HealthCheckStub = nil + if fake.healthCheckReturnsOnCall == nil { + fake.healthCheckReturnsOnCall = make(map[int]struct { + result1 bool + result2 error + }) + } + fake.healthCheckReturnsOnCall[i] = struct { + result1 bool + result2 error + }{result1, result2} +} + +func (fake *FakeClientInterface) ListBundles(arg1 context.Context) (*client.BundleIterator, error) { + fake.listBundlesMutex.Lock() + ret, specificReturn := fake.listBundlesReturnsOnCall[len(fake.listBundlesArgsForCall)] + fake.listBundlesArgsForCall = append(fake.listBundlesArgsForCall, struct { + arg1 context.Context + }{arg1}) + fake.recordInvocation("ListBundles", []interface{}{arg1}) + fake.listBundlesMutex.Unlock() + if fake.ListBundlesStub != nil { + return fake.ListBundlesStub(arg1) + } + if specificReturn { + return ret.result1, ret.result2 + } + fakeReturns := fake.listBundlesReturns + return fakeReturns.result1, fakeReturns.result2 +} + +func (fake *FakeClientInterface) ListBundlesCallCount() int { + fake.listBundlesMutex.RLock() + defer fake.listBundlesMutex.RUnlock() + return len(fake.listBundlesArgsForCall) +} + +func (fake *FakeClientInterface) ListBundlesCalls(stub func(context.Context) (*client.BundleIterator, error)) { + fake.listBundlesMutex.Lock() + defer fake.listBundlesMutex.Unlock() + fake.ListBundlesStub = stub +} + +func (fake *FakeClientInterface) ListBundlesArgsForCall(i int) context.Context { + fake.listBundlesMutex.RLock() + defer fake.listBundlesMutex.RUnlock() + argsForCall := fake.listBundlesArgsForCall[i] + return argsForCall.arg1 +} + +func (fake *FakeClientInterface) ListBundlesReturns(result1 *client.BundleIterator, result2 error) { + fake.listBundlesMutex.Lock() + defer fake.listBundlesMutex.Unlock() + fake.ListBundlesStub = nil + fake.listBundlesReturns = struct { + result1 *client.BundleIterator + result2 error + }{result1, result2} +} + +func (fake *FakeClientInterface) ListBundlesReturnsOnCall(i int, result1 *client.BundleIterator, result2 error) { + fake.listBundlesMutex.Lock() + defer fake.listBundlesMutex.Unlock() + fake.ListBundlesStub = nil + if fake.listBundlesReturnsOnCall == nil { + fake.listBundlesReturnsOnCall = make(map[int]struct { + result1 *client.BundleIterator + result2 error + }) + } + fake.listBundlesReturnsOnCall[i] = struct { + result1 *client.BundleIterator + result2 error + }{result1, result2} +} + +func (fake *FakeClientInterface) Invocations() map[string][][]interface{} { + fake.invocationsMutex.RLock() + defer fake.invocationsMutex.RUnlock() + fake.closeMutex.RLock() + defer fake.closeMutex.RUnlock() + fake.findBundleThatProvidesMutex.RLock() + defer fake.findBundleThatProvidesMutex.RUnlock() + fake.getBundleMutex.RLock() + defer fake.getBundleMutex.RUnlock() + fake.getBundleInPackageChannelMutex.RLock() + defer fake.getBundleInPackageChannelMutex.RUnlock() + fake.getBundleThatProvidesMutex.RLock() + defer fake.getBundleThatProvidesMutex.RUnlock() + fake.getLatestChannelEntriesThatProvideMutex.RLock() + defer fake.getLatestChannelEntriesThatProvideMutex.RUnlock() + fake.getPackageMutex.RLock() + defer fake.getPackageMutex.RUnlock() + fake.getReplacementBundleInPackageChannelMutex.RLock() + defer fake.getReplacementBundleInPackageChannelMutex.RUnlock() + fake.healthCheckMutex.RLock() + defer fake.healthCheckMutex.RUnlock() + fake.listBundlesMutex.RLock() + defer fake.listBundlesMutex.RUnlock() + copiedInvocations := map[string][][]interface{}{} + for key, value := range fake.invocations { + copiedInvocations[key] = value + } + return copiedInvocations +} + +func (fake *FakeClientInterface) recordInvocation(key string, args []interface{}) { + fake.invocationsMutex.Lock() + defer fake.invocationsMutex.Unlock() + if fake.invocations == nil { + fake.invocations = map[string][][]interface{}{} + } + if fake.invocations[key] == nil { + fake.invocations[key] = [][]interface{}{} + } + fake.invocations[key] = append(fake.invocations[key], args) +} + +var _ registry.ClientInterface = new(FakeClientInterface) diff --git a/staging/operator-lifecycle-manager/pkg/controller/registry/resolver/instrumented_resolver.go b/staging/operator-lifecycle-manager/pkg/controller/registry/resolver/instrumented_resolver.go index d55d67c4fc..83e4cf9206 100644 --- a/staging/operator-lifecycle-manager/pkg/controller/registry/resolver/instrumented_resolver.go +++ b/staging/operator-lifecycle-manager/pkg/controller/registry/resolver/instrumented_resolver.go @@ -22,9 +22,9 @@ func NewInstrumentedResolver(resolver StepResolver, successMetricsEmitter, failu } } -func (ir *InstrumentedResolver) ResolveSteps(namespace string) ([]*v1alpha1.Step, []v1alpha1.BundleLookup, []*v1alpha1.Subscription, error) { +func (ir *InstrumentedResolver) ResolveSteps(namespace string, failForwardEnabled bool) ([]*v1alpha1.Step, []v1alpha1.BundleLookup, []*v1alpha1.Subscription, error) { start := time.Now() - steps, lookups, subs, err := ir.resolver.ResolveSteps(namespace) + steps, lookups, subs, err := ir.resolver.ResolveSteps(namespace, failForwardEnabled) if err != nil { ir.failureMetricsEmitter(time.Since(start)) } else { diff --git a/staging/operator-lifecycle-manager/pkg/controller/registry/resolver/instrumented_resolver_test.go b/staging/operator-lifecycle-manager/pkg/controller/registry/resolver/instrumented_resolver_test.go index 47bf8d25f1..8179a78168 100644 --- a/staging/operator-lifecycle-manager/pkg/controller/registry/resolver/instrumented_resolver_test.go +++ b/staging/operator-lifecycle-manager/pkg/controller/registry/resolver/instrumented_resolver_test.go @@ -17,11 +17,11 @@ const ( type fakeResolverWithError struct{} type fakeResolverWithoutError struct{} -func (r *fakeResolverWithError) ResolveSteps(namespace string) ([]*v1alpha1.Step, []v1alpha1.BundleLookup, []*v1alpha1.Subscription, error) { +func (r *fakeResolverWithError) ResolveSteps(namespace string, failForwardEnabled bool) ([]*v1alpha1.Step, []v1alpha1.BundleLookup, []*v1alpha1.Subscription, error) { return nil, nil, nil, errors.New("Fake error") } -func (r *fakeResolverWithoutError) ResolveSteps(namespace string) ([]*v1alpha1.Step, []v1alpha1.BundleLookup, []*v1alpha1.Subscription, error) { +func (r *fakeResolverWithoutError) ResolveSteps(namespace string, failForwardEnabled bool) ([]*v1alpha1.Step, []v1alpha1.BundleLookup, []*v1alpha1.Subscription, error) { return nil, nil, nil, nil } @@ -45,7 +45,7 @@ func TestInstrumentedResolverFailure(t *testing.T) { } instrumentedResolver := NewInstrumentedResolver(newFakeResolverWithError(), changeToSuccess, changeToFailure) - instrumentedResolver.ResolveSteps("") + instrumentedResolver.ResolveSteps("", false) require.Equal(t, len(result), 1) // check that only one call was made to a change function require.Equal(t, result[0], failure) // check that the call was made to changeToFailure function } @@ -62,7 +62,7 @@ func TestInstrumentedResolverSuccess(t *testing.T) { } instrumentedResolver := NewInstrumentedResolver(newFakeResolverWithoutError(), changeToSuccess, changeToFailure) - instrumentedResolver.ResolveSteps("") + instrumentedResolver.ResolveSteps("", false) require.Equal(t, len(result), 1) // check that only one call was made to a change function require.Equal(t, result[0], success) // check that the call was made to changeToSuccess function } 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 c19aba9f26..29b28ca34f 100644 --- a/staging/operator-lifecycle-manager/pkg/controller/registry/resolver/resolver.go +++ b/staging/operator-lifecycle-manager/pkg/controller/registry/resolver/resolver.go @@ -55,7 +55,7 @@ func (w *debugWriter) Write(b []byte) (int, error) { return n, nil } -func (r *Resolver) Resolve(namespaces []string, subs []*v1alpha1.Subscription) ([]*cache.Entry, error) { +func (r *Resolver) Resolve(namespaces []string, subs []*v1alpha1.Subscription, existingEntryPredicates ...cache.Predicate) ([]*cache.Entry, error) { var errs []error variables := make(map[solver.Identifier]solver.Variable) @@ -72,7 +72,8 @@ func (r *Resolver) Resolve(namespaces []string, subs []*v1alpha1.Subscription) ( } preferredNamespace := namespaces[0] - _, existingVariables, err := r.getBundleVariables(preferredNamespace, namespacedCache.Catalog(cache.NewVirtualSourceKey(preferredNamespace)).Find(cache.True()), namespacedCache, visited) + existingEntryPredicates = append(existingEntryPredicates, cache.True()) + _, existingVariables, err := r.getBundleVariables(preferredNamespace, namespacedCache.Catalog(cache.NewVirtualSourceKey(preferredNamespace)).Find(existingEntryPredicates...), namespacedCache, visited) if err != nil { return nil, err } 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 c4ff6f4d84..9a80fe770c 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 @@ -191,6 +191,85 @@ func TestSolveOperators_WithSystemConstraints(t *testing.T) { } } +func WithInstalledCSV(sub *v1alpha1.Subscription, csvName string) *v1alpha1.Subscription { + sub.Status.InstalledCSV = csvName + return sub +} + +func TestSolveOperators_WithFailForward(t *testing.T) { + const namespace = "test-namespace" + catalog := cache.SourceKey{Name: "test-catalog", Namespace: namespace} + + packageASubV2 := newSub(namespace, "packageA", "alpha", catalog) + APISet := cache.APISet{opregistry.APIKey{Group: "g", Version: "v", Kind: "k", Plural: "ks"}: struct{}{}} + + // packageA provides an API + packageAV1 := genEntry("packageA.v1", "0.0.1", "", "packageA", "alpha", catalog.Name, catalog.Namespace, nil, APISet, nil, "", false) + packageAV2 := genEntry("packageA.v2", "0.0.2", "packageA.v1", "packageA", "alpha", catalog.Name, catalog.Namespace, nil, APISet, nil, "", false) + packageAV3 := genEntry("packageA.v3", "0.0.3", "packageA.v2", "packageA", "alpha", catalog.Name, catalog.Namespace, nil, APISet, nil, "", false) + + existingPackageAV1 := existingOperator(namespace, "packageA.v1", "packageA", "alpha", "", APISet, nil, nil, nil) + existingPackageAV2 := existingOperator(namespace, "packageA.v2", "packageA", "alpha", "packageA.v1", APISet, nil, nil, nil) + + testCases := []struct { + title string + expectedOperators []*cache.Entry + csvs []*v1alpha1.ClusterServiceVersion + subs []*v1alpha1.Subscription + snapshotEntries []*cache.Entry + failForwardPredicates []cache.Predicate + err string + }{ + { + title: "Resolver fails if v1 and v2 provide the same APIs and v1 is not omitted from the resolver", + snapshotEntries: []*cache.Entry{packageAV1, packageAV2}, + expectedOperators: nil, + csvs: []*v1alpha1.ClusterServiceVersion{existingPackageAV1, existingPackageAV2}, + subs: []*v1alpha1.Subscription{WithInstalledCSV(packageASubV2, existingPackageAV2.Name)}, + err: "provide k (g/v)", + }, + { + title: "Resolver succeeds if v1 and v2 provide the same APIs and v1 is omitted from the resolver", + snapshotEntries: []*cache.Entry{packageAV1, packageAV2}, + expectedOperators: nil, + csvs: []*v1alpha1.ClusterServiceVersion{existingPackageAV1, existingPackageAV2}, + subs: []*v1alpha1.Subscription{WithInstalledCSV(packageASubV2, existingPackageAV2.Name)}, + failForwardPredicates: []cache.Predicate{cache.Not(cache.CSVNamePredicate("packageA.v1"))}, + err: "", + }, + { + title: "Resolver succeeds if v1 and v2 provide the same APIs, v1 is omitted from the resolver, and an upgrade for v2 exists", + snapshotEntries: []*cache.Entry{packageAV1, packageAV2, packageAV3}, + expectedOperators: []*cache.Entry{packageAV3}, + csvs: []*v1alpha1.ClusterServiceVersion{existingPackageAV1, existingPackageAV2}, + subs: []*v1alpha1.Subscription{WithInstalledCSV(packageASubV2, existingPackageAV2.Name)}, + failForwardPredicates: []cache.Predicate{cache.Not(cache.CSVNamePredicate("packageA.v1"))}, + err: "", + }, + } + + for _, testCase := range testCases { + resolver := Resolver{ + cache: cache.New(cache.StaticSourceProvider{ + catalog: &cache.Snapshot{ + Entries: testCase.snapshotEntries, + }, + cache.NewVirtualSourceKey(namespace): csvSnapshotOrPanic(namespace, testCase.subs, testCase.csvs...), + }), + log: logrus.New(), + } + operators, err := resolver.Resolve([]string{namespace}, testCase.subs, testCase.failForwardPredicates...) + + if testCase.err != "" { + require.Error(t, err) + require.Containsf(t, err.Error(), testCase.err, "Test %s failed", testCase.title) + } else { + require.NoErrorf(t, err, "Test %s failed", testCase.title) + } + require.ElementsMatch(t, testCase.expectedOperators, operators, "Test %s failed", testCase.title) + } +} + func TestDisjointChannelGraph(t *testing.T) { const namespace = "test-namespace" catalog := cache.SourceKey{Name: "test-catalog", Namespace: namespace} 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 aedaaa49db..e67d27e000 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 @@ -9,6 +9,8 @@ import ( corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/selection" "github.com/operator-framework/api/pkg/operators/v1alpha1" "github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/clientset/versioned" @@ -27,7 +29,7 @@ const ( var initHooks []stepResolverInitHook type StepResolver interface { - ResolveSteps(namespace string) ([]*v1alpha1.Step, []v1alpha1.BundleLookup, []*v1alpha1.Subscription, error) + ResolveSteps(namespace string, failForwardEnabled bool) ([]*v1alpha1.Step, []v1alpha1.BundleLookup, []*v1alpha1.Subscription, error) } type OperatorStepResolver struct { @@ -84,14 +86,125 @@ func NewOperatorStepResolver(lister operatorlister.OperatorLister, client versio return stepResolver } -func (r *OperatorStepResolver) ResolveSteps(namespace string) ([]*v1alpha1.Step, []v1alpha1.BundleLookup, []*v1alpha1.Subscription, error) { +type walkOption func(csv *v1alpha1.ClusterServiceVersion) error + +func WithCSVPhase(phase v1alpha1.ClusterServiceVersionPhase) walkOption { + return func(csv *v1alpha1.ClusterServiceVersion) error { + if csv == nil || csv.Status.Phase != phase { + return fmt.Errorf("csv %s/%s in phase %s instead of %s", csv.GetNamespace(), csv.GetName(), csv.Status.Phase, phase) + } + return nil + } +} + +func WithUniqueCSVs() walkOption { + visited := map[string]struct{}{} + return func(csv *v1alpha1.ClusterServiceVersion) error { + // Check if we have visited the CSV before + if _, ok := visited[csv.GetName()]; ok { + return fmt.Errorf("infinite replacement chain detected") + } + + visited[csv.GetName()] = struct{}{} + return nil + } +} + +// walkReplacementChain walks along the chain of clusterServiceVersions being replaced and returns +// the last clusterServiceVersions in the replacement chain. An error is returned if any of the +// clusterServiceVersions before the last is not in the replaces phase or if an infinite replacement +// chain is detected. +func WalkReplacementChain(csv *v1alpha1.ClusterServiceVersion, csvToReplacement map[string]*v1alpha1.ClusterServiceVersion, options ...walkOption) (*v1alpha1.ClusterServiceVersion, error) { + if csv == nil { + return nil, fmt.Errorf("csv cannot be nil") + } + + for { + // Check if there is a CSV that replaces this CSVs + next, ok := csvToReplacement[csv.GetName()] + if !ok { + break + } + + // Check walk options + for _, o := range options { + if err := o(csv); err != nil { + return nil, err + } + } + + // Move along replacement chain. + csv = next + } + return csv, nil +} + +// isReplacementChainThatEndsInFailure returns true if the last CSV in the chain is in the failed phase and all other +// CSVs are in the replacing phase. +func isReplacementChainThatEndsInFailure(csv *v1alpha1.ClusterServiceVersion, csvToReplacement map[string]*v1alpha1.ClusterServiceVersion) (bool, error) { + lastCSV, err := WalkReplacementChain(csv, csvToReplacement, WithCSVPhase(v1alpha1.CSVPhaseReplacing), WithUniqueCSVs()) + if err != nil { + return false, err + } + return (lastCSV != nil && lastCSV.Status.Phase == v1alpha1.CSVPhaseFailed), nil +} + +// ReplacementMapping takes a list of CSVs and returns a map that maps a CSV's name to the CSV that replaces it. +func ReplacementMapping(csvs []*v1alpha1.ClusterServiceVersion) map[string]*v1alpha1.ClusterServiceVersion { + replacementMapping := map[string]*v1alpha1.ClusterServiceVersion{} + for _, csv := range csvs { + if csv.Spec.Replaces != "" { + replacementMapping[csv.Spec.Replaces] = csv + } + } + return replacementMapping +} + +func (r *OperatorStepResolver) cachePredicates(namespace string) ([]cache.Predicate, error) { + nonCopiedCSVRequirement, err := labels.NewRequirement(v1alpha1.CopiedLabelKey, selection.DoesNotExist, []string{}) + if err != nil { + return nil, err + } + + csvs, err := r.csvLister.ClusterServiceVersions(namespace).List(labels.NewSelector().Add(*nonCopiedCSVRequirement)) + if err != nil { + return nil, err + } + + predicates := []cache.Predicate{} + for i := range csvs { + replacementChainEndsInFailure, err := isReplacementChainThatEndsInFailure(csvs[i], ReplacementMapping(csvs)) + if err != nil { + return nil, err + } + if csvs[i].Status.Phase == v1alpha1.CSVPhaseReplacing && replacementChainEndsInFailure { + predicates = append(predicates, cache.Not(cache.CSVNamePredicate(csvs[i].GetName()))) + } + } + + return predicates, nil +} + +func (r *OperatorStepResolver) ResolveSteps(namespace string, failForwardEnabled bool) ([]*v1alpha1.Step, []v1alpha1.BundleLookup, []*v1alpha1.Subscription, error) { subs, err := r.listSubscriptions(namespace) if err != nil { return nil, nil, nil, err } + // The resolver considers the initial set of CSVs in the namespace by their appearance + // in the catalog cache. In order to support "fail forward" upgrades, we need to omit + // CSVs that are actively being replaced from this initial set of operators. The + // predicates defined here will omit these replacing CSVs from the set. + cachePredicates := []cache.Predicate{} + if failForwardEnabled { + cachePredicates, err = r.cachePredicates(namespace) + if err != nil { + r.log.Debugf("Unable to determine CSVs to exclude: %v", err) + } + } + namespaces := []string{namespace, r.globalCatalogNamespace} - operators, err := r.resolver.Resolve(namespaces, subs) + operators, err := r.resolver.Resolve(namespaces, subs, cachePredicates...) 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 e01692ce5f..be9e795082 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 @@ -46,6 +46,240 @@ var ( Requires4 = APISet4 ) +func TestIsReplacementChainThatEndsInFailure(t *testing.T) { + type out struct { + b bool + err error + } + + tests := []struct { + name string + csv *v1alpha1.ClusterServiceVersion + csvToReplacement map[string]*v1alpha1.ClusterServiceVersion + expected out + }{ + { + name: "NilCSV", + csv: nil, + csvToReplacement: nil, + expected: out{ + b: false, + err: fmt.Errorf("csv cannot be nil"), + }, + }, + { + name: "OneEntryReplacementChainEndsInFailure", + csv: &v1alpha1.ClusterServiceVersion{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo-v1", + Namespace: "bar", + }, + Status: v1alpha1.ClusterServiceVersionStatus{ + Phase: v1alpha1.CSVPhaseFailed, + }, + }, + csvToReplacement: nil, + expected: out{ + b: true, + err: nil, + }, + }, + { + name: "OneEntryReplacementChainEndsInSuccess", + csv: &v1alpha1.ClusterServiceVersion{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo-v1", + Namespace: "bar", + }, + Status: v1alpha1.ClusterServiceVersionStatus{ + Phase: v1alpha1.CSVPhaseSucceeded, + }, + }, + csvToReplacement: nil, + expected: out{ + b: false, + err: nil, + }, + }, + { + name: "ReplacementChainEndsInSuccess", + csv: &v1alpha1.ClusterServiceVersion{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo-v1", + Namespace: "bar", + }, + Status: v1alpha1.ClusterServiceVersionStatus{ + Phase: v1alpha1.CSVPhaseReplacing, + }, + }, + csvToReplacement: map[string]*v1alpha1.ClusterServiceVersion{ + "foo-v1": { + ObjectMeta: metav1.ObjectMeta{ + Name: "foo-v2", + Namespace: "bar", + }, + Spec: v1alpha1.ClusterServiceVersionSpec{ + Replaces: "foo-v1", + }, + Status: v1alpha1.ClusterServiceVersionStatus{ + Phase: v1alpha1.CSVPhaseReplacing, + }, + }, + "foo-v2": { + ObjectMeta: metav1.ObjectMeta{ + Name: "foo-v3", + Namespace: "bar", + }, + Spec: v1alpha1.ClusterServiceVersionSpec{ + Replaces: "foo-v2", + }, + Status: v1alpha1.ClusterServiceVersionStatus{ + Phase: v1alpha1.CSVPhaseSucceeded, + }, + }, + }, + expected: out{ + b: false, + err: nil, + }, + }, + { + name: "ReplacementChainEndsInFailure", + csv: &v1alpha1.ClusterServiceVersion{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo-v1", + Namespace: "bar", + }, + Status: v1alpha1.ClusterServiceVersionStatus{ + Phase: v1alpha1.CSVPhaseReplacing, + }, + }, + csvToReplacement: map[string]*v1alpha1.ClusterServiceVersion{ + "foo-v1": { + ObjectMeta: metav1.ObjectMeta{ + Name: "foo-v2", + Namespace: "bar", + }, + Spec: v1alpha1.ClusterServiceVersionSpec{ + Replaces: "foo-v1", + }, + Status: v1alpha1.ClusterServiceVersionStatus{ + Phase: v1alpha1.CSVPhaseReplacing, + }, + }, + "foo-v2": { + ObjectMeta: metav1.ObjectMeta{ + Name: "foo-v3", + Namespace: "bar", + }, + Spec: v1alpha1.ClusterServiceVersionSpec{ + Replaces: "foo-v2", + }, + Status: v1alpha1.ClusterServiceVersionStatus{ + Phase: v1alpha1.CSVPhaseFailed, + }, + }, + }, + expected: out{ + b: true, + err: nil, + }, + }, + { + name: "ReplacementChainBrokenByFailedCSVInMiddle", + csv: &v1alpha1.ClusterServiceVersion{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo-v1", + Namespace: "bar", + }, + Status: v1alpha1.ClusterServiceVersionStatus{ + Phase: v1alpha1.CSVPhaseReplacing, + }, + }, + csvToReplacement: map[string]*v1alpha1.ClusterServiceVersion{ + "foo-v1": { + ObjectMeta: metav1.ObjectMeta{ + Name: "foo-v2", + Namespace: "bar", + }, + Spec: v1alpha1.ClusterServiceVersionSpec{ + Replaces: "foo-v1", + }, + Status: v1alpha1.ClusterServiceVersionStatus{ + Phase: v1alpha1.CSVPhaseFailed, + }, + }, + "foo-v2": { + ObjectMeta: metav1.ObjectMeta{ + Name: "foo-v3", + Namespace: "bar", + }, + Spec: v1alpha1.ClusterServiceVersionSpec{ + Replaces: "foo-v2", + }, + Status: v1alpha1.ClusterServiceVersionStatus{ + Phase: v1alpha1.CSVPhaseFailed, + }, + }, + }, + expected: out{ + b: false, + err: fmt.Errorf("csv bar/foo-v2 in phase Failed instead of Replacing"), + }, + }, + { + name: "InfiniteLoopReplacementChain", + csv: &v1alpha1.ClusterServiceVersion{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo-v1", + Namespace: "bar", + }, + Status: v1alpha1.ClusterServiceVersionStatus{ + Phase: v1alpha1.CSVPhaseReplacing, + }, + }, + csvToReplacement: map[string]*v1alpha1.ClusterServiceVersion{ + "foo-v1": { + ObjectMeta: metav1.ObjectMeta{ + Name: "foo-v2", + Namespace: "bar", + }, + Spec: v1alpha1.ClusterServiceVersionSpec{ + Replaces: "foo-v1", + }, + Status: v1alpha1.ClusterServiceVersionStatus{ + Phase: v1alpha1.CSVPhaseReplacing, + }, + }, + "foo-v2": { + ObjectMeta: metav1.ObjectMeta{ + Name: "foo-v1", + Namespace: "bar", + }, + Spec: v1alpha1.ClusterServiceVersionSpec{ + Replaces: "foo-v2", + }, + Status: v1alpha1.ClusterServiceVersionStatus{ + Phase: v1alpha1.CSVPhaseReplacing, + }, + }, + }, + expected: out{ + b: false, + err: fmt.Errorf("infinite replacement chain detected"), + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + endsInFailure, err := isReplacementChainThatEndsInFailure(tt.csv, tt.csvToReplacement) + require.Equal(t, tt.expected.b, endsInFailure) + require.Equal(t, tt.expected.err, err) + }) + } +} + func TestInitHooks(t *testing.T) { clientFake := fake.NewSimpleClientset() lister := operatorlister.NewLister() @@ -84,12 +318,12 @@ func TestResolver(t *testing.T) { solverError solver.NotSatisfiable } type resolverTest struct { - name string - clusterState []runtime.Object - bundlesByCatalog map[resolvercache.SourceKey][]*api.Bundle - out resolverTestOut + name string + clusterState []runtime.Object + bundlesByCatalog map[resolvercache.SourceKey][]*api.Bundle + out resolverTestOut + failForwardEnabled bool } - nothing := resolverTestOut{ steps: [][]*v1alpha1.Step{}, lookups: []v1alpha1.BundleLookup{}, @@ -826,13 +1060,158 @@ func TestResolver(t *testing.T) { }}, out: resolverTestOut{ steps: [][]*v1alpha1.Step{ - bundleSteps(bundle("a.v3", "a", "alpha", "", nil, nil, nil, nil, withVersion("1.0.0"), withSkips([]string{"a.v1"})), namespace, "a.v1", catalog), + bundleSteps(bundle("a.v3", "a", "alpha", "", nil, nil, nil, nil, withVersion("1.0.0")), namespace, "a.v1", catalog), }, subs: []*v1alpha1.Subscription{ updatedSub(namespace, "a.v3", "a.v1", "a", "alpha", catalog), }, }, }, + { + name: "FailForwardDisabled/2EntryReplacementChain/NotSatisfiable", + clusterState: []runtime.Object{ + existingSub(namespace, "a.v2", "a", "alpha", catalog), + existingOperator(namespace, "a.v1", "a", "alpha", "", Provides1, nil, nil, nil, withPhase(v1alpha1.CSVPhaseReplacing)), + existingOperator(namespace, "a.v2", "a", "alpha", "a.v1", Provides1, nil, nil, nil, withPhase(v1alpha1.CSVPhaseFailed)), + }, + bundlesByCatalog: map[resolvercache.SourceKey][]*api.Bundle{catalog: { + bundle("a.v1", "a", "alpha", "", Provides1, nil, nil, nil, withVersion("1.0.0")), + bundle("a.v2", "a", "alpha", "a.v1", Provides1, nil, nil, nil, withVersion("2.0.0")), + bundle("a.v3", "a", "alpha", "a.v2", Provides1, nil, nil, nil, withVersion("3.0.0")), + }}, + out: resolverTestOut{ + steps: [][]*v1alpha1.Step{}, + subs: []*v1alpha1.Subscription{}, + errAssert: func(t *testing.T, err error) { + assert.IsType(t, solver.NotSatisfiable{}, err) + assert.Contains(t, err.Error(), "constraints not satisfiable") + assert.Contains(t, err.Error(), "provide k (g/v)") + assert.Contains(t, err.Error(), "clusterserviceversion a.v1 exists and is not referenced by a subscription") + assert.Contains(t, err.Error(), "subscription a-alpha requires at least one of catsrc/catsrc-namespace/alpha/a.v3 or @existing/catsrc-namespace//a.v2") + }, + }, + }, + { + name: "FailForwardEnabled/2EntryReplacementChain/Satisfiable", + clusterState: []runtime.Object{ + existingSub(namespace, "a.v2", "a", "alpha", catalog), + existingOperator(namespace, "a.v1", "a", "alpha", "", Provides1, nil, nil, nil, withPhase(v1alpha1.CSVPhaseReplacing)), + existingOperator(namespace, "a.v2", "a", "alpha", "a.v1", Provides1, nil, nil, nil, withPhase(v1alpha1.CSVPhaseFailed)), + }, + failForwardEnabled: true, + bundlesByCatalog: map[resolvercache.SourceKey][]*api.Bundle{catalog: { + bundle("a.v1", "a", "alpha", "", Provides1, nil, nil, nil, withVersion("1.0.0")), + bundle("a.v2", "a", "alpha", "a.v1", Provides1, nil, nil, nil, withVersion("2.0.0")), + bundle("a.v3", "a", "alpha", "a.v2", Provides1, nil, nil, nil, withVersion("3.0.0")), + }}, + out: resolverTestOut{ + steps: [][]*v1alpha1.Step{ + bundleSteps(bundle("a.v3", "a", "alpha", "a.v2", Provides1, nil, nil, nil, withVersion("3.0.0")), namespace, "a.v2", catalog), + }, + subs: []*v1alpha1.Subscription{ + updatedSub(namespace, "a.v3", "a.v2", "a", "alpha", catalog), + }, + }, + }, + { + name: "FailForwardDisabled/3EntryReplacementChain/NotSatisfiable", + clusterState: []runtime.Object{ + existingSub(namespace, "a.v3", "a", "alpha", catalog), + existingOperator(namespace, "a.v1", "a", "alpha", "", Provides1, nil, nil, nil, withPhase(v1alpha1.CSVPhaseReplacing)), + existingOperator(namespace, "a.v2", "a", "alpha", "a.v1", Provides1, nil, nil, nil, withPhase(v1alpha1.CSVPhaseReplacing)), + existingOperator(namespace, "a.v3", "a", "alpha", "a.v2", Provides1, nil, nil, nil, withPhase(v1alpha1.CSVPhaseFailed)), + }, + bundlesByCatalog: map[resolvercache.SourceKey][]*api.Bundle{catalog: { + bundle("a.v1", "a", "alpha", "", Provides1, nil, nil, nil, withVersion("1.0.0")), + bundle("a.v2", "a", "alpha", "a.v1", Provides1, nil, nil, nil, withVersion("2.0.0")), + bundle("a.v3", "a", "alpha", "a.v2", Provides1, nil, nil, nil, withVersion("3.0.0")), + bundle("a.v4", "a", "alpha", "a.v3", Provides1, nil, nil, nil, withVersion("4.0.0")), + }}, + out: resolverTestOut{ + steps: [][]*v1alpha1.Step{}, + subs: []*v1alpha1.Subscription{}, + errAssert: func(t *testing.T, err error) { + assert.IsType(t, solver.NotSatisfiable{}, err) + assert.Contains(t, err.Error(), "constraints not satisfiable") + assert.Contains(t, err.Error(), "provide k (g/v)") + assert.Contains(t, err.Error(), "exists and is not referenced by a subscription") + }, + }, + }, + { + name: "FailForwardEnabled/3EntryReplacementChain/Satisfiable", + clusterState: []runtime.Object{ + existingSub(namespace, "a.v3", "a", "alpha", catalog), + existingOperator(namespace, "a.v1", "a", "alpha", "", Provides1, nil, nil, nil, withPhase(v1alpha1.CSVPhaseReplacing)), + existingOperator(namespace, "a.v2", "a", "alpha", "a.v1", Provides1, nil, nil, nil, withPhase(v1alpha1.CSVPhaseReplacing)), + existingOperator(namespace, "a.v3", "a", "alpha", "a.v2", Provides1, nil, nil, nil, withPhase(v1alpha1.CSVPhaseFailed)), + }, + failForwardEnabled: true, + bundlesByCatalog: map[resolvercache.SourceKey][]*api.Bundle{catalog: { + bundle("a.v1", "a", "alpha", "", Provides1, nil, nil, nil, withVersion("1.0.0")), + bundle("a.v2", "a", "alpha", "a.v1", Provides1, nil, nil, nil, withVersion("2.0.0")), + bundle("a.v3", "a", "alpha", "a.v2", Provides1, nil, nil, nil, withVersion("3.0.0")), + bundle("a.v4", "a", "alpha", "a.v3", Provides1, nil, nil, nil, withVersion("4.0.0")), + }}, + out: resolverTestOut{ + steps: [][]*v1alpha1.Step{ + bundleSteps(bundle("a.v4", "a", "alpha", "a.v3", Provides1, nil, nil, nil, withVersion("4.0.0")), namespace, "a.v3", catalog), + }, + subs: []*v1alpha1.Subscription{ + updatedSub(namespace, "a.v4", "a.v3", "a", "alpha", catalog), + }, + }, + }, + { + name: "FailForwardEnabled/3EntryReplacementChain/ReplacementChainBroken/NotSatisfiable", + clusterState: []runtime.Object{ + existingSub(namespace, "a.v3", "a", "alpha", catalog), + existingOperator(namespace, "a.v1", "a", "alpha", "", Provides1, nil, nil, nil, withPhase(v1alpha1.CSVPhaseFailed)), + existingOperator(namespace, "a.v2", "a", "alpha", "a.v1", Provides1, nil, nil, nil, withPhase(v1alpha1.CSVPhaseFailed)), + existingOperator(namespace, "a.v3", "a", "alpha", "a.v2", Provides1, nil, nil, nil, withPhase(v1alpha1.CSVPhaseFailed)), + }, + failForwardEnabled: true, + bundlesByCatalog: map[resolvercache.SourceKey][]*api.Bundle{catalog: { + bundle("a.v1", "a", "alpha", "", Provides1, nil, nil, nil, withVersion("1.0.0")), + bundle("a.v2", "a", "alpha", "a.v1", Provides1, nil, nil, nil, withVersion("2.0.0")), + bundle("a.v3", "a", "alpha", "a.v2", Provides1, nil, nil, nil, withVersion("3.0.0")), + bundle("a.v4", "a", "alpha", "a.v3", Provides1, nil, nil, nil, withVersion("4.0.0")), + }}, + out: resolverTestOut{ + steps: [][]*v1alpha1.Step{}, + subs: []*v1alpha1.Subscription{}, + errAssert: func(t *testing.T, err error) { + assert.IsType(t, solver.NotSatisfiable{}, err) + assert.Contains(t, err.Error(), "constraints not satisfiable") + assert.Contains(t, err.Error(), "provide k (g/v)") + assert.Contains(t, err.Error(), "exists and is not referenced by a subscription") + }, + }, + }, + { + name: "FailForwardEnabled/MultipleReplaces/ReplacementChainEndsInFailure/ConflictingProvider/NoUpgrade", + clusterState: []runtime.Object{ + existingSub(namespace, "a.v1", "a", "alpha", catalog), + existingOperator(namespace, "b.v1", "b", "alpha", "", Provides1, nil, nil, nil, withPhase(v1alpha1.CSVPhaseReplacing)), + existingOperator(namespace, "a.v1", "a", "alpha", "", Provides1, nil, nil, nil, withPhase(v1alpha1.CSVPhaseFailed)), + }, + failForwardEnabled: true, + bundlesByCatalog: map[resolvercache.SourceKey][]*api.Bundle{catalog: { + bundle("a.v1", "a", "alpha", "", Provides1, nil, nil, nil, withVersion("1.0.0")), + bundle("a.v2", "a", "alpha", "a.v1", Provides1, nil, nil, nil, withVersion("2.0.0")), + bundle("b.v1", "b", "alpha", "", Provides1, nil, nil, nil, withVersion("1.0.0")), + }}, + out: resolverTestOut{ + steps: [][]*v1alpha1.Step{}, + subs: []*v1alpha1.Subscription{}, + errAssert: func(t *testing.T, err error) { + assert.IsType(t, solver.NotSatisfiable{}, err) + assert.Contains(t, err.Error(), "constraints not satisfiable") + assert.Contains(t, err.Error(), "provide k (g/v)") + assert.Contains(t, err.Error(), "clusterserviceversion b.v1 exists and is not referenced by a subscription") + }, + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -875,7 +1254,7 @@ func TestResolver(t *testing.T) { resolver := NewOperatorStepResolver(lister, clientFake, "", nil, log) resolver.resolver = satresolver - steps, lookups, subs, err := resolver.ResolveSteps(namespace) + steps, lookups, subs, err := resolver.ResolveSteps(namespace, tt.failForwardEnabled) if tt.out.solverError == nil { if tt.out.errAssert == nil { assert.NoError(t, err) @@ -938,10 +1317,11 @@ func TestNamespaceResolverRBAC(t *testing.T) { err error } tests := []struct { - name string - clusterState []runtime.Object - bundlesInCatalog []*api.Bundle - out out + name string + clusterState []runtime.Object + bundlesInCatalog []*api.Bundle + failForwardEnabled bool + out out }{ { name: "NewSubscription/Permissions/ClusterPermissions", @@ -1004,7 +1384,7 @@ func TestNamespaceResolverRBAC(t *testing.T) { } resolver := NewOperatorStepResolver(lister, clientFake, "", nil, logrus.New()) resolver.resolver = satresolver - steps, _, subs, err := resolver.ResolveSteps(namespace) + steps, _, subs, err := resolver.ResolveSteps(namespace, tt.failForwardEnabled) require.Equal(t, tt.out.err, err) requireStepsEqual(t, expectedSteps, steps) require.ElementsMatch(t, tt.out.subs, subs) @@ -1105,13 +1485,24 @@ func existingSub(namespace, operatorName, pkg, channel string, catalog resolverc } } -func existingOperator(namespace, operatorName, pkg, channel, replaces string, providedCRDs, requiredCRDs, providedAPIs, requiredAPIs resolvercache.APISet) *v1alpha1.ClusterServiceVersion { +type csvOption func(*v1alpha1.ClusterServiceVersion) + +func withPhase(phase v1alpha1.ClusterServiceVersionPhase) csvOption { + return func(csv *v1alpha1.ClusterServiceVersion) { + csv.Status.Phase = phase + } +} + +func existingOperator(namespace, operatorName, pkg, channel, replaces string, providedCRDs, requiredCRDs, providedAPIs, requiredAPIs resolvercache.APISet, option ...csvOption) *v1alpha1.ClusterServiceVersion { bundleForOperator := bundle(operatorName, pkg, channel, replaces, providedCRDs, requiredCRDs, providedAPIs, requiredAPIs) csv, err := V1alpha1CSVFromBundle(bundleForOperator) if err != nil { panic(err) } csv.SetNamespace(namespace) + for _, o := range option { + o(csv) + } return csv } diff --git a/staging/operator-lifecycle-manager/pkg/fakes/fake_resolver.go b/staging/operator-lifecycle-manager/pkg/fakes/fake_resolver.go index 3b85a209a1..d24d5bc16f 100644 --- a/staging/operator-lifecycle-manager/pkg/fakes/fake_resolver.go +++ b/staging/operator-lifecycle-manager/pkg/fakes/fake_resolver.go @@ -9,10 +9,11 @@ import ( ) type FakeStepResolver struct { - ResolveStepsStub func(string) ([]*v1alpha1.Step, []v1alpha1.BundleLookup, []*v1alpha1.Subscription, error) + ResolveStepsStub func(string, bool) ([]*v1alpha1.Step, []v1alpha1.BundleLookup, []*v1alpha1.Subscription, error) resolveStepsMutex sync.RWMutex resolveStepsArgsForCall []struct { arg1 string + arg2 bool } resolveStepsReturns struct { result1 []*v1alpha1.Step @@ -30,16 +31,17 @@ type FakeStepResolver struct { invocationsMutex sync.RWMutex } -func (fake *FakeStepResolver) ResolveSteps(arg1 string) ([]*v1alpha1.Step, []v1alpha1.BundleLookup, []*v1alpha1.Subscription, error) { +func (fake *FakeStepResolver) ResolveSteps(arg1 string, arg2 bool) ([]*v1alpha1.Step, []v1alpha1.BundleLookup, []*v1alpha1.Subscription, error) { fake.resolveStepsMutex.Lock() ret, specificReturn := fake.resolveStepsReturnsOnCall[len(fake.resolveStepsArgsForCall)] fake.resolveStepsArgsForCall = append(fake.resolveStepsArgsForCall, struct { arg1 string - }{arg1}) - fake.recordInvocation("ResolveSteps", []interface{}{arg1}) + arg2 bool + }{arg1, arg2}) + fake.recordInvocation("ResolveSteps", []interface{}{arg1, arg2}) fake.resolveStepsMutex.Unlock() if fake.ResolveStepsStub != nil { - return fake.ResolveStepsStub(arg1) + return fake.ResolveStepsStub(arg1, arg2) } if specificReturn { return ret.result1, ret.result2, ret.result3, ret.result4 @@ -54,17 +56,17 @@ func (fake *FakeStepResolver) ResolveStepsCallCount() int { return len(fake.resolveStepsArgsForCall) } -func (fake *FakeStepResolver) ResolveStepsCalls(stub func(string) ([]*v1alpha1.Step, []v1alpha1.BundleLookup, []*v1alpha1.Subscription, error)) { +func (fake *FakeStepResolver) ResolveStepsCalls(stub func(string, bool) ([]*v1alpha1.Step, []v1alpha1.BundleLookup, []*v1alpha1.Subscription, error)) { fake.resolveStepsMutex.Lock() defer fake.resolveStepsMutex.Unlock() fake.ResolveStepsStub = stub } -func (fake *FakeStepResolver) ResolveStepsArgsForCall(i int) string { +func (fake *FakeStepResolver) ResolveStepsArgsForCall(i int) (string, bool) { fake.resolveStepsMutex.RLock() defer fake.resolveStepsMutex.RUnlock() argsForCall := fake.resolveStepsArgsForCall[i] - return argsForCall.arg1 + return argsForCall.arg1, argsForCall.arg2 } func (fake *FakeStepResolver) ResolveStepsReturns(result1 []*v1alpha1.Step, result2 []v1alpha1.BundleLookup, result3 []*v1alpha1.Subscription, result4 error) { diff --git a/staging/operator-lifecycle-manager/test/e2e/catalog_e2e_test.go b/staging/operator-lifecycle-manager/test/e2e/catalog_e2e_test.go index 997e07c54d..9c5820a740 100644 --- a/staging/operator-lifecycle-manager/test/e2e/catalog_e2e_test.go +++ b/staging/operator-lifecycle-manager/test/e2e/catalog_e2e_test.go @@ -85,10 +85,10 @@ var _ = Describe("Starting CatalogSource e2e tests", func() { defer func() { Eventually(func() error { - return ctx.Ctx().KubeClient().ApiextensionsInterface().ApiextensionsV1().CustomResourceDefinitions().Delete(context.TODO(), crd.GetName(), metav1.DeleteOptions{}) + return ctx.Ctx().KubeClient().ApiextensionsInterface().ApiextensionsV1().CustomResourceDefinitions().Delete(context.Background(), crd.GetName(), metav1.DeleteOptions{}) }).Should(Or(Succeed(), WithTransform(k8serror.IsNotFound, BeTrue()))) Eventually(func() error { - return client.IgnoreNotFound(ctx.Ctx().Client().Delete(context.TODO(), &csv)) + return client.IgnoreNotFound(ctx.Ctx().Client().Delete(context.Background(), &csv)) }).Should(Succeed()) }() @@ -151,13 +151,13 @@ var _ = Describe("Starting CatalogSource e2e tests", func() { defer func() { Eventually(func() error { - return ctx.Ctx().KubeClient().ApiextensionsInterface().ApiextensionsV1().CustomResourceDefinitions().Delete(context.TODO(), mainCRD.GetName(), metav1.DeleteOptions{}) + return ctx.Ctx().KubeClient().ApiextensionsInterface().ApiextensionsV1().CustomResourceDefinitions().Delete(context.Background(), mainCRD.GetName(), metav1.DeleteOptions{}) }).Should(Or(Succeed(), WithTransform(k8serror.IsNotFound, BeTrue()))) Eventually(func() error { - return client.IgnoreNotFound(ctx.Ctx().Client().Delete(context.TODO(), &mainCSV)) + return client.IgnoreNotFound(ctx.Ctx().Client().Delete(context.Background(), &mainCSV)) }).Should(Succeed()) Eventually(func() error { - return client.IgnoreNotFound(ctx.Ctx().Client().Delete(context.TODO(), &replacementCSV)) + return client.IgnoreNotFound(ctx.Ctx().Client().Delete(context.Background(), &replacementCSV)) }).Should(Succeed()) }() @@ -201,12 +201,17 @@ var _ = Describe("Starting CatalogSource e2e tests", func() { installPlanName := subscription.Status.Install.Name requiresApprovalChecker := buildInstallPlanPhaseCheckFunc(v1alpha1.InstallPlanPhaseRequiresApproval) - fetchedInstallPlan, err := fetchInstallPlanWithNamespace(GinkgoT(), crc, installPlanName, ns.GetName(), requiresApprovalChecker) - Expect(err).ShouldNot(HaveOccurred()) - fetchedInstallPlan.Spec.Approved = true - _, err = crc.OperatorsV1alpha1().InstallPlans(ns.GetName()).Update(context.Background(), fetchedInstallPlan, metav1.UpdateOptions{}) - Expect(err).ShouldNot(HaveOccurred()) + Eventually(func() error { + fetchedInstallPlan, err := fetchInstallPlanWithNamespace(GinkgoT(), crc, installPlanName, ns.GetName(), requiresApprovalChecker) + if err != nil { + return err + } + + fetchedInstallPlan.Spec.Approved = true + _, err = crc.OperatorsV1alpha1().InstallPlans(ns.GetName()).Update(context.Background(), fetchedInstallPlan, metav1.UpdateOptions{}) + return err + }).Should(Succeed()) _, err = awaitCSV(crc, ns.GetName(), mainCSV.GetName(), csvSucceededChecker) Expect(err).ShouldNot(HaveOccurred()) @@ -255,13 +260,13 @@ var _ = Describe("Starting CatalogSource e2e tests", func() { defer func() { Eventually(func() error { - return ctx.Ctx().KubeClient().ApiextensionsInterface().ApiextensionsV1().CustomResourceDefinitions().Delete(context.TODO(), dependentCRD.GetName(), metav1.DeleteOptions{}) + return ctx.Ctx().KubeClient().ApiextensionsInterface().ApiextensionsV1().CustomResourceDefinitions().Delete(context.Background(), dependentCRD.GetName(), metav1.DeleteOptions{}) }).Should(Or(Succeed(), WithTransform(k8serror.IsNotFound, BeTrue()))) Eventually(func() error { - return client.IgnoreNotFound(ctx.Ctx().Client().Delete(context.TODO(), &mainCSV)) + return client.IgnoreNotFound(ctx.Ctx().Client().Delete(context.Background(), &mainCSV)) }).Should(Succeed()) Eventually(func() error { - return client.IgnoreNotFound(ctx.Ctx().Client().Delete(context.TODO(), &dependentCSV)) + return client.IgnoreNotFound(ctx.Ctx().Client().Delete(context.Background(), &dependentCSV)) }).Should(Succeed()) }() @@ -388,13 +393,13 @@ var _ = Describe("Starting CatalogSource e2e tests", func() { defer func() { Eventually(func() error { - return ctx.Ctx().KubeClient().ApiextensionsInterface().ApiextensionsV1().CustomResourceDefinitions().Delete(context.TODO(), dependentCRD.GetName(), metav1.DeleteOptions{}) + return ctx.Ctx().KubeClient().ApiextensionsInterface().ApiextensionsV1().CustomResourceDefinitions().Delete(context.Background(), dependentCRD.GetName(), metav1.DeleteOptions{}) }).Should(Or(Succeed(), WithTransform(k8serror.IsNotFound, BeTrue()))) Eventually(func() error { - return client.IgnoreNotFound(ctx.Ctx().Client().Delete(context.TODO(), &mainCSV)) + return client.IgnoreNotFound(ctx.Ctx().Client().Delete(context.Background(), &mainCSV)) }).Should(Succeed()) Eventually(func() error { - return client.IgnoreNotFound(ctx.Ctx().Client().Delete(context.TODO(), &dependentCSV)) + return client.IgnoreNotFound(ctx.Ctx().Client().Delete(context.Background(), &dependentCSV)) }).Should(Succeed()) }() @@ -482,16 +487,16 @@ var _ = Describe("Starting CatalogSource e2e tests", func() { defer func() { Eventually(func() error { - return ctx.Ctx().KubeClient().ApiextensionsInterface().ApiextensionsV1().CustomResourceDefinitions().Delete(context.TODO(), dependentCRD.GetName(), metav1.DeleteOptions{}) + return ctx.Ctx().KubeClient().ApiextensionsInterface().ApiextensionsV1().CustomResourceDefinitions().Delete(context.Background(), dependentCRD.GetName(), metav1.DeleteOptions{}) }).Should(Or(Succeed(), WithTransform(k8serror.IsNotFound, BeTrue()))) Eventually(func() error { - return client.IgnoreNotFound(ctx.Ctx().Client().Delete(context.TODO(), &mainCSV)) + return client.IgnoreNotFound(ctx.Ctx().Client().Delete(context.Background(), &mainCSV)) }).Should(Succeed()) Eventually(func() error { - return client.IgnoreNotFound(ctx.Ctx().Client().Delete(context.TODO(), &dependentCSV)) + return client.IgnoreNotFound(ctx.Ctx().Client().Delete(context.Background(), &dependentCSV)) }).Should(Succeed()) Eventually(func() error { - return client.IgnoreNotFound(ctx.Ctx().Client().Delete(context.TODO(), &replacementCSV)) + return client.IgnoreNotFound(ctx.Ctx().Client().Delete(context.Background(), &replacementCSV)) }).Should(Succeed()) }() @@ -592,11 +597,16 @@ var _ = Describe("Starting CatalogSource e2e tests", func() { Expect(err).ShouldNot(HaveOccurred()) // Update the catalog's address to point at the other registry pod's cluster ip - addressSource, err = crc.OperatorsV1alpha1().CatalogSources(ns.GetName()).Get(context.Background(), addressSourceName, metav1.GetOptions{}) - Expect(err).ShouldNot(HaveOccurred()) - addressSource.Spec.Address = net.JoinHostPort(replacementCopy.Status.PodIP, "50051") - _, err = crc.OperatorsV1alpha1().CatalogSources(ns.GetName()).Update(context.Background(), addressSource, metav1.UpdateOptions{}) - Expect(err).ShouldNot(HaveOccurred()) + Eventually(func() error { + addressSource, err = crc.OperatorsV1alpha1().CatalogSources(ns.GetName()).Get(context.Background(), addressSourceName, metav1.GetOptions{}) + if err != nil { + return err + } + + addressSource.Spec.Address = net.JoinHostPort(replacementCopy.Status.PodIP, "50051") + _, err = crc.OperatorsV1alpha1().CatalogSources(ns.GetName()).Update(context.Background(), addressSource, metav1.UpdateOptions{}) + return err + }).Should(Succeed()) // Wait for the replacement CSV to be installed _, err = awaitCSV(crc, ns.GetName(), replacementCSV.GetName(), csvSucceededChecker) @@ -629,10 +639,10 @@ var _ = Describe("Starting CatalogSource e2e tests", func() { defer func() { Eventually(func() error { - return ctx.Ctx().KubeClient().ApiextensionsInterface().ApiextensionsV1().CustomResourceDefinitions().Delete(context.TODO(), crd.GetName(), metav1.DeleteOptions{}) + return ctx.Ctx().KubeClient().ApiextensionsInterface().ApiextensionsV1().CustomResourceDefinitions().Delete(context.Background(), crd.GetName(), metav1.DeleteOptions{}) }).Should(Or(Succeed(), WithTransform(k8serror.IsNotFound, BeTrue()))) Eventually(func() error { - return client.IgnoreNotFound(ctx.Ctx().Client().Delete(context.TODO(), &csv)) + return client.IgnoreNotFound(ctx.Ctx().Client().Delete(context.Background(), &csv)) }).Should(Succeed()) }() @@ -892,12 +902,17 @@ var _ = Describe("Starting CatalogSource e2e tests", func() { } // update catalog source with annotation (to kick resync) - source, err = crc.OperatorsV1alpha1().CatalogSources(source.GetNamespace()).Get(context.Background(), source.GetName(), metav1.GetOptions{}) - Expect(err).ShouldNot(HaveOccurred(), "error awaiting registry pod") - source.Annotations = make(map[string]string) - source.Annotations["testKey"] = "testValue" - _, err = crc.OperatorsV1alpha1().CatalogSources(source.GetNamespace()).Update(context.Background(), source, metav1.UpdateOptions{}) - Expect(err).ShouldNot(HaveOccurred(), "error awaiting registry pod") + Eventually(func() error { + source, err = crc.OperatorsV1alpha1().CatalogSources(source.GetNamespace()).Get(context.Background(), source.GetName(), metav1.GetOptions{}) + if err != nil { + return nil + } + + source.Annotations = make(map[string]string) + source.Annotations["testKey"] = "testValue" + _, err = crc.OperatorsV1alpha1().CatalogSources(source.GetNamespace()).Update(context.Background(), source, metav1.UpdateOptions{}) + return err + }).Should(Succeed()) time.Sleep(11 * time.Second) @@ -916,11 +931,21 @@ var _ = Describe("Starting CatalogSource e2e tests", func() { } } // update catalog source with annotation (to kick resync) - source, err = crc.OperatorsV1alpha1().CatalogSources(source.GetNamespace()).Get(context.Background(), source.GetName(), metav1.GetOptions{}) - Expect(err).ShouldNot(HaveOccurred(), "error getting catalog source pod") - source.Annotations["testKey"] = genName("newValue") - _, err = crc.OperatorsV1alpha1().CatalogSources(source.GetNamespace()).Update(context.Background(), source, metav1.UpdateOptions{}) - Expect(err).ShouldNot(HaveOccurred(), "error updating catalog source pod with test annotation") + Eventually(func() error { + source, err = crc.OperatorsV1alpha1().CatalogSources(source.GetNamespace()).Get(context.Background(), source.GetName(), metav1.GetOptions{}) + if err != nil { + return err + } + + if source.Annotations == nil { + source.Annotations = make(map[string]string) + } + + source.Annotations["testKey"] = genName("newValue") + _, err = crc.OperatorsV1alpha1().CatalogSources(source.GetNamespace()).Update(context.Background(), source, metav1.UpdateOptions{}) + return err + }).Should(Succeed()) + return false } // await new catalog source and ensure old one was deleted @@ -930,11 +955,16 @@ var _ = Describe("Starting CatalogSource e2e tests", func() { Expect(registryPods.Items).To(HaveLen(1), "unexpected number of registry pods found") // update catalog source with annotation (to kick resync) - source, err = crc.OperatorsV1alpha1().CatalogSources(source.GetNamespace()).Get(context.Background(), source.GetName(), metav1.GetOptions{}) - Expect(err).ShouldNot(HaveOccurred(), "error awaiting registry pod") - source.Annotations["testKey"] = "newValue" - _, err = crc.OperatorsV1alpha1().CatalogSources(source.GetNamespace()).Update(context.Background(), source, metav1.UpdateOptions{}) - Expect(err).ShouldNot(HaveOccurred(), "error awaiting registry pod") + Eventually(func() error { + source, err = crc.OperatorsV1alpha1().CatalogSources(source.GetNamespace()).Get(context.Background(), source.GetName(), metav1.GetOptions{}) + if err != nil { + return err + } + + source.Annotations["testKey"] = "newValue" + _, err = crc.OperatorsV1alpha1().CatalogSources(source.GetNamespace()).Update(context.Background(), source, metav1.UpdateOptions{}) + return err + }).Should(Succeed()) subChecker := func(sub *v1alpha1.Subscription) bool { return sub.Status.InstalledCSV == "busybox.v2.0.0" @@ -1030,19 +1060,16 @@ var _ = Describe("Starting CatalogSource e2e tests", func() { Expect(subscription.Status.InstalledCSV).To(Equal("busybox-dependency.v1.0.0")) // Update the catalog image - Eventually(func() (bool, error) { + Eventually(func() error { existingSource, err := crc.OperatorsV1alpha1().CatalogSources(source.GetNamespace()).Get(context.Background(), sourceName, metav1.GetOptions{}) if err != nil { - return false, err + return err } existingSource.Spec.Image = catSrcImage + ":2.0.0-with-ListBundles-method" source, err = crc.OperatorsV1alpha1().CatalogSources(source.GetNamespace()).Update(context.Background(), existingSource, metav1.UpdateOptions{}) - if err == nil { - return true, nil - } - return false, nil - }).Should(BeTrue()) + return err + }).Should(Succeed()) // Wait for the CatalogSource to be ready _, err = fetchCatalogSourceOnStatus(crc, source.GetName(), source.GetNamespace(), catalogSourceRegistryPodSynced) @@ -1102,7 +1129,7 @@ var _ = Describe("Starting CatalogSource e2e tests", func() { }, } - source, err := crc.OperatorsV1alpha1().CatalogSources(source.GetNamespace()).Create(context.TODO(), source, metav1.CreateOptions{}) + source, err := crc.OperatorsV1alpha1().CatalogSources(source.GetNamespace()).Create(context.Background(), source, metav1.CreateOptions{}) Expect(err).ToNot(HaveOccurred()) // wait for new catalog source pod to be created and report ready @@ -1113,7 +1140,7 @@ var _ = Describe("Starting CatalogSource e2e tests", func() { Expect(catalogPods).ToNot(BeNil()) Eventually(func() (bool, error) { - podList, err := c.KubernetesInterface().CoreV1().Pods(source.GetNamespace()).List(context.TODO(), metav1.ListOptions{LabelSelector: selector.String()}) + podList, err := c.KubernetesInterface().CoreV1().Pods(source.GetNamespace()).List(context.Background(), metav1.ListOptions{LabelSelector: selector.String()}) if err != nil { return false, err } @@ -1179,17 +1206,17 @@ var _ = Describe("Starting CatalogSource e2e tests", func() { }, } - _, err := crc.OperatorsV1alpha1().CatalogSources(source.GetNamespace()).Create(context.TODO(), source, metav1.CreateOptions{}) + _, err := crc.OperatorsV1alpha1().CatalogSources(source.GetNamespace()).Create(context.Background(), source, metav1.CreateOptions{}) Expect(err).ToNot(HaveOccurred()) }) AfterEach(func() { - err := crc.OperatorsV1alpha1().CatalogSources(source.GetNamespace()).Delete(context.TODO(), source.GetName(), metav1.DeleteOptions{}) + err := crc.OperatorsV1alpha1().CatalogSources(source.GetNamespace()).Delete(context.Background(), source.GetName(), metav1.DeleteOptions{}) Expect(err).ToNot(HaveOccurred()) }) It("the catalogsource status communicates that a default interval time is being used instead", func() { Eventually(func() bool { - catsrc, err := crc.OperatorsV1alpha1().CatalogSources(source.GetNamespace()).Get(context.TODO(), source.GetName(), metav1.GetOptions{}) + catsrc, err := crc.OperatorsV1alpha1().CatalogSources(source.GetNamespace()).Get(context.Background(), source.GetName(), metav1.GetOptions{}) Expect(err).ToNot(HaveOccurred()) if catsrc.Status.Reason == v1alpha1.CatalogSourceIntervalInvalidError { if catsrc.Status.Message == "error parsing spec.updateStrategy.registryPoll.interval. Using the default value of 15m0s instead. Error: time: unknown unit \"mError\" in duration \"45mError.code\"" { @@ -1201,15 +1228,19 @@ var _ = Describe("Starting CatalogSource e2e tests", func() { }) When("the catalogsource is updated with a valid polling interval", func() { BeforeEach(func() { - catsrc, err := crc.OperatorsV1alpha1().CatalogSources(source.GetNamespace()).Get(context.TODO(), source.GetName(), metav1.GetOptions{}) - Expect(err).ToNot(HaveOccurred()) - catsrc.Spec.UpdateStrategy.RegistryPoll.RawInterval = correctInterval - _, err = crc.OperatorsV1alpha1().CatalogSources(catsrc.GetNamespace()).Update(context.TODO(), catsrc, metav1.UpdateOptions{}) - Expect(err).ToNot(HaveOccurred()) + Eventually(func() error { + catsrc, err := crc.OperatorsV1alpha1().CatalogSources(source.GetNamespace()).Get(context.Background(), source.GetName(), metav1.GetOptions{}) + if err != nil { + return err + } + catsrc.Spec.UpdateStrategy.RegistryPoll.RawInterval = correctInterval + _, err = crc.OperatorsV1alpha1().CatalogSources(catsrc.GetNamespace()).Update(context.Background(), catsrc, metav1.UpdateOptions{}) + return err + }).Should(Succeed()) }) It("the catalogsource spec shows the updated polling interval, and the error message in the status is cleared", func() { Eventually(func() error { - catsrc, err := crc.OperatorsV1alpha1().CatalogSources(source.GetNamespace()).Get(context.TODO(), source.GetName(), metav1.GetOptions{}) + catsrc, err := crc.OperatorsV1alpha1().CatalogSources(source.GetNamespace()).Get(context.Background(), source.GetName(), metav1.GetOptions{}) if err != nil { return err } @@ -1217,7 +1248,7 @@ var _ = Describe("Starting CatalogSource e2e tests", func() { if err != nil { return err } - if catsrc.Status.Reason != "" || (catsrc.Spec.UpdateStrategy.Interval != &metav1.Duration{expectedTime}) { + if catsrc.Status.Reason != "" || (catsrc.Spec.UpdateStrategy.Interval != &metav1.Duration{Duration: expectedTime}) { return err } return nil diff --git a/staging/operator-lifecycle-manager/test/e2e/fail_forward_e2e_test.go b/staging/operator-lifecycle-manager/test/e2e/fail_forward_e2e_test.go new file mode 100644 index 0000000000..d9c56331ba --- /dev/null +++ b/staging/operator-lifecycle-manager/test/e2e/fail_forward_e2e_test.go @@ -0,0 +1,285 @@ +package e2e + +import ( + "context" + "fmt" + "path/filepath" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + + operatorsv1 "github.com/operator-framework/api/pkg/operators/v1" + operatorsv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1" + "github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/clientset/versioned" + "github.com/operator-framework/operator-lifecycle-manager/test/e2e/ctx" +) + +const ( + failForwardTestDataBaseDir = "fail-forward/base/" +) + +var _ = Describe("Fail Forward Upgrades", func() { + var ( + ns corev1.Namespace + crclient versioned.Interface + c client.Client + ) + BeforeEach(func() { + crclient = newCRClient() + c = ctx.Ctx().Client() + + By("creating the testing namespace with an OG that enabled fail forward behavior") + namespaceName := genName("ff-e2e-") + og := operatorsv1.OperatorGroup{ + ObjectMeta: metav1.ObjectMeta{ + Name: fmt.Sprintf("%s-operatorgroup", namespaceName), + Namespace: namespaceName, + }, + Spec: operatorsv1.OperatorGroupSpec{ + UpgradeStrategy: operatorsv1.UpgradeStrategyUnsafeFailForward, + }, + } + ns = SetupGeneratedTestNamespaceWithOperatorGroup(namespaceName, og) + }) + + AfterEach(func() { + By("deleting the testing namespace") + TeardownNamespace(ns.GetName()) + }) + + When("an InstallPlan is reporting a failed state", func() { + var ( + magicCatalog MagicCatalog + catalogSourceName string + subscription *operatorsv1alpha1.Subscription + ) + BeforeEach(func() { + provider, err := NewFileBasedFiledBasedCatalogProvider(filepath.Join(testdataDir, failForwardTestDataBaseDir, "example-operator.v0.1.0.yaml")) + Expect(err).To(BeNil()) + + catalogSourceName = genName("mc-ip-failed-") + magicCatalog = NewMagicCatalog(c, ns.GetName(), catalogSourceName, provider) + Expect(magicCatalog.DeployCatalog(context.Background())).To(BeNil()) + + By("creating the testing subscription") + subscription = &operatorsv1alpha1.Subscription{ + ObjectMeta: metav1.ObjectMeta{ + Name: fmt.Sprintf("%s-sub", catalogSourceName), + Namespace: ns.GetName(), + }, + Spec: &operatorsv1alpha1.SubscriptionSpec{ + CatalogSource: catalogSourceName, + CatalogSourceNamespace: ns.GetName(), + Channel: "stable", + Package: "packageA", + }, + } + Expect(c.Create(context.Background(), subscription)).To(BeNil()) + + By("waiting until the subscription has an IP reference") + subscription, err := fetchSubscription(crclient, subscription.GetNamespace(), subscription.GetName(), subscriptionHasInstallPlanChecker) + Expect(err).Should(BeNil()) + + originalInstallPlanRef := subscription.Status.InstallPlanRef + + By("waiting for the v0.1.0 CSV to report a succeeded phase") + _, err = fetchCSV(crclient, subscription.Status.CurrentCSV, ns.GetName(), buildCSVConditionChecker(operatorsv1alpha1.CSVPhaseSucceeded)) + Expect(err).ShouldNot(HaveOccurred()) + + By("updating the catalog with a broken v0.2.0 bundle image") + brokenProvider, err := NewFileBasedFiledBasedCatalogProvider(filepath.Join(testdataDir, failForwardTestDataBaseDir, "example-operator.v0.2.0.yaml")) + Expect(err).To(BeNil()) + + err = magicCatalog.UpdateCatalog(context.Background(), brokenProvider) + Expect(err).To(BeNil()) + + By("verifying the subscription is referencing a new installplan") + subscription, err = fetchSubscription(crclient, subscription.GetNamespace(), subscription.GetName(), subscriptionHasInstallPlanDifferentChecker(originalInstallPlanRef.Name)) + Expect(err).Should(BeNil()) + + By("patching the installplan to reduce the bundle unpacking timeout") + addBundleUnpackTimeoutIPAnnotation(context.Background(), c, objectRefToNamespacedName(subscription.Status.InstallPlanRef), "1s") + + By("waiting for the bad InstallPlan to report a failed installation state") + ref := subscription.Status.InstallPlanRef + _, err = fetchInstallPlan(GinkgoT(), crclient, ref.Name, ref.Namespace, buildInstallPlanPhaseCheckFunc(operatorsv1alpha1.InstallPlanPhaseFailed)) + Expect(err).To(BeNil()) + + }) + AfterEach(func() { + By("removing the testing catalog resources") + Expect(magicCatalog.UndeployCatalog(context.Background())).To(BeNil()) + }) + It("eventually reports a successful state when multiple bad versions are rolled forward", func() { + By("patching the catalog with another bad bundle version") + badProvider, err := NewFileBasedFiledBasedCatalogProvider(filepath.Join(testdataDir, "fail-forward/multiple-bad-versions", "example-operator.v0.2.1.yaml")) + Expect(err).To(BeNil()) + + err = magicCatalog.UpdateCatalog(context.Background(), badProvider) + Expect(err).To(BeNil()) + + By("waiting for the subscription to have the example-operator.v0.2.1 status.updatedCSV") + subscription, err = fetchSubscription(crclient, subscription.GetNamespace(), subscription.GetName(), subscriptionHasCurrentCSV("example-operator.v0.2.1")) + Expect(err).Should(BeNil()) + + By("patching the installplan to reduce the bundle unpacking timeout") + addBundleUnpackTimeoutIPAnnotation(context.Background(), c, objectRefToNamespacedName(subscription.Status.InstallPlanRef), "1s") + + By("waiting for the bad v0.2.1 InstallPlan to report a failed installation state") + ref := subscription.Status.InstallPlanRef + _, err = fetchInstallPlan(GinkgoT(), crclient, ref.Name, ref.Namespace, buildInstallPlanPhaseCheckFunc(operatorsv1alpha1.InstallPlanPhaseFailed)) + Expect(err).To(BeNil()) + + By("patching the catalog with a fixed version") + fixedProvider, err := NewFileBasedFiledBasedCatalogProvider(filepath.Join(testdataDir, "fail-forward/multiple-bad-versions", "example-operator.v0.3.0.yaml")) + Expect(err).To(BeNil()) + + err = magicCatalog.UpdateCatalog(context.Background(), fixedProvider) + Expect(err).To(BeNil()) + + By("waiting for the subscription to have the example-operator.v0.3.0 status.updatedCSV") + subscription, err = fetchSubscription(crclient, subscription.GetNamespace(), subscription.GetName(), subscriptionHasCurrentCSV("example-operator.v0.3.0")) + Expect(err).Should(BeNil()) + }) + + It("eventually reports a successful state when using skip ranges", func() { + By("patching the catalog with a fixed version") + fixedProvider, err := NewFileBasedFiledBasedCatalogProvider(filepath.Join(testdataDir, "fail-forward/skip-range", "example-operator.v0.3.0.yaml")) + Expect(err).To(BeNil()) + + err = magicCatalog.UpdateCatalog(context.Background(), fixedProvider) + Expect(err).To(BeNil()) + + By("waiting for the subscription to have the example-operator.v0.3.0 status.updatedCSV") + subscription, err = fetchSubscription(crclient, subscription.GetNamespace(), subscription.GetName(), subscriptionHasCurrentCSV("example-operator.v0.3.0")) + Expect(err).Should(BeNil()) + }) + It("eventually reports a successful state when using skips", func() { + By("patching the catalog with a fixed version") + fixedProvider, err := NewFileBasedFiledBasedCatalogProvider(filepath.Join(testdataDir, "fail-forward/skips", "example-operator.v0.3.0.yaml")) + Expect(err).To(BeNil()) + + err = magicCatalog.UpdateCatalog(context.Background(), fixedProvider) + Expect(err).To(BeNil()) + + By("waiting for the subscription to have the example-operator.v0.3.0 status.updatedCSV") + subscription, err = fetchSubscription(crclient, subscription.GetNamespace(), subscription.GetName(), subscriptionHasCurrentCSV("example-operator.v0.3.0")) + Expect(err).Should(BeNil()) + }) + It("eventually reports a failed state when using replaces", func() { + By("patching the catalog with a fixed version") + fixedProvider, err := NewFileBasedFiledBasedCatalogProvider(filepath.Join(testdataDir, "fail-forward/replaces", "example-operator.v0.3.0.yaml")) + Expect(err).To(BeNil()) + + err = magicCatalog.UpdateCatalog(context.Background(), fixedProvider) + Expect(err).To(BeNil()) + + By("waiting for the subscription to maintain the example-operator.v0.2.0 status.updatedCSV") + Consistently(func() string { + subscription, err := crclient.OperatorsV1alpha1().Subscriptions(subscription.GetNamespace()).Get(context.Background(), subscription.GetName(), metav1.GetOptions{}) + if err != nil || subscription == nil { + return "" + } + return subscription.Status.CurrentCSV + }).Should(Equal("example-operator.v0.2.0")) + }) + }) + When("a CSV resource is in a failed state", func() { + var ( + magicCatalog MagicCatalog + catalogSourceName string + subscription *operatorsv1alpha1.Subscription + ) + BeforeEach(func() { + provider, err := NewFileBasedFiledBasedCatalogProvider(filepath.Join(testdataDir, failForwardTestDataBaseDir, "example-operator.v0.1.0.yaml")) + Expect(err).To(BeNil()) + + catalogSourceName = genName("mc-csv-failed-") + magicCatalog = NewMagicCatalog(c, ns.GetName(), catalogSourceName, provider) + Expect(magicCatalog.DeployCatalog(context.Background())).To(BeNil()) + + By("creating the testing subscription") + subscription = &operatorsv1alpha1.Subscription{ + ObjectMeta: metav1.ObjectMeta{ + Name: fmt.Sprintf("%s-sub", catalogSourceName), + Namespace: ns.GetName(), + }, + Spec: &operatorsv1alpha1.SubscriptionSpec{ + CatalogSource: catalogSourceName, + CatalogSourceNamespace: ns.GetName(), + Channel: "stable", + Package: "packageA", + }, + } + Expect(c.Create(context.Background(), subscription)).To(BeNil()) + + By("waiting until the subscription has an IP reference") + subscription, err := fetchSubscription(crclient, subscription.GetNamespace(), subscription.GetName(), subscriptionHasInstallPlanChecker) + Expect(err).Should(BeNil()) + + By("waiting for the v0.1.0 CSV to report a succeeded phase") + _, err = fetchCSV(crclient, subscription.Status.CurrentCSV, ns.GetName(), buildCSVConditionChecker(operatorsv1alpha1.CSVPhaseSucceeded)) + Expect(err).ShouldNot(HaveOccurred()) + + By("updating the catalog with a broken v0.2.0 csv") + brokenProvider, err := NewFileBasedFiledBasedCatalogProvider(filepath.Join(testdataDir, failForwardTestDataBaseDir, "example-operator.v0.2.0-2.yaml")) + Expect(err).To(BeNil()) + + err = magicCatalog.UpdateCatalog(context.Background(), brokenProvider) + Expect(err).To(BeNil()) + + badCSV := "example-operator.v0.2.0" + By("verifying the subscription has installed the current csv") + subscription, err = fetchSubscription(crclient, subscription.GetNamespace(), subscription.GetName(), subscriptionHasCurrentCSV(badCSV)) + Expect(err).Should(BeNil()) + + By("waiting for the bad CSV to report a failed state") + _, err = fetchCSV(crclient, subscription.Status.CurrentCSV, ns.GetName(), csvFailedChecker) + Expect(err).To(BeNil()) + + }) + AfterEach(func() { + By("removing the testing catalog resources") + Expect(magicCatalog.UndeployCatalog(context.Background())).To(BeNil()) + }) + It("eventually reports a successful state when using skip ranges", func() { + By("patching the catalog with a fixed version") + fixedProvider, err := NewFileBasedFiledBasedCatalogProvider(filepath.Join(testdataDir, "fail-forward/skip-range", "example-operator.v0.3.0.yaml")) + Expect(err).To(BeNil()) + + err = magicCatalog.UpdateCatalog(context.Background(), fixedProvider) + Expect(err).To(BeNil()) + + By("waiting for the subscription to have the example-operator.v0.3.0 status.updatedCSV") + subscription, err = fetchSubscription(crclient, subscription.GetNamespace(), subscription.GetName(), subscriptionHasCurrentCSV("example-operator.v0.3.0")) + Expect(err).Should(BeNil()) + }) + It("eventually reports a successful state when using skips", func() { + By("patching the catalog with a fixed version") + fixedProvider, err := NewFileBasedFiledBasedCatalogProvider(filepath.Join(testdataDir, "fail-forward/skips", "example-operator.v0.3.0.yaml")) + Expect(err).To(BeNil()) + + err = magicCatalog.UpdateCatalog(context.Background(), fixedProvider) + Expect(err).To(BeNil()) + + By("waiting for the subscription to have the example-operator.v0.3.0 status.updatedCSV") + subscription, err = fetchSubscription(crclient, subscription.GetNamespace(), subscription.GetName(), subscriptionHasCurrentCSV("example-operator.v0.3.0")) + Expect(err).Should(BeNil()) + }) + It("eventually reports a successful state when using replaces", func() { + By("patching the catalog with a fixed version") + fixedProvider, err := NewFileBasedFiledBasedCatalogProvider(filepath.Join(testdataDir, "fail-forward/replaces", "example-operator.v0.3.0.yaml")) + Expect(err).To(BeNil()) + + err = magicCatalog.UpdateCatalog(context.Background(), fixedProvider) + Expect(err).To(BeNil()) + + By("waiting for the subscription to have the example-operator.v0.3.0 status.updatedCSV") + subscription, err = fetchSubscription(crclient, subscription.GetNamespace(), subscription.GetName(), subscriptionHasCurrentCSV("example-operator.v0.3.0")) + Expect(err).Should(BeNil()) + }) + }) +}) diff --git a/staging/operator-lifecycle-manager/test/e2e/magic_catalog.go b/staging/operator-lifecycle-manager/test/e2e/magic_catalog.go index 0f8c3b20c0..81d788778f 100644 --- a/staging/operator-lifecycle-manager/test/e2e/magic_catalog.go +++ b/staging/operator-lifecycle-manager/test/e2e/magic_catalog.go @@ -9,6 +9,7 @@ import ( k8serror "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + utilerrors "k8s.io/apimachinery/pkg/util/errors" "k8s.io/apimachinery/pkg/util/intstr" k8scontrollerclient "sigs.k8s.io/controller-runtime/pkg/client" ) @@ -22,6 +23,7 @@ const ( type MagicCatalog interface { DeployCatalog(ctx context.Context) error + UpdateCatalog(ctx context.Context, provider FileBasedCatalogProvider) error UndeployCatalog(ctx context.Context) []error } @@ -50,7 +52,6 @@ func NewMagicCatalog(kubeClient k8scontrollerclient.Client, namespace string, ca } func (c *magicCatalog) DeployCatalog(ctx context.Context) error { - catalogSource := c.makeCatalogSource() resourcesInOrderOfDeployment := []k8scontrollerclient.Object{ c.makeConfigMap(), @@ -58,35 +59,80 @@ func (c *magicCatalog) DeployCatalog(ctx context.Context) error { c.makeCatalogService(), catalogSource, } + if err := c.deployCatalog(ctx, resourcesInOrderOfDeployment); err != nil { + return err + } + if err := catalogSourceIsReady(ctx, c.kubeClient, catalogSource); err != nil { + return c.cleanUpAfter(ctx, err) + } + + return nil +} + +func catalogSourceIsReady(ctx context.Context, c k8scontrollerclient.Client, cs *operatorsv1alpha1.CatalogSource) error { + // wait for catalog source to become ready + return waitFor(func() (bool, error) { + err := c.Get(ctx, k8scontrollerclient.ObjectKey{ + Name: cs.GetName(), + Namespace: cs.GetNamespace(), + }, cs) + if err != nil || cs.Status.GRPCConnectionState == nil { + return false, err + } + state := cs.Status.GRPCConnectionState.LastObservedState + if state != catalogReadyState { + return false, nil + } + return true, nil + }) +} - for _, res := range resourcesInOrderOfDeployment { +func (c *magicCatalog) deployCatalog(ctx context.Context, resources []k8scontrollerclient.Object) error { + for _, res := range resources { err := c.kubeClient.Create(ctx, res) if err != nil { return c.cleanUpAfter(ctx, err) } } + return nil +} - // wait for catalog source to become ready +func (c *magicCatalog) UpdateCatalog(ctx context.Context, provider FileBasedCatalogProvider) error { + resourcesInOrderOfDeletion := []k8scontrollerclient.Object{ + c.makeCatalogSourcePod(), + c.makeConfigMap(), + } + errors := c.undeployCatalog(ctx, resourcesInOrderOfDeletion) + if len(errors) != 0 { + return utilerrors.NewAggregate(errors) + } + + // TODO(tflannag): Create a pod watcher struct and setup an underlying watch + // and block until ctx.Done()? err := waitFor(func() (bool, error) { + pod := &corev1.Pod{} err := c.kubeClient.Get(ctx, k8scontrollerclient.ObjectKey{ - Name: catalogSource.GetName(), - Namespace: catalogSource.GetNamespace(), - }, catalogSource) - - if err != nil || catalogSource.Status.GRPCConnectionState == nil { - return false, err - } - - state := catalogSource.Status.GRPCConnectionState.LastObservedState - - if state != catalogReadyState { - return false, nil - } else { + Name: c.podName, + Namespace: c.namespace, + }, pod) + if k8serror.IsNotFound(err) { return true, nil } + return false, err }) - if err != nil { + return fmt.Errorf("failed to successfully update the catalog deployment: %v", err) + } + + c.fileBasedCatalog = provider + resourcesInOrderOfCreation := []k8scontrollerclient.Object{ + c.makeConfigMap(), + c.makeCatalogSourcePod(), + } + if err := c.deployCatalog(ctx, resourcesInOrderOfCreation); err != nil { + return err + } + if err := catalogSourceIsReady(ctx, c.kubeClient, c.makeCatalogSource()); err != nil { return c.cleanUpAfter(ctx, err) } @@ -94,30 +140,31 @@ func (c *magicCatalog) DeployCatalog(ctx context.Context) error { } func (c *magicCatalog) UndeployCatalog(ctx context.Context) []error { - var errs []error = nil - resourcesInOrderOfDeletion := []k8scontrollerclient.Object{ c.makeCatalogSource(), c.makeCatalogService(), c.makeCatalogSourcePod(), c.makeConfigMap(), } + return c.undeployCatalog(ctx, resourcesInOrderOfDeletion) +} +func (c *magicCatalog) undeployCatalog(ctx context.Context, resources []k8scontrollerclient.Object) []error { + var errors []error // try to delete all resourcesInOrderOfDeletion even if errors are // encountered through deletion. - for _, res := range resourcesInOrderOfDeletion { + for _, res := range resources { err := c.kubeClient.Delete(ctx, res) // ignore not found errors if err != nil && !k8serror.IsNotFound(err) { - if errs == nil { - errs = make([]error, 0) + if errors == nil { + errors = make([]error, 0) } - errs = append(errs, err) + errors = append(errors, err) } } - - return errs + return errors } func (c *magicCatalog) cleanUpAfter(ctx context.Context, err error) error { diff --git a/staging/operator-lifecycle-manager/test/e2e/magic_catalog_test.go b/staging/operator-lifecycle-manager/test/e2e/magic_catalog_test.go index cf47d0100e..69d305d12c 100644 --- a/staging/operator-lifecycle-manager/test/e2e/magic_catalog_test.go +++ b/staging/operator-lifecycle-manager/test/e2e/magic_catalog_test.go @@ -2,23 +2,26 @@ package e2e import ( "context" + "fmt" "path/filepath" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" "github.com/operator-framework/operator-lifecycle-manager/test/e2e/ctx" corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" ) var _ = Describe("MagicCatalog", func() { var ( generatedNamespace corev1.Namespace + c client.Client ) - BeforeEach(func() { + c = ctx.Ctx().Client() generatedNamespace = SetupGeneratedTestNamespace(genName("magic-catalog-e2e-")) }) - AfterEach(func() { TeardownNamespace(generatedNamespace.GetName()) }) @@ -27,15 +30,90 @@ var _ = Describe("MagicCatalog", func() { // create dependencies const catalogName = "test" namespace := generatedNamespace.GetName() - kubeClient := ctx.Ctx().Client() - provider, err := NewFileBasedFiledBasedCatalogProvider(filepath.Join(testdataDir, "fbc_catalog.json")) + provider, err := NewFileBasedFiledBasedCatalogProvider(filepath.Join(testdataDir, "magiccatalog/fbc_catalog.json")) Expect(err).To(BeNil()) // create and deploy and undeploy the magic catalog - magicCatalog := NewMagicCatalog(kubeClient, namespace, catalogName, provider) + magicCatalog := NewMagicCatalog(c, namespace, catalogName, provider) // deployment blocks until the catalog source has reached a READY status - Expect(magicCatalog.DeployCatalog(context.TODO())).To(BeNil()) - Expect(magicCatalog.UndeployCatalog(context.TODO())).To(BeNil()) + Expect(magicCatalog.DeployCatalog(context.Background())).To(BeNil()) + Expect(magicCatalog.UndeployCatalog(context.Background())).To(BeNil()) + }) + When("an existing magic catalog exists", func() { + var ( + mc MagicCatalog + catalogName string + ) + BeforeEach(func() { + provider, err := NewFileBasedFiledBasedCatalogProvider(filepath.Join(testdataDir, "magiccatalog/fbc_initial.yaml")) + Expect(err).To(BeNil()) + + catalogName = genName("mc-e2e-catalog-") + + mc = NewMagicCatalog(c, generatedNamespace.GetName(), catalogName, provider) + Expect(mc.DeployCatalog(context.Background())).To(BeNil()) + }) + AfterEach(func() { + Expect(mc.UndeployCatalog(context.Background())).To(BeNil()) + }) + + It("should succeed when the magic catalog is updated", func() { + updatedProvider, err := NewFileBasedFiledBasedCatalogProvider(filepath.Join(testdataDir, "magiccatalog/fbc_updated.yaml")) + Expect(err).To(BeNil()) + + updatedMC := NewMagicCatalog(c, generatedNamespace.GetName(), catalogName, updatedProvider) + Expect(updatedMC.UpdateCatalog(context.Background(), updatedProvider)).To(BeNil()) + + Eventually(func() (*corev1.ConfigMap, error) { + cm := &corev1.ConfigMap{} + err := c.Get(context.Background(), types.NamespacedName{ + Name: fmt.Sprintf("%s-configmap", catalogName), + Namespace: generatedNamespace.GetName(), + }, cm) + if err != nil { + return nil, err + } + return cm, nil + }).Should(And( + Not(BeNil()), + WithTransform(func(c *corev1.ConfigMap) string { + data, ok := c.Data["catalog.json"] + if !ok { + return "" + } + return data + }, ContainSubstring(`--- +schema: olm.package +name: packageA +defaultChannel: stable +--- +schema: olm.channel +package: packageA +name: stable +entries: + - name: busybox.v2.0.0 + replaces: busybox.v1.0.0 +--- +schema: olm.bundle +name: busybox.v2.0.0 +package: packageA +image: quay.io/olmtest/busybox-bundle:2.0.0 +properties: + - type: olm.gvk + value: + group: example.com + kind: TestA + version: v1alpha1 + - type: olm.package + value: + packageName: packageA + version: 1.0.0 +`)), + )) + }) + It("should fail when the magic catalog is re-created", func() { + Expect(mc.DeployCatalog(context.Background())).ToNot(BeNil()) + }) }) }) diff --git a/staging/operator-lifecycle-manager/test/e2e/testdata/fail-forward/base/example-operator.v0.1.0.yaml b/staging/operator-lifecycle-manager/test/e2e/testdata/fail-forward/base/example-operator.v0.1.0.yaml new file mode 100644 index 0000000000..d2b4c981be --- /dev/null +++ b/staging/operator-lifecycle-manager/test/e2e/testdata/fail-forward/base/example-operator.v0.1.0.yaml @@ -0,0 +1,25 @@ +--- +schema: olm.package +name: packageA +defaultChannel: stable +--- +schema: olm.channel +package: packageA +name: stable +entries: + - name: example-operator.v0.1.0 +--- +schema: olm.bundle +name: example-operator.v0.1.0 +package: packageA +image: quay.io/olmtest/example-operator-bundle:0.1.0 +properties: + - type: olm.gvk + value: + group: example.com + kind: TestA + version: v1alpha1 + - type: olm.package + value: + packageName: packageA + version: 1.0.0 diff --git a/staging/operator-lifecycle-manager/test/e2e/testdata/fail-forward/base/example-operator.v0.2.0-2.yaml b/staging/operator-lifecycle-manager/test/e2e/testdata/fail-forward/base/example-operator.v0.2.0-2.yaml new file mode 100644 index 0000000000..030708d012 --- /dev/null +++ b/staging/operator-lifecycle-manager/test/e2e/testdata/fail-forward/base/example-operator.v0.2.0-2.yaml @@ -0,0 +1,26 @@ +--- +schema: olm.package +name: packageA +defaultChannel: stable +--- +schema: olm.channel +package: packageA +name: stable +entries: + - name: example-operator.v0.2.0 + replaces: example-operator.v0.1.0 +--- +schema: olm.bundle +name: example-operator.v0.2.0 +package: packageA +image: quay.io/olmtest/example-operator-bundle:0.2.0-invalid-csv +properties: + - type: olm.gvk + value: + group: example.com + kind: TestA + version: v1alpha1 + - type: olm.package + value: + packageName: packageA + version: 1.0.1 diff --git a/staging/operator-lifecycle-manager/test/e2e/testdata/fail-forward/base/example-operator.v0.2.0.yaml b/staging/operator-lifecycle-manager/test/e2e/testdata/fail-forward/base/example-operator.v0.2.0.yaml new file mode 100644 index 0000000000..555242e536 --- /dev/null +++ b/staging/operator-lifecycle-manager/test/e2e/testdata/fail-forward/base/example-operator.v0.2.0.yaml @@ -0,0 +1,26 @@ +--- +schema: olm.package +name: packageA +defaultChannel: stable +--- +schema: olm.channel +package: packageA +name: stable +entries: + - name: example-operator.v0.2.0 + replaces: example-operator.v0.1.0 +--- +schema: olm.bundle +name: example-operator.v0.2.0 +package: packageA +image: quay.io/olmtest/example-operator-bundle:non-existent-tag +properties: + - type: olm.gvk + value: + group: example.com + kind: TestA + version: v1alpha1 + - type: olm.package + value: + packageName: packageA + version: 1.0.1 diff --git a/staging/operator-lifecycle-manager/test/e2e/testdata/fail-forward/base/example-operator.v0.3.0.yaml b/staging/operator-lifecycle-manager/test/e2e/testdata/fail-forward/base/example-operator.v0.3.0.yaml new file mode 100644 index 0000000000..1f6189eb74 --- /dev/null +++ b/staging/operator-lifecycle-manager/test/e2e/testdata/fail-forward/base/example-operator.v0.3.0.yaml @@ -0,0 +1,26 @@ +--- +schema: olm.package +name: packageA +defaultChannel: stable +--- +schema: olm.channel +package: packageA +name: stable +entries: + - name: example-operator.v0.3.0 + replaces: example-operator.v0.2.0 +--- +schema: olm.bundle +name: example-operator.v0.3.0 +package: packageA +image: quay.io/olmtest/example-operator-bundle:0.3.0 +properties: + - type: olm.gvk + value: + group: example.com + kind: TestA + version: v1alpha1 + - type: olm.package + value: + packageName: packageA + version: 0.3.0 diff --git a/staging/operator-lifecycle-manager/test/e2e/testdata/fail-forward/multiple-bad-versions/example-operator.v0.2.1.yaml b/staging/operator-lifecycle-manager/test/e2e/testdata/fail-forward/multiple-bad-versions/example-operator.v0.2.1.yaml new file mode 100644 index 0000000000..0bdbedf454 --- /dev/null +++ b/staging/operator-lifecycle-manager/test/e2e/testdata/fail-forward/multiple-bad-versions/example-operator.v0.2.1.yaml @@ -0,0 +1,28 @@ +--- +schema: olm.package +name: packageA +defaultChannel: stable +--- +schema: olm.channel +package: packageA +name: stable +entries: + - name: example-operator.v0.2.1 + skips: + - example-operator.v0.1.0 + - example-operator.v0.2.0 +--- +schema: olm.bundle +name: example-operator.v0.2.1 +package: packageA +image: quay.io/olmtest/example-operator-bundle:non-existent-tags +properties: + - type: olm.gvk + value: + group: example.com + kind: TestA + version: v1alpha1 + - type: olm.package + value: + packageName: packageA + version: 0.2.1 diff --git a/staging/operator-lifecycle-manager/test/e2e/testdata/fail-forward/multiple-bad-versions/example-operator.v0.3.0.yaml b/staging/operator-lifecycle-manager/test/e2e/testdata/fail-forward/multiple-bad-versions/example-operator.v0.3.0.yaml new file mode 100644 index 0000000000..8a2cdb96b9 --- /dev/null +++ b/staging/operator-lifecycle-manager/test/e2e/testdata/fail-forward/multiple-bad-versions/example-operator.v0.3.0.yaml @@ -0,0 +1,26 @@ +--- +schema: olm.package +name: packageA +defaultChannel: stable +--- +schema: olm.channel +package: packageA +name: stable +entries: + - name: example-operator.v0.3.0 + skipRange: ">=0.1.0 <0.3.0" +--- +schema: olm.bundle +name: example-operator.v0.3.0 +package: packageA +image: quay.io/olmtest/example-operator-bundle:0.3.0 +properties: + - type: olm.gvk + value: + group: example.com + kind: TestA + version: v1alpha1 + - type: olm.package + value: + packageName: packageA + version: 0.3.0 diff --git a/staging/operator-lifecycle-manager/test/e2e/testdata/fail-forward/replaces/example-operator.v0.3.0.yaml b/staging/operator-lifecycle-manager/test/e2e/testdata/fail-forward/replaces/example-operator.v0.3.0.yaml new file mode 100644 index 0000000000..1f6189eb74 --- /dev/null +++ b/staging/operator-lifecycle-manager/test/e2e/testdata/fail-forward/replaces/example-operator.v0.3.0.yaml @@ -0,0 +1,26 @@ +--- +schema: olm.package +name: packageA +defaultChannel: stable +--- +schema: olm.channel +package: packageA +name: stable +entries: + - name: example-operator.v0.3.0 + replaces: example-operator.v0.2.0 +--- +schema: olm.bundle +name: example-operator.v0.3.0 +package: packageA +image: quay.io/olmtest/example-operator-bundle:0.3.0 +properties: + - type: olm.gvk + value: + group: example.com + kind: TestA + version: v1alpha1 + - type: olm.package + value: + packageName: packageA + version: 0.3.0 diff --git a/staging/operator-lifecycle-manager/test/e2e/testdata/fail-forward/skip-range/example-operator.v0.3.0.yaml b/staging/operator-lifecycle-manager/test/e2e/testdata/fail-forward/skip-range/example-operator.v0.3.0.yaml new file mode 100644 index 0000000000..8a2cdb96b9 --- /dev/null +++ b/staging/operator-lifecycle-manager/test/e2e/testdata/fail-forward/skip-range/example-operator.v0.3.0.yaml @@ -0,0 +1,26 @@ +--- +schema: olm.package +name: packageA +defaultChannel: stable +--- +schema: olm.channel +package: packageA +name: stable +entries: + - name: example-operator.v0.3.0 + skipRange: ">=0.1.0 <0.3.0" +--- +schema: olm.bundle +name: example-operator.v0.3.0 +package: packageA +image: quay.io/olmtest/example-operator-bundle:0.3.0 +properties: + - type: olm.gvk + value: + group: example.com + kind: TestA + version: v1alpha1 + - type: olm.package + value: + packageName: packageA + version: 0.3.0 diff --git a/staging/operator-lifecycle-manager/test/e2e/testdata/fail-forward/skips/example-operator.v0.3.0.yaml b/staging/operator-lifecycle-manager/test/e2e/testdata/fail-forward/skips/example-operator.v0.3.0.yaml new file mode 100644 index 0000000000..ff0c5754b4 --- /dev/null +++ b/staging/operator-lifecycle-manager/test/e2e/testdata/fail-forward/skips/example-operator.v0.3.0.yaml @@ -0,0 +1,28 @@ +--- +schema: olm.package +name: packageA +defaultChannel: stable +--- +schema: olm.channel +package: packageA +name: stable +entries: + - name: example-operator.v0.3.0 + skips: + - example-operator.v0.1.0 + - example-operator.v0.2.0 +--- +schema: olm.bundle +name: example-operator.v0.3.0 +package: packageA +image: quay.io/olmtest/example-operator-bundle:0.3.0 +properties: + - type: olm.gvk + value: + group: example.com + kind: TestA + version: v1alpha1 + - type: olm.package + value: + packageName: packageA + version: 0.3.0 diff --git a/staging/operator-lifecycle-manager/test/e2e/testdata/fbc_catalog.json b/staging/operator-lifecycle-manager/test/e2e/testdata/magiccatalog/fbc_catalog.json similarity index 100% rename from staging/operator-lifecycle-manager/test/e2e/testdata/fbc_catalog.json rename to staging/operator-lifecycle-manager/test/e2e/testdata/magiccatalog/fbc_catalog.json diff --git a/staging/operator-lifecycle-manager/test/e2e/testdata/magiccatalog/fbc_initial.yaml b/staging/operator-lifecycle-manager/test/e2e/testdata/magiccatalog/fbc_initial.yaml new file mode 100644 index 0000000000..abccd7dec4 --- /dev/null +++ b/staging/operator-lifecycle-manager/test/e2e/testdata/magiccatalog/fbc_initial.yaml @@ -0,0 +1,25 @@ +--- +schema: olm.package +name: packageA +defaultChannel: stable +--- +schema: olm.channel +package: packageA +name: stable +entries: + - name: busybox.v1.0.0 +--- +schema: olm.bundle +name: busybox.v1.0.0 +package: packageA +image: quay.io/olmtest/busybox-bundle:1.0.0 +properties: + - type: olm.gvk + value: + group: example.com + kind: TestA + version: v1alpha1 + - type: olm.package + value: + packageName: packageA + version: 1.0.0 diff --git a/staging/operator-lifecycle-manager/test/e2e/testdata/magiccatalog/fbc_updated.yaml b/staging/operator-lifecycle-manager/test/e2e/testdata/magiccatalog/fbc_updated.yaml new file mode 100644 index 0000000000..bd241dd7d3 --- /dev/null +++ b/staging/operator-lifecycle-manager/test/e2e/testdata/magiccatalog/fbc_updated.yaml @@ -0,0 +1,26 @@ +--- +schema: olm.package +name: packageA +defaultChannel: stable +--- +schema: olm.channel +package: packageA +name: stable +entries: + - name: busybox.v2.0.0 + replaces: busybox.v1.0.0 +--- +schema: olm.bundle +name: busybox.v2.0.0 +package: packageA +image: quay.io/olmtest/busybox-bundle:2.0.0 +properties: + - type: olm.gvk + value: + group: example.com + kind: TestA + version: v1alpha1 + - type: olm.package + value: + packageName: packageA + version: 1.0.0 diff --git a/staging/operator-lifecycle-manager/test/e2e/util.go b/staging/operator-lifecycle-manager/test/e2e/util.go index fe4c663ba1..228bda19a3 100644 --- a/staging/operator-lifecycle-manager/test/e2e/util.go +++ b/staging/operator-lifecycle-manager/test/e2e/util.go @@ -38,6 +38,7 @@ import ( operatorsv1 "github.com/operator-framework/api/pkg/operators/v1" operatorsv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1" "github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/clientset/versioned" + "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/bundle" "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/install" "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry" controllerclient "github.com/operator-framework/operator-lifecycle-manager/pkg/lib/controller-runtime/client" @@ -79,6 +80,33 @@ func newPMClient() pmversioned.Interface { return ctx.Ctx().PackageClient() } +// objectRefToNamespacedName is a helper function that's responsible for translating +// a *corev1.ObjectReference into a types.NamespacedName. +func objectRefToNamespacedName(ip *corev1.ObjectReference) types.NamespacedName { + return types.NamespacedName{ + Name: ip.Name, + Namespace: ip.Namespace, + } +} + +// addBundleUnpackTimeoutIPAnnotation is a helper function that's responsible for +// adding the "operatorframework.io/bundle-unpack-timeout" annotation to an InstallPlan +// resource. This allows you to have more control over the bundle unpack timeout when interacting +// with test InstallPlan resources. +func addBundleUnpackTimeoutIPAnnotation(ctx context.Context, c k8scontrollerclient.Client, ipNN types.NamespacedName, timeout string) { + Eventually(func() error { + ip := &operatorsv1alpha1.InstallPlan{} + if err := c.Get(ctx, ipNN, ip); err != nil { + return err + } + annotations := make(map[string]string) + annotations[bundle.BundleUnpackTimeoutAnnotationKey] = timeout + ip.SetAnnotations(annotations) + + return c.Update(ctx, ip) + }).Should(Succeed()) +} + type cleanupFunc func() // waitFor wraps wait.Pool with default polling parameters diff --git a/vendor/github.com/operator-framework/api/crds/operators.coreos.com_operatorgroups.yaml b/vendor/github.com/operator-framework/api/crds/operators.coreos.com_operatorgroups.yaml index 56ef0b5c4f..b62f3996a9 100644 --- a/vendor/github.com/operator-framework/api/crds/operators.coreos.com_operatorgroups.yaml +++ b/vendor/github.com/operator-framework/api/crds/operators.coreos.com_operatorgroups.yaml @@ -37,6 +37,8 @@ spec: spec: description: OperatorGroupSpec is the spec for an OperatorGroup resource. type: object + default: + upgradeStrategy: Default properties: selector: description: Selector selects the OperatorGroup's target namespaces. @@ -80,6 +82,13 @@ spec: items: type: string x-kubernetes-list-type: set + upgradeStrategy: + description: "UpgradeStrategy defines the upgrade strategy for operators in the namespace. There are currently two supported upgrade strategies: \n Default: OLM will only allow clusterServiceVersions to move to the replacing phase from the succeeded phase. This effectively means that OLM will not allow operators to move to the next version if an installation or upgrade has failed. \n TechPreviewUnsafeFailForward: OLM will allow clusterServiceVersions to move to the replacing phase from the succeeded phase or from the failed phase. Additionally, OLM will generate new installPlans when a subscription references a failed installPlan and the catalog has been updated with a new upgrade for the existing set of operators. \n WARNING: The TechPreviewUnsafeFailForward upgrade strategy is unsafe and may result in unexpected behavior or unrecoverable data loss unless you have deep understanding of the set of operators being managed in the namespace." + type: string + default: Default + enum: + - Default + - TechPreviewUnsafeFailForward status: description: OperatorGroupStatus is the status for an OperatorGroupResource. type: object diff --git a/vendor/github.com/operator-framework/api/crds/zz_defs.go b/vendor/github.com/operator-framework/api/crds/zz_defs.go index 31a7d8bb5e..46f68c053b 100644 --- a/vendor/github.com/operator-framework/api/crds/zz_defs.go +++ b/vendor/github.com/operator-framework/api/crds/zz_defs.go @@ -185,7 +185,7 @@ func operatorsCoreosCom_operatorconditionsYaml() (*asset, error) { return a, nil } -var _operatorsCoreosCom_operatorgroupsYaml = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xec\x5a\xe9\x6f\x1b\x37\x94\xff\xee\xbf\xe2\x41\x5d\x20\x76\x56\x1a\xc5\xee\xa2\xdb\x0a\x08\x02\x23\x69\x0a\x6f\x73\x18\xb1\xdb\x0f\x6b\x7b\xb7\xd4\xcc\xd3\x88\x35\x87\x9c\x92\x1c\xdb\x6a\x90\xff\x7d\xf1\x1e\x39\x87\x6e\x39\x49\xbb\xdd\x85\xfc\x25\xd1\xf0\x7a\xe7\xef\x1d\xa4\x28\xe5\xaf\x68\x9d\x34\x7a\x04\xa2\x94\xf8\xe0\x51\xd3\x2f\x97\xdc\x7e\xef\x12\x69\x86\x77\xc7\x07\xb7\x52\x67\x23\x78\x59\x39\x6f\x8a\x0f\xe8\x4c\x65\x53\x7c\x85\x13\xa9\xa5\x97\x46\x1f\x14\xe8\x45\x26\xbc\x18\x1d\x00\x08\xad\x8d\x17\xf4\xd9\xd1\x4f\x80\xd4\x68\x6f\x8d\x52\x68\x07\x39\xea\xe4\xb6\x1a\xe3\xb8\x92\x2a\x43\xcb\x9b\xd7\x47\xdf\x3d\x4b\xbe\x4f\x9e\x1d\x00\xa4\x16\x79\xf9\xa5\x2c\xd0\x79\x51\x94\x23\xd0\x95\x52\x07\x00\x5a\x14\x38\x02\x53\xa2\x15\xde\xd8\xdc\x9a\xaa\x74\x49\xfd\xd3\x25\xa9\xb1\x68\xe8\x9f\xe2\xc0\x95\x98\xd2\xe9\x3c\xa7\x5d\x32\x37\x27\xec\x57\x13\x29\x3c\xe6\xc6\xca\xfa\x37\xc0\x00\x8c\x2a\xf8\xff\x81\xf9\xf7\x71\x8f\x9f\x68\x4b\xfe\xae\xa4\xf3\x3f\x2f\x8f\xbd\x91\xce\xf3\x78\xa9\x2a\x2b\xd4\x22\xc1\x3c\xe4\xa6\xc6\xfa\x77\xed\xf1\x7c\x5c\x1e\x86\xa4\xce\x2b\x25\xec\xc2\xba\x03\x00\x97\x9a\x12\x47\xc0\xcb\x4a\x91\x62\x76\x00\x10\xc5\x17\xb7\x19\x44\x11\xdd\x1d\xc7\x5d\x5d\x3a\xc5\x42\xd4\x67\x00\x6d\xa9\x4f\xcf\xcf\x7e\xfd\xf6\x62\x61\x00\x20\x43\x97\x5a\x59\x7a\x56\xc6\x1c\x43\x20\x1d\xf8\x29\x42\xa5\xa5\x07\x33\x81\xa2\x52\x5e\x7a\xd4\x42\xa7\x33\x98\x18\x0b\xef\xdf\xbc\x85\x42\x68\x91\x63\xd6\x11\x35\x9c\x79\xd2\xbd\xf3\x56\x48\x1d\x76\x90\xda\x79\xa1\x14\xab\x97\x76\x6a\x26\x83\xd4\x20\xbd\x0b\x1a\x21\xde\xc0\x1b\x10\x40\x6a\x94\x13\x89\x19\x38\xe4\xa3\xbd\xb0\x39\xfa\x76\x9a\x4b\x3a\x1c\xf8\x19\x89\xc7\x8c\x7f\xc7\xd4\x77\x3e\x5b\xfc\xa3\x92\x16\xb3\x2e\xb3\x24\xaa\xda\x68\x3b\x9f\x4b\x4b\x14\xf9\x8e\x15\x84\xbf\x8e\x8b\xcc\x7d\x5f\x90\xda\x13\x12\x6d\x98\x07\x19\x79\x07\x06\xb6\xa3\x92\x88\x0d\x16\x3b\x73\x32\x95\x0e\x2c\x96\x16\x1d\x6a\xdf\x48\x44\xe8\xc8\x40\x02\x17\x68\x69\x21\xd9\x4a\xa5\x32\x12\xe5\x1d\x5a\x0f\x16\x53\x93\x6b\xf9\x67\xb3\x9b\x23\x59\xd1\x31\x4a\x78\x74\x1e\xa4\xf6\x68\xb5\x50\x70\x27\x54\x85\x7d\x10\x3a\x83\x42\xcc\xc0\x22\xed\x0b\x95\xee\xec\xc0\x53\x5c\x02\x6f\x8d\x25\xed\x4c\xcc\x08\xa6\xde\x97\x6e\x34\x1c\xe6\xd2\xd7\x00\x90\x9a\xa2\x20\xe5\xcf\x86\xec\xcb\x72\x5c\x91\xce\x86\x19\xde\xa1\x1a\x3a\x99\x0f\x84\x4d\xa7\xd2\x63\xea\x2b\x8b\x43\x51\xca\x01\x13\xab\x19\x04\x92\x22\xfb\xc6\x46\xc8\x70\x4f\x16\xc4\x17\x54\xe6\xbc\x95\x3a\x9f\x1b\x62\x9f\xdb\x28\x6b\xf2\x3c\xb2\x4c\x11\x97\x07\x5e\x5a\x91\xd2\x27\x92\xca\x87\x1f\x2f\x2e\xa1\x26\x20\x88\x3d\x48\xb8\x9d\xea\x5a\x61\x93\xa0\xa4\x9e\xa0\x0d\x33\x27\xd6\x14\xbc\x0b\xea\xac\x34\x52\x7b\xfe\x91\x2a\x89\xda\x83\xab\xc6\x05\x19\x2d\x19\x18\x3a\x4f\x7a\x48\xe0\x25\xe3\x1f\x8c\x11\xaa\x32\x13\x1e\xb3\x04\xce\x34\xbc\x14\x05\xaa\x97\xc2\xe1\x5f\x2e\x6a\x92\xa8\x1b\x90\xf8\x76\x17\x76\x17\xbe\x97\x17\x2c\x39\x14\x40\x0d\xaf\x6b\xb5\x33\x87\x1f\x17\x25\xa6\x35\x86\xd0\x4a\xc6\x0c\xa1\x17\x40\xa6\x56\x51\xb2\x2b\x11\xeb\xdd\x95\x49\x44\x85\xa9\x37\x76\x79\x64\x81\xd4\x8b\x38\x31\xae\x08\x64\xce\x91\xf6\xc4\x6d\xc6\x9d\x1d\x28\xdd\x46\x2d\x6b\x41\xf8\x74\xfa\xe3\x03\xd9\x64\x07\xd2\xb7\x50\xbf\xb8\x28\x78\x04\x45\x26\x42\x13\x25\xc6\xa8\x1a\x51\xd4\x48\x58\x04\x93\xbf\x9c\xe2\xdc\x17\x10\x16\xe1\xf4\xdd\x2b\xcc\x56\x31\xd7\x32\x28\xac\x15\xb3\x35\x33\xa4\xc7\x62\x2d\xe1\x0b\xa4\x9f\x6e\x20\x2f\x3a\x76\x3d\xe2\xa7\x82\x63\x89\xe7\x48\x12\x40\xab\x0f\x02\x6e\x71\x16\xf0\x8d\x60\x33\xaa\x2c\x4c\xb6\xc8\x68\xc8\xca\xbc\xc5\x19\x4f\x8a\x60\xb7\x96\xba\x2d\xfa\x0b\x7f\xab\xa3\xc9\xfc\xdf\x80\x8e\xdc\x38\x5e\x13\xbb\x76\xd2\x36\x63\x09\x7f\xb7\x38\xdb\x34\xbc\x20\x70\x92\x43\x74\xc3\x20\x79\xfa\xc0\xd2\x62\xcf\xac\x85\x2d\xca\x52\x49\x64\x34\xdb\xb8\xf7\x5a\x38\x99\xff\xab\x59\x7d\x04\xa1\x8d\x2a\x5b\x84\x0e\xca\x7e\xe2\x82\x62\xc9\xd2\xa7\xb2\x8c\x49\x42\x48\x0d\xea\x50\xf6\xab\x50\xb2\x93\x86\xb0\x55\x9f\xe9\x3e\xbc\x33\x9e\xfe\xf9\xf1\x41\x12\x54\x93\x3d\xbc\x32\xe8\xde\x19\xcf\x5f\xbe\x0a\xab\x81\x84\x47\x30\x1a\x16\xb0\xb1\xeb\xe0\x57\xc4\x49\x37\x9e\x51\x1a\x35\x61\xfd\x34\x42\x91\x8e\x22\x8a\xb1\x35\x47\x9c\x61\x84\x8d\xc2\x16\x45\xe5\x38\x00\x69\xa3\x07\x58\x94\x7e\xb6\x72\x8f\x28\x08\x63\xe7\xe4\xb0\x61\xbb\xb8\xd5\x25\xc5\xc5\x30\x12\x32\x18\x45\xa9\x28\x64\x15\x13\xcd\xd1\x98\x72\x69\x99\x42\x81\x36\x47\x28\x09\xa1\x76\x11\xef\x26\x5c\x09\x7f\x5b\xd0\x65\x47\x5d\x31\x64\xbe\x21\x07\x78\x04\xc4\x86\xf9\x01\x96\x0a\x51\x92\x9a\x3e\x12\xfa\xb0\xa4\x3e\x41\x29\x24\x65\xbc\xa7\x9c\xbd\x2b\x9c\x1b\x93\x9a\x65\xda\xdd\x86\x76\x90\x0e\x08\x4a\xee\x84\x22\xbc\x23\x4b\xd6\x80\x2a\xa0\x1f\x25\xd9\x0b\xc0\xde\x87\xfb\xa9\x71\x01\xcc\x26\x12\x15\xe7\x3e\xbd\x5b\x9c\xf5\xfa\x4b\xaa\xed\x9d\xe9\x5e\xc0\xc5\x25\x65\x36\x20\x6a\xb4\x9a\x41\x8f\xc7\x7a\x9f\x1f\x0b\x36\x82\xa5\xc8\x32\x2e\x0f\x85\x3a\xdf\x01\xcd\x36\xea\xcd\xa1\xbd\x93\x29\x9e\xa6\xa9\xa9\x34\x17\x4e\x3b\xc4\xf5\xc5\x25\x35\xf8\x89\xac\x90\x7a\xae\xb6\xe0\x99\x20\xc2\x54\xb8\x9f\xca\x74\x0a\xf7\x52\x29\x4e\xe3\x1c\x66\xa4\x9e\x0c\x4b\x65\x66\x8d\x9c\x0f\xdd\x51\xd0\x2c\xe5\x93\xb5\xec\xb9\x52\x5b\x9f\x1a\xac\x63\x8e\xd2\xff\xf4\xdc\x9a\x3b\x99\x61\x76\x7a\x7e\xb6\x52\x4a\xf3\xcc\xf1\x12\xf0\xa8\x94\xe3\xf2\x8b\x72\x4e\x6f\x62\xce\xb9\x32\x85\x29\x3b\xfb\x77\x8a\xf4\xb5\xc4\x8e\x8d\x51\x28\x96\xc7\x43\x2a\xd4\x14\xa1\xdb\x69\xbd\x5c\x58\x10\xe1\x0e\x1f\x4a\x25\x53\xe9\x6b\xfc\x6e\x73\x2b\xae\x67\x78\x11\x03\x97\xe4\x6c\xc0\xa1\xef\xb7\xb9\x9a\x74\x20\x73\x6d\xec\x6a\xfb\xdc\x8c\x27\x1b\x50\x64\x0b\x76\x3c\x0c\x6e\xab\x31\x5a\x8d\x1e\xdd\x80\x72\xac\x41\x5c\x80\x0b\xe9\xb1\x17\xbe\x5a\x3a\x62\x43\x82\xcc\xf3\x9b\x14\x39\xfc\x5a\x95\x24\x7f\x78\x7c\x8e\xbc\x3e\x5f\x19\x80\x12\xce\xff\x12\xaa\x94\x47\x64\xd6\xa9\xd1\xc1\xaf\xb7\xab\xfe\x65\x33\x75\x31\xc6\xad\xb2\xd0\x76\xe3\xaf\xaa\xd4\x39\x8a\x7a\x0d\x49\x2d\x14\x66\xe8\x85\x54\x41\xe2\x46\x23\x08\x82\x06\x5f\x53\x99\x56\xd6\x72\xb5\xe7\xc9\xb3\xea\xca\xfd\xf4\xfc\x0c\x1a\x6d\xc0\x60\x30\x08\x71\xd1\x79\x5b\xa5\x6c\xaf\x54\x85\xeb\x0c\x33\xde\x35\x93\x96\x4b\x6f\x47\x9b\xb7\x72\x88\x99\x57\x80\xf3\x52\xf8\x29\x24\x41\xf9\x49\x47\x14\x00\xaf\x8d\x05\x7c\x10\x45\xa9\xb0\xcf\x62\x80\xd7\xc6\x44\x9b\x09\x07\x7e\x84\xe1\x10\x3e\xb4\xc9\x12\x07\x84\x31\xe1\x5a\xc8\x95\xb8\xb3\x00\x13\x63\x48\xca\x5d\x7e\x12\x5a\xf8\xb3\x36\xf7\x7a\xd5\xd1\x7c\x96\xb0\x38\x82\xeb\xde\xe9\x9d\x90\x4a\x8c\x15\x5e\xf7\xfa\x70\xdd\x3b\xb7\x26\xe7\xd0\xa4\xf3\xeb\x18\x6b\xae\x7b\xaf\x30\xb7\x22\xc3\xec\xba\x47\xdb\xfe\x2b\x47\xfe\xb7\x94\x04\xfc\x8c\xb3\xe7\xbc\x59\xf3\xf9\x22\x64\x09\xb3\xe7\x21\x49\xa0\xef\xe4\x50\x97\xb3\x12\x9f\x53\x74\xac\x3f\xbc\x15\x65\xb3\xb8\x63\x4d\x57\x37\x54\xb3\xde\x1d\x27\xad\x3a\x7f\xfb\xdd\x19\x3d\xba\xee\xb5\xf4\xf7\x4d\x41\x66\x51\xfa\xd9\x75\x0f\xe6\x4e\x1d\x5d\xf7\xf8\xdc\xfa\x7b\x4d\xe4\xe8\xba\x47\x27\xd1\x67\x6b\xbc\x19\x57\x93\xd1\x75\x6f\x3c\xf3\xe8\xfa\xc7\x7d\x8b\x65\x9f\x40\xea\x79\x7b\xc2\x75\xef\x37\xb8\xd6\x44\xac\xf1\x53\xb4\x41\x93\x0e\x3e\xf5\x36\xa0\xcb\x86\x90\xb9\xad\xb6\x08\x1e\x7b\x69\x85\x76\xb2\xee\x90\xae\x9d\x5a\xa0\x73\x22\x5f\x3f\x6e\x51\xb8\x95\xf0\x1f\x86\x83\x35\xac\x1d\x26\x5e\x56\x0e\x6e\x2f\x5c\x96\x79\xd8\xb1\x60\x5c\x5e\xd8\x96\x33\xce\x83\xa7\x0f\xec\xb1\x8d\x4d\xf8\x66\x36\x39\xa2\x35\x05\xfb\x77\x04\x58\x4e\xb9\x58\x6f\x31\xa9\x8d\x8d\xb6\x31\xc2\xfd\x14\x75\x6c\x79\x66\x68\xd5\x8c\x32\xdb\x76\xd7\x74\x2a\x74\x8e\x59\x02\x21\xad\x16\xec\xef\x14\x80\x6f\xc9\x91\x38\x1d\xd3\x50\xb9\xba\x01\xc5\x74\x35\x3b\x12\x70\x04\x87\x8f\xdb\x30\x32\xa6\x29\x96\x9e\xbc\x6b\x5b\x75\xba\xa5\x06\x99\x18\x5b\x08\x3f\x02\xc2\xf4\x81\x5f\x6f\x1e\xd1\x38\x76\x14\x7c\x9c\x1d\xb2\xdf\x69\x55\x08\x4d\xd6\x93\x11\xbd\xed\x98\xce\x64\x2a\xb8\xeb\x56\xe3\xa9\x18\x9b\x2a\x20\x5c\xab\x87\x28\xea\x42\xcc\x48\xce\x94\x06\x90\x7f\x46\xb6\xbe\x90\xf9\x42\x3c\xbc\x41\x9d\xfb\xe9\x08\xbe\x3d\xf9\xf7\xef\xbe\x5f\x33\x31\x00\x23\x66\x3f\xa1\xa6\xf8\xb3\xa2\xa9\xbb\x46\x0c\xcb\x0b\xbb\x05\x2a\xf1\x99\xd4\x9d\xb4\x24\x6f\xe7\x34\x15\x76\x6b\x41\xf7\x82\x13\x1a\x18\x0b\x4a\x2e\xab\x92\xe4\x42\x28\xcf\xfd\x71\x9d\x62\x1f\xe4\x64\xf5\x66\xb2\x01\x70\x35\x83\xe3\x93\x3e\x8c\xa3\x88\x97\xe1\xfb\xea\xe1\x26\x59\x41\xb2\x74\xf0\x43\x7f\x81\x1e\xca\x61\x2b\x8e\x78\x9c\x3e\xde\x4b\x3f\x05\x8b\x21\x0c\xc6\xe6\xf2\x8a\x30\x88\x0d\xbd\xdb\x14\x47\xc1\x30\xc7\xf5\xdd\x8e\xda\x6c\xa5\xf6\xdf\xfd\xdb\x7a\xfd\x4a\x2d\x8b\xaa\x18\xc1\xb3\x35\x53\x02\xa4\xed\xa8\xcd\x30\xb9\xcd\x02\x04\x41\x57\x6e\x45\x51\x70\x6a\x2d\x33\xd4\x9e\xea\x03\xdb\x35\x6d\xcf\x75\x12\x2f\x9c\x70\xab\xa9\x23\xc5\x27\x2e\xe2\x50\xc7\xd8\xcf\xad\xc9\xaa\x14\x2d\x47\xe0\x58\x71\xa4\x5d\x80\x9a\x95\x18\xbc\x21\xdc\x17\x50\x56\x8c\xa9\x6f\x3a\xf3\xa1\x79\x8f\x42\x4b\x9d\xbb\x78\xa4\x74\x01\x40\x42\xd4\xbd\x9f\x22\x87\x9e\xb9\x4a\x8f\xa9\x72\x32\x43\x8b\x19\x08\xc8\x2b\x61\x85\xf6\x88\x19\xc1\x4f\xa8\xf6\x42\xb7\xbc\x85\x3c\xd1\xf6\xa8\x6b\x6f\x0c\xae\x1a\xc0\x8a\x48\x8c\x7d\xed\xd0\x07\xf8\x6a\xae\x7a\xfc\xec\x64\xa3\xca\x9b\x79\xeb\x7b\x65\xc2\x7b\xb4\x7a\x04\xff\x75\x75\x3a\xf8\x4f\x31\xf8\xf3\xe6\x30\xfe\xe7\xd9\xe0\x87\xff\xee\x8f\x6e\x9e\x76\x7e\xde\x1c\xbd\xf8\x97\x35\x3b\xad\x4e\xdb\xd7\x98\x4f\x0c\x22\x75\x92\x58\x6b\xb4\xcf\x11\xc6\x4c\xe0\xd2\x56\xd8\x87\xd7\x42\x39\xec\xc3\x2f\x9a\x43\xc3\x17\x0a\x0d\x75\x55\x6c\x6e\x3b\xf6\xe8\xd4\xd5\xc9\x47\x33\x85\x49\xda\x3c\x27\x92\xbb\xa9\xf2\xdf\x4d\x48\x9c\xb6\x99\x49\x17\x69\x3a\x77\x21\xc0\x88\x47\x69\x69\x12\xd3\xdb\x24\x35\xc5\xb0\x73\x57\x42\x79\xf5\x5b\xa1\x67\xd0\xc2\x5a\x48\x4a\x17\x2d\xdd\x79\xc2\x26\x91\x5a\xe3\x5c\x73\x93\xe0\x40\xc9\x5b\x84\x26\x73\x0d\x60\x39\xc6\x54\x70\x22\x6e\xc7\xd2\x5b\x61\x67\x9d\xba\x03\x52\xa1\x63\xcd\x3f\xa9\x14\x1c\x3a\x44\x48\xb4\xc9\x70\x19\x5d\x8f\x02\x86\x8a\xb1\x54\xd2\xcf\x42\x83\x20\x35\x7a\xa2\x64\xcc\xff\x8b\xd2\x58\x2f\xb4\xaf\x9b\x2b\x39\x3e\x50\x29\xcb\x7d\x9d\x50\x04\x1f\x66\xda\x1d\x1f\x9f\x7c\x7b\x51\x8d\x33\x53\x08\xa9\x5f\x17\x7e\x78\xf4\xe2\xf0\x8f\x4a\x28\xee\x4c\x50\xcd\xfc\xba\xf0\x47\x5f\x2f\x2c\x1e\x7f\xb7\x83\x17\x1d\x5e\x05\x5f\xb9\x39\xbc\x1a\xc4\xff\x3d\xad\x3f\x1d\xbd\x38\xbc\x4e\x36\x8e\x1f\x3d\x25\x1e\x3a\x1e\x78\x73\x35\x68\xdd\x2f\xb9\x79\x7a\xf4\xa2\x33\x76\xb4\xec\x8c\x9d\xaa\x74\x6b\x81\xf9\xa6\x9d\x1b\xb2\x13\x5f\x3f\x1a\xa8\x3d\x73\x3e\x35\x5c\x2c\x39\xa3\x17\x53\x3c\x8e\xdb\x3c\xba\x7b\xb3\x4b\xd2\xa5\x77\xef\x96\xcc\xf7\x49\x42\x63\x7e\xf5\xd5\x77\x13\x81\xe6\x98\xfa\x27\xf6\x43\x60\xa9\x83\xf7\x01\x27\x8f\x6c\xe0\x7d\xc0\x09\x58\x9c\xa0\x45\x9d\x62\x2d\x98\xf9\xbe\x5d\xbc\xd6\x6d\x1a\x7b\x7f\xc1\x1d\xdd\xfa\x87\x00\x2b\x59\xa0\x64\x3f\x5e\xfe\xd7\xf6\x18\x79\x58\x7b\xe1\xb0\xd5\xa5\x39\x1e\x9f\x0b\x3f\xdd\x89\x82\x27\x67\x51\x6c\xdc\x9d\xe7\xfb\x92\x52\x62\x8a\x73\x6f\x0d\x38\x8f\x43\x91\xc5\x8f\x94\xf8\x58\x8c\x63\xfd\x90\x71\xc4\x3b\x89\xf6\x2d\x02\x25\x4d\x20\x08\x88\x65\x06\xff\x71\xf1\xfe\xdd\xf0\x27\x13\x73\x05\xaa\x66\x5c\xf0\x2d\xee\x26\xf7\xc1\x55\xe9\x14\x84\x23\xd2\xa8\xbe\xbd\xe0\xd6\x43\x21\xb4\x9c\xa0\xf3\x49\xdc\x0d\xad\xbb\x3a\xb9\x49\xe6\xdb\x1d\x32\x5e\x5c\xd4\x37\xf6\xd1\x00\xd8\x37\x88\x99\x66\x2d\x27\xad\x4c\x52\x69\xb2\x48\xf4\x3d\x13\xeb\xc5\x2d\x82\x89\xc4\x56\xc8\x41\x61\x04\x3d\x32\x93\xce\xd1\x1f\xc9\xb1\x3e\xf5\xe0\xf0\x7e\x8a\x16\xa1\x47\x3f\x7b\xe1\xc0\xe6\x81\x05\x7d\xeb\x44\xfc\x78\x70\xc8\xef\xad\xcc\x73\x4e\xb7\xf8\xb5\xc0\x1d\x6a\x7f\xc4\xf1\x6d\x02\xda\x74\x26\xeb\xd8\x87\x6e\xbb\xcf\x8b\x84\x5c\x9d\xdc\xf4\xe0\x70\x9e\x2f\x4a\x41\xf1\x01\x4e\x9a\x8e\x73\x69\xb2\xa3\xba\x6a\x9d\x69\x2f\x1e\xb8\x30\x98\x1a\x87\x3a\x74\xf6\xbd\x81\xa9\xb8\x43\x70\x86\x8a\x4f\x54\x6a\x10\x12\xcc\x0c\xee\x43\x03\xae\x16\x65\xb8\xb4\x29\x85\xf5\x0b\xcf\x4f\x2e\xdf\xbf\x7a\x3f\x0a\xa7\x91\xda\x72\x5d\x57\xb9\x13\xa9\x85\x8a\xb7\x0b\x4d\x7e\x48\x84\x54\x41\x49\xde\xc4\xd2\xb6\xbe\xf9\x98\x54\xbe\xb2\x98\x2c\x3e\x47\xd8\xd9\xe2\x57\xbd\x05\x59\x6d\xec\xfc\x26\x64\xd1\xd1\xfe\x17\x5f\x5c\xec\xcc\xa2\x5e\x73\xa3\xb1\xcc\xe2\xbb\x8e\x0d\x6e\x64\xb1\x85\x66\xe2\x32\x33\xa9\x23\x06\x53\x2c\xbd\x1b\x9a\x3b\x82\x4e\xbc\x1f\xde\x1b\x7b\x2b\x75\x3e\x20\x23\x1b\x04\xcd\xbb\x21\x87\x98\xe1\x37\xfc\xcf\x17\x71\xc4\x71\x6a\x77\xb6\xc2\xc3\xaf\xbf\x81\x37\x0e\x9f\xc3\xcf\x66\xad\xce\x2f\x1f\x13\x09\x9e\x5c\xd4\xc5\xdf\xc2\x6a\x72\x97\x70\xe1\x14\x5f\x84\x75\x10\xae\x10\x59\x80\x40\xa1\x67\x7f\xb9\x19\x93\x00\xb9\xc6\x4f\x67\x83\xf8\x64\x73\x20\x74\x36\x68\xf2\xeb\x74\xf6\xd9\x12\xab\xe4\x8e\x0e\xfc\xcb\xd9\xab\xbf\xc7\xb8\x2b\xf9\x28\x6f\x0d\x5d\x94\x11\x78\x5b\xd5\xd9\x9d\xf3\xc6\x8a\x1c\xe7\xbf\x55\xe3\xa6\xf8\x68\x19\x8e\x75\x25\x7c\xfc\xc4\x9f\xda\x47\x9a\x42\x95\x53\x71\x52\xaf\xdd\x3f\xd5\xdc\x3f\xd5\xdc\x3f\xd5\xdc\x3f\xd5\xdc\x28\xec\xfd\x53\xcd\xfd\x53\xcd\xfd\x53\xcd\xfd\x53\xcd\xfd\x53\xcd\x2f\x63\x75\xff\x54\x73\xff\x54\x73\xff\x54\xb3\xf9\xdb\x3f\xd5\xdc\x91\xb9\xfd\x53\xcd\x7f\xfa\x53\xcd\xff\xdf\x8f\x2f\xf7\x97\x63\xff\x37\x2e\xc7\xf6\xd7\x5d\xfb\xeb\xae\xfd\x75\xd7\xfe\xba\xeb\x33\x2c\x7e\x7f\xdd\xb5\xbf\xee\xda\x5f\x77\xed\xaf\xbb\xfe\xa1\xd7\x5d\x13\xa1\xdc\xce\xf7\x5d\xff\x13\x00\x00\xff\xff\xc7\xa0\x23\x13\x5c\x46\x00\x00") +var _operatorsCoreosCom_operatorgroupsYaml = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xec\x5a\x79\x6f\x23\x37\x96\xff\xbf\x3f\xc5\x83\x66\x81\xb6\xb3\x3a\xda\x9d\x45\x36\x23\x20\x08\x8c\xee\x74\xe0\x4d\xb7\xdb\x68\xbb\xb3\xc0\x5a\xde\x1d\xaa\xea\x55\x89\x63\x16\x59\x43\xb2\x24\x6b\x82\x7c\xf7\xc5\x7b\x64\x1d\xba\xe5\x1c\xb3\x93\x85\xea\x1f\x5b\x3c\xdf\xc1\xf7\x7b\x07\x29\x4a\xf9\x23\x5a\x27\x8d\x1e\x83\x28\x25\x3e\x79\xd4\xf4\xcb\x0d\x1f\xbf\x76\x43\x69\x46\xf3\x8b\x17\x8f\x52\xa7\x63\x78\x53\x39\x6f\x8a\x4f\xe8\x4c\x65\x13\x7c\x8b\x99\xd4\xd2\x4b\xa3\x5f\x14\xe8\x45\x2a\xbc\x18\xbf\x00\x10\x5a\x1b\x2f\xa8\xd9\xd1\x4f\x80\xc4\x68\x6f\x8d\x52\x68\x07\x39\xea\xe1\x63\x35\xc5\x69\x25\x55\x8a\x96\x17\xaf\xb7\x9e\xbf\x1a\x7e\x3d\x7c\xf5\x02\x20\xb1\xc8\xd3\xef\x64\x81\xce\x8b\xa2\x1c\x83\xae\x94\x7a\x01\xa0\x45\x81\x63\x30\x25\x5a\xe1\x8d\xcd\xad\xa9\x4a\x37\xac\x7f\xba\x61\x62\x2c\x1a\xfa\x53\xbc\x70\x25\x26\xb4\x3b\x8f\x69\xa7\xac\x8c\x09\xeb\xd5\x44\x0a\x8f\xb9\xb1\xb2\xfe\x0d\x30\x00\xa3\x0a\xfe\x3f\x30\xff\x31\xae\xf1\x3d\x2d\xc9\xed\x4a\x3a\xff\xc3\x66\xdf\x7b\xe9\x3c\xf7\x97\xaa\xb2\x42\xad\x13\xcc\x5d\x6e\x66\xac\xbf\x6e\xb7\xe7\xed\xf2\xd0\x25\x75\x5e\x29\x61\xd7\xe6\xbd\x00\x70\x89\x29\x71\x0c\x3c\xad\x14\x09\xa6\x2f\x00\xa2\xf8\xe2\x32\x83\x28\xa2\xf9\x45\x5c\xd5\x25\x33\x2c\x44\xbd\x07\xd0\x92\xfa\xf2\xe6\xea\xc7\x2f\x6f\xd7\x3a\x00\x52\x74\x89\x95\xa5\x67\x65\xac\x30\x04\xd2\x81\x9f\x21\x54\x5a\x7a\x30\x19\x14\x95\xf2\xd2\xa3\x16\x3a\x59\x42\x66\x2c\x7c\x7c\xff\x01\x0a\xa1\x45\x8e\x69\x47\xd4\x70\xe5\x49\xf7\xce\x5b\x21\x75\x58\x41\x6a\xe7\x85\x52\xac\x5e\x5a\xa9\x19\x0c\x52\x83\xf4\x2e\x68\x84\x78\x03\x6f\x40\x00\xa9\x51\x66\x12\x53\x70\xc8\x5b\x7b\x61\x73\xf4\xed\x30\x37\xec\x70\xe0\x97\x24\x1e\x33\xfd\x2b\x26\xbe\xd3\x6c\xf1\x6f\x95\xb4\x98\x76\x99\x25\x51\xd5\x87\xb6\xd3\x5c\x5a\xa2\xc8\x77\x4e\x41\xf8\x3a\x26\xb2\xd2\xbe\x26\xb5\x97\x24\xda\x30\x0e\x52\xb2\x0e\x0c\x6c\x47\x25\x11\x1b\x2c\x76\xe6\x64\x26\x1d\x58\x2c\x2d\x3a\xd4\xbe\x91\x88\xd0\x91\x81\x21\xdc\xa2\xa5\x89\x74\x56\x2a\x95\x92\x28\xe7\x68\x3d\x58\x4c\x4c\xae\xe5\xdf\x9b\xd5\x1c\xc9\x8a\xb6\x51\xc2\xa3\xf3\x20\xb5\x47\xab\x85\x82\xb9\x50\x15\xf6\x41\xe8\x14\x0a\xb1\x04\x8b\xb4\x2e\x54\xba\xb3\x02\x0f\x71\x43\xf8\x60\x2c\x69\x27\x33\x63\x98\x79\x5f\xba\xf1\x68\x94\x4b\x5f\x03\x40\x62\x8a\x82\x94\xbf\x1c\xb1\x2d\xcb\x69\x45\x3a\x1b\xa5\x38\x47\x35\x72\x32\x1f\x08\x9b\xcc\xa4\xc7\xc4\x57\x16\x47\xa2\x94\x03\x26\x56\x33\x08\x0c\x8b\xf4\x4f\x36\x42\x86\x7b\xb9\x26\xbe\xa0\x32\xe7\xad\xd4\xf9\x4a\x17\xdb\xdc\x5e\x59\x93\xe5\xd1\xc9\x14\x71\x7a\xe0\xa5\x15\x29\x35\x91\x54\x3e\x7d\x77\x7b\x07\x35\x01\x41\xec\x41\xc2\xed\x50\xd7\x0a\x9b\x04\x25\x75\x86\x36\x8c\xcc\xac\x29\x78\x15\xd4\x69\x69\xa4\xf6\xfc\x23\x51\x12\xb5\x07\x57\x4d\x0b\x3a\xb4\x74\xc0\xd0\x79\xd2\xc3\x10\xde\x30\xfe\xc1\x14\xa1\x2a\x53\xe1\x31\x1d\xc2\x95\x86\x37\xa2\x40\xf5\x46\x38\xfc\xdd\x45\x4d\x12\x75\x03\x12\xdf\xf1\xc2\xee\xc2\xf7\xe6\x84\x0d\x83\x02\xa8\xe1\x75\xa7\x76\x56\xf0\xe3\xb6\xc4\xa4\xc6\x10\x9a\xc9\x98\x21\xf4\x1a\xc8\xd4\x2a\x1a\x1e\x4b\x04\x6d\x99\x89\x4a\xf9\x75\x4a\x00\xaa\x32\xb7\x22\xc5\x5b\x6f\x09\xd6\x97\x63\x78\x1b\x46\xae\x0d\xdc\x65\xee\xcc\x22\x2a\x4c\xbc\xb1\x9b\x3d\x6b\xac\xde\xc6\x81\x71\x46\x60\x73\x85\xb5\x97\x6e\x3f\x6e\x1d\xc1\xe9\x21\x6a\xe9\x2b\x84\x4f\x66\xdf\x3d\xd1\x99\xee\xb8\x84\x03\xd4\xaf\x4f\x0a\x16\x45\x9e\x8d\xd0\x48\x89\x29\xaa\x46\x14\x35\x92\x16\xc1\x64\xee\x66\xb8\xd2\x02\xc2\x22\x5c\x5e\xbf\xc5\x74\x1b\x73\x2d\x83\xc2\x5a\xb1\xdc\x31\x42\x7a\x2c\x76\x12\xbe\x46\xfa\xe5\x1e\xf2\x22\x30\xd4\x3d\x7e\x26\xd8\x17\x79\xf6\x44\x01\xf4\xfa\x20\xe0\x11\x97\x01\x1f\x09\x76\xa3\xca\xc2\x60\x8b\x8c\xa6\xac\xcc\x47\x5c\xf2\xa0\x08\x96\x3b\xa9\x3b\xa0\xbf\xf0\x6d\xf7\x46\xab\xdf\x80\xb6\xdc\xdb\x5f\x13\xbb\x73\xd0\xa1\xc3\x12\xbe\x47\x5c\xee\xeb\x5e\x13\x38\xc9\x21\x9a\x71\x90\x3c\x35\xb0\xb4\xd8\xb2\x6b\x61\x8b\xb2\x54\x12\x19\x0d\xf7\xae\xbd\x13\x8e\x56\xbf\x9a\xd5\x67\x10\xda\xa8\xb2\x45\xf8\xa0\xec\x97\x2e\x28\x96\x4e\xfa\x4c\x96\x31\xc8\x08\xa1\x45\xed\x0a\x7f\x14\x4a\x76\xc2\x18\x3e\xd5\x57\xba\x0f\xd7\xc6\xd3\x9f\xef\x9e\x24\x41\x3d\x9d\x87\xb7\x06\xdd\xb5\xf1\xdc\xf2\x9b\xb0\x1a\x48\x78\x06\xa3\x61\x02\x1f\x76\x1d\xec\x8a\x38\xe9\xfa\x43\x0a\xc3\x32\xd6\x4f\x23\x14\xe9\xc8\x23\x19\x5b\x73\xc4\x11\x4a\x58\x28\x2c\x51\x54\x8e\x1d\x98\x36\x7a\x80\x45\xe9\x97\x5b\xd7\x88\x82\x30\x76\x45\x0e\x7b\x96\x8b\x4b\xdd\x91\x5f\x0d\x3d\x21\x02\x52\x14\xca\x42\x5a\x31\xd1\xec\xcd\x09\xb4\x65\x02\x05\xda\x1c\xa1\x24\x84\x3a\x46\xbc\xfb\x70\x25\x7c\x07\xd0\xe5\x48\x5d\x31\x64\xbe\x27\x03\x78\x06\xc4\x86\xf1\x01\x96\x0a\x51\x92\x9a\x7e\x22\xf4\x61\x49\xfd\x0c\xa5\x90\x14\x31\x5f\x72\xf4\xaf\x70\xa5\x4f\x6a\x96\x69\x77\x19\x5a\x41\x3a\x20\x28\x99\x0b\x45\x78\x47\x27\x59\x03\xaa\x80\x7e\x14\xa4\xaf\x01\x7b\x1f\x16\x33\xe3\x02\x98\x65\x12\x15\xc7\x4e\xbd\x47\x5c\xf6\xfa\x1b\xaa\xed\x5d\xe9\x5e\xc0\xc5\x0d\x65\x36\x20\x6a\xb4\x5a\x42\x8f\xfb\x7a\xbf\xdc\x17\xec\x05\x4b\x91\xa6\x9c\x5e\x0a\x75\x73\x04\x9a\xed\xd5\x9b\x43\x3b\x97\x09\x5e\x26\x89\xa9\x34\x27\x5e\x47\xf8\xf5\xf5\x29\x35\xf8\x89\xb4\x90\x7a\x25\x37\xe1\x91\x20\xc2\x50\x58\xcc\x64\x32\x83\x85\x54\x8a\xc3\x40\x87\x29\xa9\x27\xc5\x52\x99\x65\x23\xe7\x33\x77\x1e\x34\x4b\xf1\x68\x2d\x7b\xce\xf4\x76\x87\x06\xbb\x98\xa3\xf4\x21\xb9\xb1\x66\x2e\x53\x4c\x2f\x6f\xae\xb6\x4a\x69\x95\x39\x9e\x02\x1e\x95\x72\x9c\xbe\x51\xcc\xea\x4d\x8c\x59\xb7\x86\x30\x65\x67\xfd\x4e\x92\xbf\x93\xd8\xa9\x31\x0a\xc5\x66\x7f\x08\x85\x9a\x24\xf6\x30\xad\x77\x6b\x13\x22\xdc\xe1\x53\xa9\x64\x22\x7d\x8d\xdf\x6d\x6c\xc5\xf9\x10\x4f\x62\xe0\x92\x1c\x0d\x38\xf4\xfd\x36\x56\x93\x0e\x64\xae\x8d\xdd\x7e\x3e\xf7\xe3\xc9\x1e\x14\x39\x80\x1d\x4f\x83\xc7\x6a\x8a\x56\xa3\x47\x37\xa0\x18\x6b\x10\x27\xe0\xa6\x09\xac\x87\xb0\x87\xa4\xd4\xfb\xbc\x3a\x61\x25\x01\x8d\x8b\xd5\xd8\x1a\xb2\xf6\x95\xe4\x9b\x46\x35\x12\x64\x7b\xb6\xc8\x46\x9c\x54\xd6\xa2\xf6\x6a\x09\x7e\x61\xc0\x55\x65\x69\xac\xc7\x74\x7d\x49\x32\x4d\x98\xe8\x3a\xd0\x1e\xf3\xa1\x62\x13\x60\xa0\x10\x4a\x99\x05\x24\xaa\x72\x1e\x6d\xb4\xac\x98\x29\xb3\xba\x0a\x33\xc7\x3a\x8d\x0d\x2e\x81\x9c\x41\x39\x13\x0e\xdb\x1c\xcc\x55\x49\x82\x98\x62\x1a\x3a\xa2\x2b\xc1\x2c\xc3\xc4\xcb\x39\xaa\x25\x14\x28\xb8\xd2\x20\x7c\xbb\x3f\x9d\xec\xb0\x7d\xcb\xf0\xda\x8e\x1a\x9f\x7c\x9d\xa4\x83\xe4\x24\x7c\xb5\x52\x61\x1b\x76\x67\xc2\x41\x26\xa4\xa2\xbc\x6e\xa2\xe1\x0e\x93\xd9\x8d\xc5\xb9\xc4\xc5\x67\xed\x44\x86\xef\x84\x54\xef\x8c\x5d\x08\x9b\x76\x64\xf0\x7b\xb0\x4f\x54\x35\x7d\x81\xa4\x5a\x2e\x97\x0d\x70\xaa\x65\xbf\xa5\x22\x47\x4d\x02\x20\x7e\x17\x35\x83\x37\x8a\x24\xb6\x98\xa1\x26\xd7\x5b\x4d\x9b\x13\x05\x16\x33\xb4\xa8\xc9\x9e\x44\xbd\x7e\x67\x52\xe3\x1e\x12\xe1\x85\x32\x39\x4b\x66\x8a\xa8\xeb\xbc\x17\x16\xd2\xcf\x40\xf0\x66\xb5\xf4\x32\x0e\xaf\x11\x90\x42\x05\x62\x31\xda\x6e\xa7\x68\x34\xd1\xf0\x9f\x97\x9f\xae\xaf\xae\xbf\x1f\xb3\x57\xd9\x27\xe1\xcd\x73\x2d\x1d\x54\x3c\xaa\x53\xf5\x70\x95\xf2\x74\xc4\x2b\x8d\x4f\x25\x26\x44\xda\x14\x67\x62\x2e\xc9\x06\x6c\xac\x87\xcc\xd1\x8a\xa9\x42\xa0\x34\x18\x94\x71\xb4\x8e\x42\xe7\x60\x69\x2a\x98\x89\x39\x42\x8a\x58\x42\xa5\x53\xb4\xce\x0b\x9d\x12\xf5\x26\x8b\x91\xef\x2a\x13\x30\x45\xea\xad\x2b\x62\x1b\xd6\xd5\x7b\x2e\xc0\xb7\x99\xee\x8e\x44\x96\x3e\xd4\x55\xb1\x1d\x95\x06\x7b\x66\x51\xef\x3e\x11\xaf\xa6\xfd\x5e\xf8\x6a\x03\xfa\xf6\x24\xfe\x3c\xbe\x49\xfd\xc3\xaf\x6d\xc9\xff\xa7\xe7\xe7\xfe\xbb\xf3\xa8\x01\x28\xe1\xfc\xe7\x70\x0a\x9f\x91\xf1\x27\x46\x07\xb3\x39\xec\x92\xde\x34\x43\xd7\x63\xef\x6d\x9e\xb3\x5d\xf8\x37\x75\x36\xab\xf0\xdf\x90\xd4\x86\x68\x29\x7a\x21\x55\x90\xb8\xd1\x08\x82\x42\x16\x5f\x53\x19\x81\x9d\xd5\x82\x4d\x45\xf2\xf2\xe6\x0a\x1a\x6d\xc0\x60\x30\x08\x20\xeb\xbc\xad\x12\xf6\xa3\x52\x7b\xd4\x04\x42\xb4\x6a\x2a\x2d\x97\x14\x1d\x2d\xde\xca\x21\x66\x84\x21\xcc\x2c\x85\x9f\xc1\x30\x28\x7f\xd8\x11\x05\xc0\x3b\x63\x01\x9f\x44\x51\x2a\xec\xb3\x18\xe0\x9d\x31\xf1\xcc\x84\x0d\x7f\x82\xd1\x08\x3e\xb5\x49\x1c\x07\xaa\x53\x8a\xb7\x42\x0e\xc7\x15\x53\xc8\x8c\x21\x29\x77\xf9\x19\xd2\xc4\x1f\xb4\x59\xe8\x6d\x5b\xf3\x5e\xc2\xe2\x18\x26\xbd\xcb\xb9\x90\x8a\x4c\x7f\xd2\xeb\xc3\xa4\x77\x63\x4d\xce\x21\xb3\xce\x27\x31\x06\x9e\xf4\xde\x22\xc3\x4c\x3a\xe9\xd1\xb2\xff\xca\x19\xc9\x07\x4a\x4e\x7e\xc0\xe5\x37\xbc\x58\xd3\x5c\xbb\xdf\x6f\x42\xf2\x42\xed\xe4\xe8\xef\x96\x25\x7e\x43\x51\x7b\xdd\xf0\x41\x94\xcd\xe4\xce\x69\xba\x7f\x28\xd0\x8b\xf9\xc5\xb0\x55\xe7\x5f\xfe\xea\x8c\x1e\x4f\x7a\x2d\xfd\x7d\x53\xd0\xb1\x28\xfd\x72\xd2\x83\x95\x5d\xc7\x93\x1e\xef\x5b\xb7\xd7\x44\x8e\x27\x3d\xda\x89\x9a\xad\xf1\x66\x5a\x65\xe3\x49\x6f\xba\xf4\xe8\xfa\x17\x7d\x8b\x65\x9f\xc0\xe9\x9b\x76\x87\x49\xef\x2f\x04\xc4\xa3\x11\x18\x3f\x43\x1b\x34\xe9\xe0\xe7\x6d\xc8\x75\x44\x28\x7f\xa8\xe6\x11\x2c\xf6\xce\x0a\xed\x64\x7d\xf3\xb3\x73\x68\x81\xce\x89\x7c\x77\xbf\x45\xe1\xb6\x86\xa5\xa1\x3b\x9c\x86\x9d\xdd\xc4\xcb\xd6\xce\xc3\x05\x95\x4d\x1e\x8e\x2c\x64\x6d\x4e\x6c\xcb\x2c\xce\x83\xa7\x06\xb6\xd8\xe6\x4c\xf8\x66\x34\x19\x22\x05\x01\x64\xdf\x11\x60\x39\x15\x64\xbd\xc5\x08\x29\x5e\x20\x4c\x31\xf8\xf9\x70\x95\x93\xa2\x55\x4b\x72\x53\xed\xaa\xc9\x4c\xe8\x9c\x02\x9b\x90\xee\x0b\xb6\x77\x0a\x9f\x1e\xc9\x90\x38\x4d\xd4\x50\xb9\xba\xb0\xce\x74\x35\x2b\x12\x70\x04\x83\x8f\xcb\x30\x32\x26\x09\x96\x9e\xac\xeb\x50\xd5\xec\x40\x6d\x24\x33\xb6\x10\x7e\x4c\xee\x19\x07\x7e\xf7\xf1\x88\x87\xe3\x48\xc1\xc7\xd1\x21\x2b\x9f\x55\x85\xa0\xa8\x47\xa4\x1c\x08\x34\x7d\x3a\x95\x89\xe0\x60\xa5\xc6\x53\x31\x35\x55\x40\xb8\x56\x0f\x51\xd4\x14\x71\x4c\x91\xd3\x13\xb2\xcf\xc8\xd6\xaf\x64\xbe\x10\x4f\xef\x51\xe7\x7e\x36\x86\x2f\x5f\xff\xfb\x57\x5f\xef\x18\x18\x80\x11\xd3\xef\x43\x98\xb7\xe5\xb2\x6a\x87\x18\x36\x27\x76\x0b\x67\xc4\xe7\xb0\xbe\x21\x18\xe6\xed\x98\xa6\xf2\xd7\x9e\xa0\x85\xe0\x44\x0b\xa6\xc2\x71\x8a\x40\x72\x21\x94\xe7\xb8\x51\x27\xd8\xa7\xe8\x7a\xeb\x62\xd2\x75\x32\x8d\x8b\xd7\x7d\x98\x46\x11\x6f\xc2\xf7\xfd\xd3\xc3\x70\x0b\xc9\xd2\xc1\x9f\xfb\x6b\xf4\x50\x6e\x5d\xb1\xc7\xe3\xb4\x96\x23\x52\x8b\xc1\x0d\xc6\x70\x7b\x8b\x1b\xc4\x86\xde\x43\x8a\x23\x67\x98\xe3\xee\x2a\x6c\x7d\x6c\xa5\xf6\x5f\xfd\xdb\x6e\xfd\x4a\x2d\x8b\xaa\x18\xc3\xab\x1d\x43\x02\xa4\x1d\xa9\xcd\x30\xb8\x8d\x02\x04\x41\x57\x6e\x45\x51\x70\xca\x2f\x53\xd4\x5e\x66\x12\x6d\xf7\x68\x87\xc4\x83\x27\xd6\x31\x7a\x23\xc5\x97\x2e\xe2\x50\xe7\xb0\xdf\x58\x93\x56\x09\x5a\xf6\xc0\xb1\x12\x92\x74\x01\x6a\x59\x62\xb0\x86\x90\x86\x42\x13\x7a\xd7\xd5\x24\x0a\xcf\x51\x68\xa9\x73\x17\xb7\x94\x2e\x00\x48\xf0\xba\x8b\x19\xb2\xeb\x59\xa9\x40\x31\x55\x4e\xa6\x68\x31\x05\x01\x79\x25\xac\xd0\x1e\x31\x25\xf8\x09\x55\xa8\x70\x0b\xd8\x42\x9e\x68\xef\xde\x6a\x6b\x0c\xa6\x1a\xc0\x8a\x48\x8c\xf7\x75\xa1\x3e\xf9\x9b\x99\xea\xc5\xab\xd7\x7b\x55\xde\x8c\xdb\x5d\xc3\x17\xde\xa3\xd5\x63\xf8\xef\xfb\xcb\xc1\x7f\x89\xc1\xdf\x1f\xce\xe2\x3f\xaf\x06\x7f\xfe\x9f\xfe\xf8\xe1\x8b\xce\xcf\x87\xf3\x6f\xff\x65\xc7\x4a\xdb\xc3\xf6\x1d\xc7\x27\x3a\x91\x3a\x48\xac\x35\xda\x67\x0f\x63\x32\xb8\xb3\x15\xf6\xe1\x9d\x50\x0e\xfb\xf0\x59\xb3\x6b\xf8\x95\x42\xdb\x9d\xb9\x84\x6f\x00\x3d\xda\x75\x7b\xf0\xd1\x0c\x61\x92\xf6\x8f\x89\xe4\xee\xab\x48\x1e\x27\x24\x0e\xdb\x4c\xd6\x45\x9a\xce\x1d\x2f\x30\xe2\x51\x58\x3a\x8c\xe1\xed\x30\x31\xc5\xa8\x73\x07\x4c\x71\xf5\x07\xa1\x97\xd0\xc2\x5a\x08\x4a\xd7\x4f\xba\xf3\x84\x4d\x22\xb1\x94\x91\x36\xb7\xe8\xa0\xe4\x23\x42\x13\xb9\x06\xb0\x9c\x62\x22\x38\x10\xb7\x53\xe9\xad\xb0\xcb\x4e\xde\x01\x89\xd0\xb1\x16\x99\x55\x0a\xce\x1c\x22\x0c\xb5\x49\x71\x13\x5d\xcf\x03\x86\x8a\xa9\x54\xd2\x2f\x43\xe1\x32\x31\x3a\x53\x32\xc6\xff\x45\x69\xac\x17\xda\xd7\x45\xdf\x1c\x9f\x40\xfa\x50\x6f\x0e\xc5\xb9\xb3\x54\xbb\x8b\x8b\xd7\x5f\xde\x56\xd3\xd4\x14\x42\xea\x77\x85\x1f\x9d\x7f\x7b\xf6\xb7\x4a\x28\xae\x98\x5e\x8b\x02\xdf\x15\xfe\xfc\xb7\x73\x8b\x17\x5f\x1d\x61\x45\x67\xf7\xc1\x56\x1e\xce\xee\x07\xf1\xbf\x2f\xea\xa6\xf3\x6f\xcf\x26\xc3\xbd\xfd\xe7\x5f\x10\x0f\x1d\x0b\x7c\xb8\x1f\xb4\xe6\x37\x7c\xf8\xe2\xfc\xdb\x4e\xdf\xf9\xa6\x31\x76\xb2\xd2\x83\x09\xe6\xfb\x76\x6c\x88\x4e\x7c\xfd\x18\xaa\xb6\xcc\xd5\xd0\x70\x3d\xe5\x8c\x56\x4c\xfe\x38\x2e\xf3\xec\xaa\xf2\x31\x41\x97\x3e\xbe\x8a\xbb\x5a\xbf\xed\x94\x4d\x36\xae\xc6\x1b\x0f\xb4\xc2\xd4\x3f\x6b\x9d\x76\xf5\x66\xe1\x13\x66\xcf\xbc\x58\xf8\x84\x59\xb7\xd4\x16\x04\xb3\x7a\x9f\x10\x9f\xab\x34\x17\x0e\xbf\xc3\xdb\x81\xdd\x0f\x9c\xb6\xb2\x40\xc1\x7e\x5d\x2f\x8d\xe7\x31\xf2\xb0\xf3\x22\xf4\xa0\x49\xb3\x3f\xbe\x11\x7e\x76\x14\x05\x2f\xaf\xa2\xd8\xf8\xd6\x90\xef\x71\x4b\x89\x09\xae\xbc\xa1\xe2\x38\x0e\x45\x1a\x1b\x29\xf0\xb1\x18\xfb\xfa\x21\xe2\x88\x77\xa5\xed\x1b\x2b\x0a\x9a\x40\x10\x10\xcb\x14\xfe\xe3\xf6\xe3\xf5\xe8\x7b\x13\x63\x05\xca\x66\x5c\xb0\x2d\xbe\xe5\xea\x83\xab\x92\x19\x08\x47\xa4\x51\x7e\x7b\xcb\xa5\x87\x42\x68\x99\xa1\xf3\xc3\xb8\x1a\x5a\x77\xff\xfa\x61\xb8\x5a\xee\x90\xf1\x42\xb5\x7e\x89\x14\x0f\x00\xdb\x06\x31\xd3\xcc\xe5\xa0\x95\x49\x2a\x4d\x1a\x89\x5e\x30\xb1\x5e\x3c\x22\x98\x48\x6c\x85\xec\x14\xc6\xd0\xa3\x63\xd2\xd9\xfa\x27\x32\xac\x9f\x7b\x70\xb6\xe0\x92\x7e\x8f\x7e\xf6\xc2\x86\xcd\xc3\x31\x6a\xeb\x78\xfc\xb8\x71\x88\xef\xad\xcc\x73\x0e\xb7\xb8\x6e\x3b\x47\xed\xcf\xd9\xbf\x65\xa0\x4d\x67\xb0\x8e\xf7\x63\xed\xad\xd8\x3a\x21\xf7\xaf\x1f\x7a\x70\xb6\xca\x17\x85\xa0\xf8\x04\xaf\x9b\x9b\xb0\xd2\xa4\xe7\x75\xd6\xba\xd4\x5e\x3c\x71\x62\x30\x33\x0e\x75\xb8\x48\xf0\x26\x54\x63\x9d\xa1\xe4\x13\x95\x1a\x84\x00\x33\x85\x45\x28\xc0\xd5\xa2\x0c\x97\xc9\xa5\xb0\x7e\xed\x59\xdd\xdd\xc7\xb7\x1f\xc7\x61\x37\x52\x5b\xae\xeb\x2c\x37\x93\x5a\xa8\x58\xd6\x6e\xe2\x43\x22\xa4\x0a\x4a\xf2\x26\xa6\xb6\x75\x45\x37\xab\x7c\x65\x71\xb8\xfe\xcc\xea\xe8\x13\xbf\xed\x8d\xdb\xf6\xc3\xce\x6f\xdd\xd6\x0d\xed\xff\xf0\x25\xd9\xd1\x2c\xea\x1d\x37\xad\x9b\x2c\x5e\x77\xce\xe0\x5e\x16\x5b\x68\x26\x2e\x53\x93\x38\x62\x30\xc1\xd2\xbb\x91\x99\x13\x74\xe2\x62\xb4\x30\xf6\x51\xea\x7c\x40\x87\x6c\x10\x34\xef\x46\xec\x62\x46\x7f\xe2\x3f\xbf\x8a\x23\xf6\x53\xc7\xb3\x15\x1e\xb4\xfe\x03\x78\x63\xf7\x39\xfa\xc5\xac\xd5\xf1\xe5\x73\x3c\xc1\xcb\xdb\x3a\xf9\x5b\x9b\x4d\xe6\x12\x2e\xc2\xe3\x4b\xd7\x0e\xc2\x15\x22\x0d\x10\x28\xf4\xf2\x77\x3f\xc6\x24\x40\xce\xf1\x93\xe5\x20\x3e\x45\x1f\x08\x9d\x0e\x9a\xf8\x3a\x59\xfe\x62\x89\x55\xf2\x48\x03\xfe\x7c\xf5\xf6\x1f\x73\xb8\x2b\xf9\x2c\x6b\x0d\x55\x94\x31\x78\x5b\xd5\xd1\x9d\xf3\xc6\x8a\x1c\x57\xdb\xaa\x69\x93\x7c\xb4\x0c\xc7\xbc\x12\x7e\xfa\x99\x9b\xda\xc7\xe7\x42\x95\x33\xf1\xba\x9e\x7b\x7a\x82\x7e\x7a\x82\x7e\x7a\x82\x7e\x7a\x82\xbe\x57\xd8\x7f\xd4\x27\xe8\xa7\x27\xe4\xa7\x27\xe4\xa7\x27\xe4\xbb\xbb\x4f\x4f\xc8\x4f\x4f\xc8\x4f\x4f\xc8\xd7\xbf\xd3\x13\xf2\xd3\x13\xf2\xd3\x13\xf2\xd3\x13\xf2\x2d\xdf\x4e\x35\xfd\xff\x7e\x7c\x79\xba\x1c\xfb\x63\x5c\x8e\x9d\xae\xbb\x4e\xd7\x5d\xa7\xeb\xae\xd3\x75\xd7\x2f\x38\xf1\xa7\xeb\xae\xd3\x75\xd7\xe9\xba\xeb\x74\xdd\xf5\x4f\x7a\xdd\x95\x09\xe5\x8e\xbe\xef\xfa\xdf\x00\x00\x00\xff\xff\x25\xf1\x04\x48\x34\x4b\x00\x00") func operatorsCoreosCom_operatorgroupsYamlBytes() ([]byte, error) { return bindataRead( diff --git a/vendor/github.com/operator-framework/api/pkg/operators/v1/operatorgroup_types.go b/vendor/github.com/operator-framework/api/pkg/operators/v1/operatorgroup_types.go index 1706eb17e4..81ad352d4e 100644 --- a/vendor/github.com/operator-framework/api/pkg/operators/v1/operatorgroup_types.go +++ b/vendor/github.com/operator-framework/api/pkg/operators/v1/operatorgroup_types.go @@ -24,8 +24,29 @@ const ( MutlipleOperatorGroupCondition = "MultipleOperatorGroup" MultipleOperatorGroupsReason = "MultipleOperatorGroupsFound" OperatorGroupServiceAccountReason = "ServiceAccountNotFound" + + // UpgradeStrategyDefault configures OLM such that it will only allow + // clusterServiceVersions to move to the replacing phase to the succeeded + // phase. This effectively means that OLM will not allow operators to move + // to the next version if an installation or upgrade has failed. + UpgradeStrategyDefault UpgradeStrategy = "Default" + + // UpgradeStrategyUnsafeFailForward configures OLM such that it will allow + // clusterServiceVersions to move to the replacing phase from the succeeded + // phase or from the failed phase. Additionally, OLM will generate new + // installPlans when a subscription references a failed installPlan and the + // catalog has been updated with a new upgrade for the existing set of + // operators. + // + // WARNING: The UpgradeStrategyUnsafeFailForward upgrade strategy is unsafe + // and may result in unexpected behavior or unrecoverable data loss unless + // you have deep understanding of the set of operators being managed in the + // namespace. + UpgradeStrategyUnsafeFailForward UpgradeStrategy = "TechPreviewUnsafeFailForward" ) +type UpgradeStrategy string + // OperatorGroupSpec is the spec for an OperatorGroup resource. type OperatorGroupSpec struct { // Selector selects the OperatorGroup's target namespaces. @@ -45,6 +66,29 @@ type OperatorGroupSpec struct { // Static tells OLM not to update the OperatorGroup's providedAPIs annotation // +optional StaticProvidedAPIs bool `json:"staticProvidedAPIs,omitempty"` + + // UpgradeStrategy defines the upgrade strategy for operators in the namespace. + // There are currently two supported upgrade strategies: + // + // Default: OLM will only allow clusterServiceVersions to move to the replacing + // phase from the succeeded phase. This effectively means that OLM will not + // allow operators to move to the next version if an installation or upgrade + // has failed. + // + // TechPreviewUnsafeFailForward: OLM will allow clusterServiceVersions to move to the + // replacing phase from the succeeded phase or from the failed phase. + // Additionally, OLM will generate new installPlans when a subscription references + // a failed installPlan and the catalog has been updated with a new upgrade for + // the existing set of operators. + // + // WARNING: The TechPreviewUnsafeFailForward upgrade strategy is unsafe and may result + // in unexpected behavior or unrecoverable data loss unless you have deep + // understanding of the set of operators being managed in the namespace. + // + // +kubebuilder:validation:Enum=Default;TechPreviewUnsafeFailForward + // +kubebuilder:default=Default + // +optional + UpgradeStrategy UpgradeStrategy `json:"upgradeStrategy,omitempty"` } // OperatorGroupStatus is the status for an OperatorGroupResource. @@ -76,6 +120,7 @@ type OperatorGroup struct { metav1.ObjectMeta `json:"metadata"` // +optional + // +kubebuilder:default={upgradeStrategy:Default} Spec OperatorGroupSpec `json:"spec"` Status OperatorGroupStatus `json:"status,omitempty"` } @@ -98,6 +143,17 @@ func (o *OperatorGroup) BuildTargetNamespaces() string { return strings.Join(ns, ",") } +// UpgradeStrategy returns the UpgradeStrategy specified or the default value otherwise. +func (o *OperatorGroup) UpgradeStrategy() UpgradeStrategy { + strategyName := o.Spec.UpgradeStrategy + switch { + case strategyName == UpgradeStrategyUnsafeFailForward: + return strategyName + default: + return UpgradeStrategyDefault + } +} + // IsServiceAccountSpecified returns true if the spec has a service account name specified. func (o *OperatorGroup) IsServiceAccountSpecified() bool { if o.Spec.ServiceAccountName == "" { diff --git a/vendor/github.com/operator-framework/operator-lifecycle-manager/pkg/controller/operators/catalog/operator.go b/vendor/github.com/operator-framework/operator-lifecycle-manager/pkg/controller/operators/catalog/operator.go index 455d9f4646..da2994ac3c 100644 --- a/vendor/github.com/operator-framework/operator-lifecycle-manager/pkg/controller/operators/catalog/operator.go +++ b/vendor/github.com/operator-framework/operator-lifecycle-manager/pkg/controller/operators/catalog/operator.go @@ -42,6 +42,7 @@ import ( "k8s.io/client-go/util/workqueue" "github.com/operator-framework/api/pkg/operators/reference" + operatorsv1 "github.com/operator-framework/api/pkg/operators/v1" "github.com/operator-framework/api/pkg/operators/v1alpha1" "github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/clientset/versioned" "github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/informers/externalversions" @@ -102,6 +103,7 @@ type Operator struct { catsrcQueueSet *queueinformer.ResourceQueueSet subQueueSet *queueinformer.ResourceQueueSet ipQueueSet *queueinformer.ResourceQueueSet + ogQueueSet *queueinformer.ResourceQueueSet nsResolveQueue workqueue.RateLimitingInterface namespace string recorder record.EventRecorder @@ -181,6 +183,7 @@ func NewOperator(ctx context.Context, kubeconfigPath string, clock utilclock.Clo catsrcQueueSet: queueinformer.NewEmptyResourceQueueSet(), subQueueSet: queueinformer.NewEmptyResourceQueueSet(), ipQueueSet: queueinformer.NewEmptyResourceQueueSet(), + ogQueueSet: queueinformer.NewEmptyResourceQueueSet(), catalogSubscriberIndexer: map[string]cache.Indexer{}, serviceAccountQuerier: scoped.NewUserDefinedServiceAccountQuerier(logger, crClient), clientAttenuator: scoped.NewClientAttenuator(logger, config, opClient), @@ -255,6 +258,24 @@ func NewOperator(ctx context.Context, kubeconfigPath string, clock utilclock.Clo return nil, err } + operatorGroupInformer := crInformerFactory.Operators().V1().OperatorGroups() + op.lister.OperatorsV1().RegisterOperatorGroupLister(metav1.NamespaceAll, operatorGroupInformer.Lister()) + ogQueue := workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "ogs") + op.ogQueueSet.Set(metav1.NamespaceAll, ogQueue) + operatorGroupQueueInformer, err := queueinformer.NewQueueInformer( + ctx, + queueinformer.WithLogger(op.logger), + queueinformer.WithQueue(ogQueue), + queueinformer.WithInformer(operatorGroupInformer.Informer()), + queueinformer.WithSyncer(queueinformer.LegacySyncHandler(op.syncResolvingNamespace).ToSyncer()), + ) + if err != nil { + return nil, err + } + if err := op.RegisterQueueInformer(operatorGroupQueueInformer); err != nil { + return nil, err + } + // Wire CatalogSources catsrcInformer := crInformerFactory.Operators().V1alpha1().CatalogSources() op.lister.OperatorsV1alpha1().RegisterCatalogSourceLister(metav1.NamespaceAll, catsrcInformer.Lister()) @@ -882,6 +903,20 @@ func (o *Operator) syncCatalogSources(obj interface{}) (syncError error) { return } +func (o *Operator) isFailForwardEnabled(namespace string) (bool, error) { + ogs, err := o.lister.OperatorsV1().OperatorGroupLister().OperatorGroups(namespace).List(labels.Everything()) + if err != nil { + o.logger.Debugf("failed to list operatorgroups in the %s namespace: %v", namespace, err) + // Couldn't list operatorGroups, assuming default upgradeStrategy + // so existing behavior is observed for failed CSVs. + return false, nil + } + if len(ogs) != 1 { + return false, fmt.Errorf("found %d operatorGroups in namespace %s, expected 1", len(ogs), namespace) + } + return ogs[0].UpgradeStrategy() == operatorsv1.UpgradeStrategyUnsafeFailForward, nil +} + func (o *Operator) syncResolvingNamespace(obj interface{}) error { ns, ok := obj.(*corev1.Namespace) if !ok { @@ -908,6 +943,11 @@ func (o *Operator) syncResolvingNamespace(obj interface{}) error { return err } + failForwardEnabled, err := o.isFailForwardEnabled(namespace) + if err != nil { + return err + } + // TODO: parallel maxGeneration := 0 subscriptionUpdated := false @@ -924,7 +964,7 @@ func (o *Operator) syncResolvingNamespace(obj interface{}) error { } // ensure the installplan reference is correct - sub, changedIP, err := o.ensureSubscriptionInstallPlanState(logger, sub) + sub, changedIP, err := o.ensureSubscriptionInstallPlanState(logger, sub, failForwardEnabled) if err != nil { logger.Debugf("error ensuring installplan state: %v", err) return err @@ -932,7 +972,7 @@ func (o *Operator) syncResolvingNamespace(obj interface{}) error { subscriptionUpdated = subscriptionUpdated || changedIP // record the current state of the desired corresponding CSV in the status. no-op if we don't know the csv yet. - sub, changedCSV, err := o.ensureSubscriptionCSVState(logger, sub) + sub, changedCSV, err := o.ensureSubscriptionCSVState(logger, sub, failForwardEnabled) if err != nil { logger.Debugf("error recording current state of CSV in status: %v", err) return err @@ -958,7 +998,7 @@ func (o *Operator) syncResolvingNamespace(obj interface{}) error { logger.Debug("resolving subscriptions in namespace") // resolve a set of steps to apply to a cluster, a set of subscriptions to create/update, and any errors - steps, bundleLookups, updatedSubs, err := o.resolver.ResolveSteps(namespace) + steps, bundleLookups, updatedSubs, err := o.resolver.ResolveSteps(namespace, failForwardEnabled) if err != nil { go o.recorder.Event(ns, corev1.EventTypeWarning, "ResolutionFailed", err.Error()) // If the error is constraints not satisfiable, then simply project the @@ -1080,7 +1120,7 @@ func (o *Operator) nothingToUpdate(logger *logrus.Entry, sub *v1alpha1.Subscript return false } -func (o *Operator) ensureSubscriptionInstallPlanState(logger *logrus.Entry, sub *v1alpha1.Subscription) (*v1alpha1.Subscription, bool, error) { +func (o *Operator) ensureSubscriptionInstallPlanState(logger *logrus.Entry, sub *v1alpha1.Subscription, failForwardEnabled bool) (*v1alpha1.Subscription, bool, error) { if sub.Status.InstallPlanRef != nil || sub.Status.Install != nil { return sub, false, nil } @@ -1110,13 +1150,16 @@ func (o *Operator) ensureSubscriptionInstallPlanState(logger *logrus.Entry, sub out.Status.InstallPlanRef = ref out.Status.Install = v1alpha1.NewInstallPlanReference(ref) out.Status.State = v1alpha1.SubscriptionStateUpgradePending + if failForwardEnabled && ip.Status.Phase == v1alpha1.InstallPlanPhaseFailed { + out.Status.State = v1alpha1.SubscriptionStateFailed + } out.Status.CurrentCSV = out.Spec.StartingCSV out.Status.LastUpdated = o.now() return out, true, nil } -func (o *Operator) ensureSubscriptionCSVState(logger *logrus.Entry, sub *v1alpha1.Subscription) (*v1alpha1.Subscription, bool, error) { +func (o *Operator) ensureSubscriptionCSVState(logger *logrus.Entry, sub *v1alpha1.Subscription, failForwardEnabled bool) (*v1alpha1.Subscription, bool, error) { if sub.Status.CurrentCSV == "" { return sub, false, nil } @@ -1126,6 +1169,14 @@ func (o *Operator) ensureSubscriptionCSVState(logger *logrus.Entry, sub *v1alpha if err != nil { logger.WithError(err).WithField("currentCSV", sub.Status.CurrentCSV).Debug("error fetching csv listed in subscription status") out.Status.State = v1alpha1.SubscriptionStateUpgradePending + if failForwardEnabled && sub.Status.InstallPlanRef != nil { + ip, err := o.client.OperatorsV1alpha1().InstallPlans(sub.GetNamespace()).Get(context.TODO(), sub.Status.InstallPlanRef.Name, metav1.GetOptions{}) + if err != nil { + logger.WithError(err).WithField("currentCSV", sub.Status.CurrentCSV).Debug("error fetching installplan listed in subscription status") + } else if ip.Status.Phase == v1alpha1.InstallPlanPhaseFailed { + out.Status.State = v1alpha1.SubscriptionStateFailed + } + } } else { out.Status.State = v1alpha1.SubscriptionStateAtLatest out.Status.InstalledCSV = sub.Status.CurrentCSV 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 7efa2746d6..194843361a 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/install" "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/operators/internal/pruning" "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/operators/olm/overrides" + resolver "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver" "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" @@ -1996,6 +1997,15 @@ func (a *Operator) transitionCSVState(in v1alpha1.ClusterServiceVersion) (out *v } case v1alpha1.CSVPhaseFailed: + // Transition to the replacing phase if FailForward is enabled and a CSV exists that replaces the operator. + if operatorGroup.UpgradeStrategy() == operatorsv1.UpgradeStrategyUnsafeFailForward { + if replacement := a.isBeingReplaced(out, a.csvSet(out.GetNamespace(), v1alpha1.CSVPhaseAny)); replacement != nil { + msg := fmt.Sprintf("Fail Forward is enabled, allowing %s csv to be replaced by csv: %s", out.Status.Phase, replacement.GetName()) + out.SetPhaseWithEvent(v1alpha1.CSVPhaseReplacing, v1alpha1.CSVReasonBeingReplaced, msg, a.now(), a.recorder) + metrics.CSVUpgradeCount.Inc() + return + } + } installer, strategy := a.parseStrategiesAndUpdateStatus(out) if strategy == nil { return @@ -2087,7 +2097,29 @@ func (a *Operator) transitionCSVState(in v1alpha1.ClusterServiceVersion) (out *v } // If there is a succeeded replacement, mark this for deletion - if next := a.isBeingReplaced(out, a.csvSet(out.GetNamespace(), v1alpha1.CSVPhaseAny)); next != nil { + next := a.isBeingReplaced(out, a.csvSet(out.GetNamespace(), v1alpha1.CSVPhaseAny)) + // Get the newest CSV in the replacement chain if fail forward upgrades are enabled. + if operatorGroup.UpgradeStrategy() == operatorsv1.UpgradeStrategyUnsafeFailForward { + csvs, err := a.lister.OperatorsV1alpha1().ClusterServiceVersionLister().ClusterServiceVersions(next.GetNamespace()).List(labels.Everything()) + if err != nil { + syncError = err + return + } + + lastCSVInChain, err := resolver.WalkReplacementChain(next, resolver.ReplacementMapping(csvs), resolver.WithUniqueCSVs()) + if err != nil { + syncError = err + return + } + + if lastCSVInChain == nil { + syncError = fmt.Errorf("fail forward upgrades enabled, unable to identify last CSV in replacement chain") + return + } + + next = lastCSVInChain + } + if next != nil { if next.Status.Phase == v1alpha1.CSVPhaseSucceeded { out.SetPhaseWithEvent(v1alpha1.CSVPhaseDeleting, v1alpha1.CSVReasonReplaced, "has been replaced by a newer ClusterServiceVersion that has successfully installed.", now, a.recorder) } else { diff --git a/vendor/github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/instrumented_resolver.go b/vendor/github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/instrumented_resolver.go index d55d67c4fc..83e4cf9206 100644 --- a/vendor/github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/instrumented_resolver.go +++ b/vendor/github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/instrumented_resolver.go @@ -22,9 +22,9 @@ func NewInstrumentedResolver(resolver StepResolver, successMetricsEmitter, failu } } -func (ir *InstrumentedResolver) ResolveSteps(namespace string) ([]*v1alpha1.Step, []v1alpha1.BundleLookup, []*v1alpha1.Subscription, error) { +func (ir *InstrumentedResolver) ResolveSteps(namespace string, failForwardEnabled bool) ([]*v1alpha1.Step, []v1alpha1.BundleLookup, []*v1alpha1.Subscription, error) { start := time.Now() - steps, lookups, subs, err := ir.resolver.ResolveSteps(namespace) + steps, lookups, subs, err := ir.resolver.ResolveSteps(namespace, failForwardEnabled) if err != nil { ir.failureMetricsEmitter(time.Since(start)) } else { 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 c19aba9f26..29b28ca34f 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 @@ -55,7 +55,7 @@ func (w *debugWriter) Write(b []byte) (int, error) { return n, nil } -func (r *Resolver) Resolve(namespaces []string, subs []*v1alpha1.Subscription) ([]*cache.Entry, error) { +func (r *Resolver) Resolve(namespaces []string, subs []*v1alpha1.Subscription, existingEntryPredicates ...cache.Predicate) ([]*cache.Entry, error) { var errs []error variables := make(map[solver.Identifier]solver.Variable) @@ -72,7 +72,8 @@ func (r *Resolver) Resolve(namespaces []string, subs []*v1alpha1.Subscription) ( } preferredNamespace := namespaces[0] - _, existingVariables, err := r.getBundleVariables(preferredNamespace, namespacedCache.Catalog(cache.NewVirtualSourceKey(preferredNamespace)).Find(cache.True()), namespacedCache, visited) + existingEntryPredicates = append(existingEntryPredicates, cache.True()) + _, existingVariables, err := r.getBundleVariables(preferredNamespace, namespacedCache.Catalog(cache.NewVirtualSourceKey(preferredNamespace)).Find(existingEntryPredicates...), namespacedCache, visited) if err != nil { return nil, err } 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 aedaaa49db..e67d27e000 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 @@ -9,6 +9,8 @@ import ( corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/selection" "github.com/operator-framework/api/pkg/operators/v1alpha1" "github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/clientset/versioned" @@ -27,7 +29,7 @@ const ( var initHooks []stepResolverInitHook type StepResolver interface { - ResolveSteps(namespace string) ([]*v1alpha1.Step, []v1alpha1.BundleLookup, []*v1alpha1.Subscription, error) + ResolveSteps(namespace string, failForwardEnabled bool) ([]*v1alpha1.Step, []v1alpha1.BundleLookup, []*v1alpha1.Subscription, error) } type OperatorStepResolver struct { @@ -84,14 +86,125 @@ func NewOperatorStepResolver(lister operatorlister.OperatorLister, client versio return stepResolver } -func (r *OperatorStepResolver) ResolveSteps(namespace string) ([]*v1alpha1.Step, []v1alpha1.BundleLookup, []*v1alpha1.Subscription, error) { +type walkOption func(csv *v1alpha1.ClusterServiceVersion) error + +func WithCSVPhase(phase v1alpha1.ClusterServiceVersionPhase) walkOption { + return func(csv *v1alpha1.ClusterServiceVersion) error { + if csv == nil || csv.Status.Phase != phase { + return fmt.Errorf("csv %s/%s in phase %s instead of %s", csv.GetNamespace(), csv.GetName(), csv.Status.Phase, phase) + } + return nil + } +} + +func WithUniqueCSVs() walkOption { + visited := map[string]struct{}{} + return func(csv *v1alpha1.ClusterServiceVersion) error { + // Check if we have visited the CSV before + if _, ok := visited[csv.GetName()]; ok { + return fmt.Errorf("infinite replacement chain detected") + } + + visited[csv.GetName()] = struct{}{} + return nil + } +} + +// walkReplacementChain walks along the chain of clusterServiceVersions being replaced and returns +// the last clusterServiceVersions in the replacement chain. An error is returned if any of the +// clusterServiceVersions before the last is not in the replaces phase or if an infinite replacement +// chain is detected. +func WalkReplacementChain(csv *v1alpha1.ClusterServiceVersion, csvToReplacement map[string]*v1alpha1.ClusterServiceVersion, options ...walkOption) (*v1alpha1.ClusterServiceVersion, error) { + if csv == nil { + return nil, fmt.Errorf("csv cannot be nil") + } + + for { + // Check if there is a CSV that replaces this CSVs + next, ok := csvToReplacement[csv.GetName()] + if !ok { + break + } + + // Check walk options + for _, o := range options { + if err := o(csv); err != nil { + return nil, err + } + } + + // Move along replacement chain. + csv = next + } + return csv, nil +} + +// isReplacementChainThatEndsInFailure returns true if the last CSV in the chain is in the failed phase and all other +// CSVs are in the replacing phase. +func isReplacementChainThatEndsInFailure(csv *v1alpha1.ClusterServiceVersion, csvToReplacement map[string]*v1alpha1.ClusterServiceVersion) (bool, error) { + lastCSV, err := WalkReplacementChain(csv, csvToReplacement, WithCSVPhase(v1alpha1.CSVPhaseReplacing), WithUniqueCSVs()) + if err != nil { + return false, err + } + return (lastCSV != nil && lastCSV.Status.Phase == v1alpha1.CSVPhaseFailed), nil +} + +// ReplacementMapping takes a list of CSVs and returns a map that maps a CSV's name to the CSV that replaces it. +func ReplacementMapping(csvs []*v1alpha1.ClusterServiceVersion) map[string]*v1alpha1.ClusterServiceVersion { + replacementMapping := map[string]*v1alpha1.ClusterServiceVersion{} + for _, csv := range csvs { + if csv.Spec.Replaces != "" { + replacementMapping[csv.Spec.Replaces] = csv + } + } + return replacementMapping +} + +func (r *OperatorStepResolver) cachePredicates(namespace string) ([]cache.Predicate, error) { + nonCopiedCSVRequirement, err := labels.NewRequirement(v1alpha1.CopiedLabelKey, selection.DoesNotExist, []string{}) + if err != nil { + return nil, err + } + + csvs, err := r.csvLister.ClusterServiceVersions(namespace).List(labels.NewSelector().Add(*nonCopiedCSVRequirement)) + if err != nil { + return nil, err + } + + predicates := []cache.Predicate{} + for i := range csvs { + replacementChainEndsInFailure, err := isReplacementChainThatEndsInFailure(csvs[i], ReplacementMapping(csvs)) + if err != nil { + return nil, err + } + if csvs[i].Status.Phase == v1alpha1.CSVPhaseReplacing && replacementChainEndsInFailure { + predicates = append(predicates, cache.Not(cache.CSVNamePredicate(csvs[i].GetName()))) + } + } + + return predicates, nil +} + +func (r *OperatorStepResolver) ResolveSteps(namespace string, failForwardEnabled bool) ([]*v1alpha1.Step, []v1alpha1.BundleLookup, []*v1alpha1.Subscription, error) { subs, err := r.listSubscriptions(namespace) if err != nil { return nil, nil, nil, err } + // The resolver considers the initial set of CSVs in the namespace by their appearance + // in the catalog cache. In order to support "fail forward" upgrades, we need to omit + // CSVs that are actively being replaced from this initial set of operators. The + // predicates defined here will omit these replacing CSVs from the set. + cachePredicates := []cache.Predicate{} + if failForwardEnabled { + cachePredicates, err = r.cachePredicates(namespace) + if err != nil { + r.log.Debugf("Unable to determine CSVs to exclude: %v", err) + } + } + namespaces := []string{namespace, r.globalCatalogNamespace} - operators, err := r.resolver.Resolve(namespaces, subs) + operators, err := r.resolver.Resolve(namespaces, subs, cachePredicates...) if err != nil { return nil, nil, nil, err } diff --git a/vendor/modules.txt b/vendor/modules.txt index 99a96f22c5..13652ec944 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -634,7 +634,7 @@ github.com/openshift/client-go/config/informers/externalversions/config github.com/openshift/client-go/config/informers/externalversions/config/v1 github.com/openshift/client-go/config/informers/externalversions/internalinterfaces github.com/openshift/client-go/config/listers/config/v1 -# github.com/operator-framework/api v0.12.0 => ./staging/api +# github.com/operator-framework/api v0.14.1-0.20220413143725-33310d6154f3 => ./staging/api ## explicit; go 1.17 github.com/operator-framework/api/crds github.com/operator-framework/api/pkg/constraints