diff --git a/deploy/crds/org_v1_che_crd-v1beta1.yaml b/deploy/crds/org_v1_che_crd-v1beta1.yaml index 0564d5419..a503576ef 100644 --- a/deploy/crds/org_v1_che_crd-v1beta1.yaml +++ b/deploy/crds/org_v1_che_crd-v1beta1.yaml @@ -868,6 +868,38 @@ spec: devfileRegistryURL: description: Public URL to the devfile registry. type: string + devworkspaceStatus: + description: The status of the Devworkspace subsystem + properties: + gatewayHost: + description: GatewayHost is the resolved host of the ingress/route. + This is equal to the Host in the spec on Kubernetes but contains + the actual host name of the route if Host is unspecified on OpenShift. + type: string + gatewayPhase: + description: GatewayPhase specifies the phase in which the gateway + deployment currently is. If the gateway is disabled, the phase + is "Inactive". + type: string + message: + description: Message contains further human-readable info for why + the Che cluster is in the phase it currently is. + type: string + phase: + description: Phase is the phase in which the Che cluster as a whole + finds itself in. + type: string + reason: + description: A brief CamelCase message indicating details about + why the Che cluster is in this state. + type: string + workspaceBaseDomain: + description: The resolved workspace base domain. This is either + the copy of the explicitly defined property of the same name in + the spec or, if it is undefined in the spec and we're running + on OpenShift, the automatically resolved basedomain for routes. + type: string + type: object gitHubOAuthProvisioned: description: Indicates whether an Identity Provider instance, Keycloak or RH-SSO, has been configured to integrate with the GitHub OAuth. diff --git a/deploy/crds/org_v1_che_crd.yaml b/deploy/crds/org_v1_che_crd.yaml index 5c7450f43..6f3a3b38a 100644 --- a/deploy/crds/org_v1_che_crd.yaml +++ b/deploy/crds/org_v1_che_crd.yaml @@ -885,6 +885,39 @@ spec: devfileRegistryURL: description: Public URL to the devfile registry. type: string + devworkspaceStatus: + description: The status of the Devworkspace subsystem + properties: + gatewayHost: + description: GatewayHost is the resolved host of the ingress/route. + This is equal to the Host in the spec on Kubernetes but contains + the actual host name of the route if Host is unspecified on + OpenShift. + type: string + gatewayPhase: + description: GatewayPhase specifies the phase in which the gateway + deployment currently is. If the gateway is disabled, the phase + is "Inactive". + type: string + message: + description: Message contains further human-readable info for + why the Che cluster is in the phase it currently is. + type: string + phase: + description: Phase is the phase in which the Che cluster as a + whole finds itself in. + type: string + reason: + description: A brief CamelCase message indicating details about + why the Che cluster is in this state. + type: string + workspaceBaseDomain: + description: The resolved workspace base domain. This is either + the copy of the explicitly defined property of the same name + in the spec or, if it is undefined in the spec and we're running + on OpenShift, the automatically resolved basedomain for routes. + type: string + type: object gitHubOAuthProvisioned: description: Indicates whether an Identity Provider instance, Keycloak or RH-SSO, has been configured to integrate with the GitHub OAuth. diff --git a/deploy/olm-catalog/nightly/eclipse-che-preview-kubernetes/manifests/org_v1_che_crd.yaml b/deploy/olm-catalog/nightly/eclipse-che-preview-kubernetes/manifests/org_v1_che_crd.yaml index 5c7450f43..6f3a3b38a 100644 --- a/deploy/olm-catalog/nightly/eclipse-che-preview-kubernetes/manifests/org_v1_che_crd.yaml +++ b/deploy/olm-catalog/nightly/eclipse-che-preview-kubernetes/manifests/org_v1_che_crd.yaml @@ -885,6 +885,39 @@ spec: devfileRegistryURL: description: Public URL to the devfile registry. type: string + devworkspaceStatus: + description: The status of the Devworkspace subsystem + properties: + gatewayHost: + description: GatewayHost is the resolved host of the ingress/route. + This is equal to the Host in the spec on Kubernetes but contains + the actual host name of the route if Host is unspecified on + OpenShift. + type: string + gatewayPhase: + description: GatewayPhase specifies the phase in which the gateway + deployment currently is. If the gateway is disabled, the phase + is "Inactive". + type: string + message: + description: Message contains further human-readable info for + why the Che cluster is in the phase it currently is. + type: string + phase: + description: Phase is the phase in which the Che cluster as a + whole finds itself in. + type: string + reason: + description: A brief CamelCase message indicating details about + why the Che cluster is in this state. + type: string + workspaceBaseDomain: + description: The resolved workspace base domain. This is either + the copy of the explicitly defined property of the same name + in the spec or, if it is undefined in the spec and we're running + on OpenShift, the automatically resolved basedomain for routes. + type: string + type: object gitHubOAuthProvisioned: description: Indicates whether an Identity Provider instance, Keycloak or RH-SSO, has been configured to integrate with the GitHub OAuth. diff --git a/deploy/olm-catalog/nightly/eclipse-che-preview-openshift/manifests/org_v1_che_crd.yaml b/deploy/olm-catalog/nightly/eclipse-che-preview-openshift/manifests/org_v1_che_crd.yaml index 27cdcafb6..c06367b87 100644 --- a/deploy/olm-catalog/nightly/eclipse-che-preview-openshift/manifests/org_v1_che_crd.yaml +++ b/deploy/olm-catalog/nightly/eclipse-che-preview-openshift/manifests/org_v1_che_crd.yaml @@ -898,6 +898,39 @@ spec: devfileRegistryURL: description: Public URL to the devfile registry. type: string + devworkspaceStatus: + description: The status of the Devworkspace subsystem + properties: + gatewayHost: + description: GatewayHost is the resolved host of the ingress/route. + This is equal to the Host in the spec on Kubernetes but contains + the actual host name of the route if Host is unspecified on + OpenShift. + type: string + gatewayPhase: + description: GatewayPhase specifies the phase in which the gateway + deployment currently is. If the gateway is disabled, the phase + is "Inactive". + type: string + message: + description: Message contains further human-readable info for + why the Che cluster is in the phase it currently is. + type: string + phase: + description: Phase is the phase in which the Che cluster as + a whole finds itself in. + type: string + reason: + description: A brief CamelCase message indicating details about + why the Che cluster is in this state. + type: string + workspaceBaseDomain: + description: The resolved workspace base domain. This is either + the copy of the explicitly defined property of the same name + in the spec or, if it is undefined in the spec and we're running + on OpenShift, the automatically resolved basedomain for routes. + type: string + type: object gitHubOAuthProvisioned: description: Indicates whether an Identity Provider instance, Keycloak or RH-SSO, has been configured to integrate with the GitHub OAuth. diff --git a/go.mod b/go.mod index 470367eaf..2ff0690df 100644 --- a/go.mod +++ b/go.mod @@ -17,6 +17,7 @@ require ( k8s.io/apiextensions-apiserver v0.18.2 k8s.io/apimachinery v0.18.2 k8s.io/client-go v12.0.0+incompatible + k8s.io/utils v0.0.0-20191010214722-8d271d903fe4 sigs.k8s.io/controller-runtime v0.6.0 sigs.k8s.io/yaml v1.2.0 ) diff --git a/go.sum b/go.sum index 2536094f1..21b901b6e 100644 --- a/go.sum +++ b/go.sum @@ -72,10 +72,8 @@ github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8 github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= -github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= -github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/Rican7/retry v0.1.0/go.mod h1:FgOROf8P5bebcC1DS0PdOQiqGUridaZvikzUmkFW6gg= github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ= @@ -222,7 +220,6 @@ github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkg github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/emicklei/go-restful v2.9.6+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= -github.com/emicklei/go-restful v2.11.1+incompatible h1:CjKsv3uWcCMvySPQYKxO8XX3f9zD4FeZRsW4G0B4ffE= github.com/emicklei/go-restful v2.11.1+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g= @@ -273,13 +270,11 @@ github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+ github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= github.com/go-openapi/jsonpointer v0.18.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= -github.com/go-openapi/jsonpointer v0.19.3 h1:gihV7YNZK1iK6Tgwwsxo2rJbD1GTbdm72325Bq8FI3w= github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg= github.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= github.com/go-openapi/jsonreference v0.18.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= -github.com/go-openapi/jsonreference v0.19.3 h1:5cxNfTy0UVC3X8JL5ymxzyoUZmo8iZb+jeTWn7tUa8o= github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= github.com/go-openapi/loads v0.17.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= github.com/go-openapi/loads v0.18.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= @@ -291,7 +286,6 @@ github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nA github.com/go-openapi/spec v0.17.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= github.com/go-openapi/spec v0.18.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= github.com/go-openapi/spec v0.19.2/go.mod h1:sCxk3jxKgioEJikev4fgkNmwS+3kuYdJtcsZsD5zxMY= -github.com/go-openapi/spec v0.19.4 h1:ixzUSnHTd6hCemgtAJgluaTSGYpLNpJY4mA2DIkdOAo= github.com/go-openapi/spec v0.19.4/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= github.com/go-openapi/strfmt v0.17.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU= github.com/go-openapi/strfmt v0.18.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU= @@ -301,7 +295,6 @@ github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/ github.com/go-openapi/swag v0.18.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.4/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= -github.com/go-openapi/swag v0.19.5 h1:lTz6Ys4CmqqCQmZPBlbQENR1/GucA2bzYTE12Pw4tFY= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= 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= @@ -509,7 +502,6 @@ github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.7.0 h1:aizVhC/NAAcKWb+5QsU1iNOZb4Yws5UO2I+aIprQITM= github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= github.com/maorfr/helm-plugin-utils v0.0.0-20181205064038-588190cb5e3b/go.mod h1:p3gwmRSFqbWw6plBpR0sKl3n3vpu8kX70gvCJKMvvCA= github.com/markbates/inflect v1.0.4/go.mod h1:1fR9+pO2KHEO9ZRtto13gDwwZaAKstQzferVeWqbgNs= @@ -578,7 +570,6 @@ github.com/onsi/ginkgo v1.4.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+W github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.10.1 h1:q/mM8GF/n0shIN8SaAZ0V+jnLPzen6WIVZdiwrRlMlo= github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.0 h1:Iw5WCbBcaAAd0fpRb1c9r5YCylv4XDoCSigm1zLevwU= @@ -587,7 +578,6 @@ github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGV github.com/onsi/gomega v1.3.0/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= -github.com/onsi/gomega v1.7.0 h1:XPnZz8VVBHjVsy1vzJmRwIcSwiUO+JFfrv/xGiigmME= 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= @@ -607,7 +597,6 @@ github.com/openshift/prom-label-proxy v0.1.1-0.20191016113035-b8153a7f39f1/go.mo github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= github.com/openzipkin/zipkin-go v0.2.0/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= -github.com/operator-framework/api v0.0.0-20200120235816-80fd2f1a09c9 h1:HfxMEPJ0djo/RNfrmli3kI2oKS6IeuIZWu1Q5Rewt/o= github.com/operator-framework/api v0.0.0-20200120235816-80fd2f1a09c9/go.mod h1:S5IdlJvmKkF84K2tBvsrqJbI2FVy03P88R75snpRxJo= github.com/operator-framework/api v0.3.20 h1:2Ks8GXXl/H2sV9ll2iQBUO65ABQ5VuzN3IKEZCJWljo= github.com/operator-framework/api v0.3.20/go.mod h1:Xbje9x0SHmh0nihE21kpesB38vk3cyxnE6JdDS8Jo1Q= @@ -636,7 +625,6 @@ github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0 github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pierrec/lz4 v2.2.6+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -738,7 +726,6 @@ github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoH github.com/stretchr/testify v0.0.0-20151208002404-e3a8ff8ce365/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= @@ -926,7 +913,6 @@ golang.org/x/sys v0.0.0-20190912141932-bc967efca4b8/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191028164358-195ce5e7f934/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191210023423-ac6580df4449 h1:gSbV7h1NRL2G1xTg/owz62CST1oJBmxy4QpMMregXVQ= golang.org/x/sys v0.0.0-20191210023423-ac6580df4449/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5 h1:LfCXLvNmTYH9kEmVgqbnsWfruoXZIrh4YBgqVHtDvw0= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1014,7 +1000,6 @@ google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98 google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= google.golang.org/genproto v0.0.0-20191009194640-548a555dbc03/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191028173616-919d9bdd9fe6 h1:UXl+Zk3jqqcbEVV7ace5lrt4YdA4tXiz3f/KbmD29Vo= google.golang.org/genproto v0.0.0-20191028173616-919d9bdd9fe6/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= @@ -1090,7 +1075,6 @@ k8s.io/apiextensions-apiserver v0.0.0-20191016113550-5357c4baaf65 h1:kThoiqgMsSw k8s.io/apiextensions-apiserver v0.0.0-20191016113550-5357c4baaf65/go.mod h1:5BINdGqggRXXKnDgpwoJ7PyQH8f+Ypp02fvVNcIFy9s= k8s.io/apimachinery v0.0.0-20191004115801-a2eda9f80ab8 h1:Iieh/ZEgT3BWwbLD5qEKcY06jKuPEl6zC7gPSehoLw4= k8s.io/apimachinery v0.0.0-20191004115801-a2eda9f80ab8/go.mod h1:llRdnznGEAqC3DcNm6yEj472xaFVfLM7hnYofMb12tQ= -k8s.io/apiserver v0.0.0-20191016112112-5190913f932d h1:leksCBKKBrPJmW1jV4dZUvwqmVtXpKdzpHsqXfFS094= k8s.io/apiserver v0.0.0-20191016112112-5190913f932d/go.mod h1:7OqfAolfWxUM/jJ/HBLyE+cdaWFBUoo5Q5pHgJVj2ws= k8s.io/autoscaler v0.0.0-20190607113959-1b4f1855cb8e/go.mod h1:QEXezc9uKPT91dwqhSJq3GNI3B1HxFRQHiku9kmrsSA= k8s.io/cli-runtime v0.0.0-20191016114015-74ad18325ed5/go.mod h1:sDl6WKSQkDM6zS1u9F49a0VooQ3ycYFBFLqd2jf2Xfo= @@ -1127,7 +1111,6 @@ k8s.io/kube-state-metrics v1.7.2/go.mod h1:U2Y6DRi07sS85rmVPmBFlmv+2peBcL8IWGjM+ k8s.io/kubectl v0.0.0-20191016120415-2ed914427d51/go.mod h1:gL826ZTIfD4vXTGlmzgTbliCAT9NGiqpCqK2aNYv5MQ= k8s.io/kubelet v0.0.0-20191016114556-7841ed97f1b2/go.mod h1:SBvrtLbuePbJygVXGGCMtWKH07+qrN2dE1iMnteSG8E= k8s.io/kubernetes v1.16.0/go.mod h1:nlP2zevWKRGKuaaVbKIwozU0Rjg9leVDXkL4YTtjmVs= -k8s.io/kubernetes v1.16.2 h1:k0f/OVp6Yfv+UMTm6VYKhqjRgcvHh4QhN9coanjrito= k8s.io/kubernetes v1.16.2/go.mod h1:SmhGgKfQ30imqjFVj8AI+iW+zSyFsswNErKYeTfgoH0= k8s.io/legacy-cloud-providers v0.0.0-20191016115753-cf0698c3a16b/go.mod h1:tKW3pKqdRW8pMveUTpF5pJuCjQxg6a25iLo+Z9BXVH0= k8s.io/metrics v0.0.0-20191016113814-3b1a734dba6e/go.mod h1:ve7/vMWeY5lEBkZf6Bt5TTbGS3b8wAxwGbdXAsufjRs= diff --git a/pkg/apis/org/conversion.go b/pkg/apis/org/conversion.go new file mode 100644 index 000000000..4b0b3d9ac --- /dev/null +++ b/pkg/apis/org/conversion.go @@ -0,0 +1,280 @@ +package org + +import ( + v1 "github.com/eclipse-che/che-operator/pkg/apis/org/v1" + "github.com/eclipse-che/che-operator/pkg/apis/org/v2alpha1" + "github.com/eclipse-che/che-operator/pkg/util" + "sigs.k8s.io/yaml" +) + +const ( + v1StorageAnnotation = "che.eclipse.org/cheClusterV1Spec" + v2alpha1StorageAnnotation = "che.eclipse.org/cheClusterV2alpha1Spec" + routeDomainSuffixPropertyKey = "CHE_INFRA_OPENSHIFT_ROUTE_HOST_DOMAIN__SUFFIX" + defaultV2alpha1IngressClass = "nginx" + defaultV1IngressClass = "nginx" +) + +func AsV1(v2 *v2alpha1.CheCluster) *v1.CheCluster { + ret := &v1.CheCluster{} + V2alpha1ToV1(v2, ret) + return ret +} + +func AsV2alpha1(v1 *v1.CheCluster) *v2alpha1.CheCluster { + ret := &v2alpha1.CheCluster{} + V1ToV2alpha1(v1, ret) + return ret +} + +func V1ToV2alpha1(v1 *v1.CheCluster, v2 *v2alpha1.CheCluster) error { + v2Data := v1.Annotations[v2alpha1StorageAnnotation] + v2Spec := v2alpha1.CheClusterSpec{} + if v2Data != "" { + err := yaml.Unmarshal([]byte(v2Data), &v2Spec) + if err != nil { + return err + } + } + + v2.ObjectMeta = v1.ObjectMeta + v2.Spec = v2Spec + + v1Spec, err := yaml.Marshal(v1.Spec) + if err != nil { + return err + } + if v2.Annotations == nil { + v2.Annotations = map[string]string{} + } + v2.Annotations[v1StorageAnnotation] = string(v1Spec) + + v1ToV2alpha1_Enabled(v1, v2) + v1ToV2alpha1_Host(v1, v2) + v1ToV2alpha1_GatewayEnabled(v1, v2) + v1ToV2alpha1_GatewayImage(v1, v2) + v1ToV2alpha1_GatewayConfigurerImage(v1, v2) + v1ToV2alpha1_GatewayTlsSecretName(v1, v2) + v1ToV2alpha1_WorkspaceDomainEndpointsBaseDomain(v1, v2) + v1ToV2alpha1_WorkspaceDomainEndpointsTlsSecretName(v1, v2) + v1ToV2alpha1_K8sIngressAnnotations(v1, v2) + + return nil +} + +func V2alpha1ToV1(v2 *v2alpha1.CheCluster, v1Obj *v1.CheCluster) error { + v1Data := v2.Annotations[v1StorageAnnotation] + v1Spec := v1.CheClusterSpec{} + if v1Data != "" { + err := yaml.Unmarshal([]byte(v1Data), &v1Spec) + if err != nil { + return err + } + } + + v1Obj.ObjectMeta = v2.ObjectMeta + v1Obj.Spec = v1Spec + v1Obj.Status = v1.CheClusterStatus{} + + v2Spec, err := yaml.Marshal(v2.Spec) + if err != nil { + return err + } + if v1Obj.Annotations == nil { + v1Obj.Annotations = map[string]string{} + } + v1Obj.Annotations[v2alpha1StorageAnnotation] = string(v2Spec) + + v2alpha1ToV1_Enabled(v1Obj, v2) + v2alpha1ToV1_Host(v1Obj, v2) + v2alpha1ToV1_GatewayEnabled(v1Obj, v2) + v2alpha1ToV1_GatewayImage(v1Obj, v2) + v2alpha1ToV1_GatewayConfigurerImage(v1Obj, v2) + v2alpha1ToV1_GatewayTlsSecretName(v1Obj, v2) + v2alpha1ToV1_WorkspaceDomainEndpointsBaseDomain(v1Obj, v2) + v2alpha1ToV1_WorkspaceDomainEndpointsTlsSecretName(v1Obj, v2) + v2alpha1ToV1_K8sIngressAnnotations(v1Obj, v2) + + return nil +} + +func v1ToV2alpha1_Enabled(v1 *v1.CheCluster, v2 *v2alpha1.CheCluster) { + v2.Spec.Enabled = &v1.Spec.DevWorkspace.Enable +} + +func v1ToV2alpha1_Host(v1 *v1.CheCluster, v2 *v2alpha1.CheCluster) { + v2.Spec.Gateway.Host = v1.Spec.Server.CheHost +} + +func v1ToV2alpha1_WorkspaceDomainEndpointsBaseDomain(v1 *v1.CheCluster, v2 *v2alpha1.CheCluster) { + if util.IsOpenShift { + v2.Spec.WorkspaceDomainEndpoints.BaseDomain = v1.Spec.Server.CustomCheProperties[routeDomainSuffixPropertyKey] + } else { + v2.Spec.WorkspaceDomainEndpoints.BaseDomain = v1.Spec.K8s.IngressDomain + } +} + +func v1ToV2alpha1_WorkspaceDomainEndpointsTlsSecretName(v1 *v1.CheCluster, v2 *v2alpha1.CheCluster) { + // Che server always uses the default cluster certificate for subdomain workspace endpoints on OpenShift, and the K8s.TlsSecretName on K8s. + // Because we're dealing with endpoints, let's try to use the secret on Kubernetes and nothing (e.g. the default cluster cert on OpenShift) + // which is in line with the logic of the Che server. + if !util.IsOpenShift { + v2.Spec.WorkspaceDomainEndpoints.TlsSecretName = v1.Spec.K8s.TlsSecretName + } +} + +func v1ToV2alpha1_GatewayEnabled(v1 *v1.CheCluster, v2 *v2alpha1.CheCluster) { + exposureStrategy := util.GetServerExposureStrategy(v1) + // On Kubernetes, we can have single-host realized using ingresses (that use the same host but different paths). + // This is actually not supported on DWCO where we always use the gateway for that. So here, we actually just + // ignore the Spec.K8s.SingleHostExposureType, but we need to be aware of it when converting back. + // Note that default-host is actually not supported on v2, but it is similar enough to default host that we + // treat it as such for v2. The difference between default-host and single-host is that the default-host uses + // the cluster domain itself as the base domain whereas single-host uses a configured domain. In v2 we always + // need a domain configured. + val := exposureStrategy == "single-host" || exposureStrategy == "default-host" + v2.Spec.Gateway.Enabled = &val +} + +func v1ToV2alpha1_GatewayImage(v1 *v1.CheCluster, v2 *v2alpha1.CheCluster) { + v2.Spec.Gateway.Image = v1.Spec.Server.SingleHostGatewayImage +} + +func v1ToV2alpha1_GatewayConfigurerImage(v1 *v1.CheCluster, v2 *v2alpha1.CheCluster) { + v2.Spec.Gateway.ConfigurerImage = v1.Spec.Server.SingleHostGatewayConfigSidecarImage +} + +func v1ToV2alpha1_GatewayTlsSecretName(v1 *v1.CheCluster, v2 *v2alpha1.CheCluster) { + // v1.Spec.Server.CheHostTLSSecret is used specifically for Che Host - i.e. the host under which che server is deployed. + // In DW we would only used that for subpath endpoints but wouldn't know what TLS to use for subdomain endpoints. + + v2.Spec.Gateway.TlsSecretName = v1.Spec.Server.CheHostTLSSecret +} + +func v1ToV2alpha1_K8sIngressAnnotations(v1 *v1.CheCluster, v2 *v2alpha1.CheCluster) { + // The only property in v1 spec that boils down to the ingress annotations is the K8s.IngressClass + if v1.Spec.K8s.IngressClass != "" && v1.Spec.K8s.IngressClass != defaultV2alpha1IngressClass { + if v2.Spec.K8s.IngressAnnotations == nil { + v2.Spec.K8s.IngressAnnotations = map[string]string{} + } + v2.Spec.K8s.IngressAnnotations["kubernetes.io/ingress.class"] = v1.Spec.K8s.IngressClass + } + + // This is what is applied in the deploy/ingress.go but I don't think it is applicable in our situation + // if ingressStrategy != "multi-host" && (component == DevfileRegistryName || component == PluginRegistryName) { + // annotations["nginx.ingress.kubernetes.io/rewrite-target"] = "/$1" + // } +} + +func v2alpha1ToV1_Enabled(v1 *v1.CheCluster, v2 *v2alpha1.CheCluster) { + v1.Spec.DevWorkspace.Enable = v2.Spec.IsEnabled() +} + +func v2alpha1ToV1_Host(v1 *v1.CheCluster, v2 *v2alpha1.CheCluster) { + v1.Spec.Server.CheHost = v2.Spec.Gateway.Host +} + +func v2alpha1ToV1_WorkspaceDomainEndpointsBaseDomain(v1 *v1.CheCluster, v2 *v2alpha1.CheCluster) { + if util.IsOpenShift { + if v1.Spec.Server.CustomCheProperties == nil { + v1.Spec.Server.CustomCheProperties = map[string]string{} + } + v1.Spec.Server.CustomCheProperties[routeDomainSuffixPropertyKey] = v2.Spec.WorkspaceDomainEndpoints.BaseDomain + } else { + v1.Spec.K8s.IngressDomain = v2.Spec.WorkspaceDomainEndpoints.BaseDomain + } +} + +func v2alpha1ToV1_WorkspaceDomainEndpointsTlsSecretName(v1 *v1.CheCluster, v2 *v2alpha1.CheCluster) { + // see the comments in the v1 to v2alpha1 conversion method + if !util.IsOpenShift { + v1.Spec.K8s.TlsSecretName = v2.Spec.WorkspaceDomainEndpoints.TlsSecretName + } +} + +func v2alpha1ToV1_GatewayEnabled(v1 *v1.CheCluster, v2 *v2alpha1.CheCluster) { + v1Strategy := util.GetServerExposureStrategy(v1) + v1IngressStrategy := v1.Spec.K8s.IngressStrategy + + var v2Strategy string + if v2.Spec.Gateway.IsEnabled() { + v2Strategy = "single-host" + } else { + v2Strategy = "multi-host" + } + + if v1.Spec.Server.ServerExposureStrategy == "" { + // in the original, the server exposure strategy was undefined, so we need to check whether we can leave it that way + if util.IsOpenShift { + if v2Strategy != v1Strategy { + // only update if the v2Strategy doesn't correspond to the default determined from the v1 + v1.Spec.Server.ServerExposureStrategy = v2Strategy + } + } else { + // on Kubernetes, the strategy might have been defined by the deprecated Spec.K8s.IngressStrategy + if v1IngressStrategy != "" { + // check for the default host + if v1IngressStrategy == "default-host" { + if v2Strategy != "single-host" { + v1.Spec.K8s.IngressStrategy = v2Strategy + } + } else if v2Strategy != v1Strategy { + // only change the strategy if the determined strategy would differ + v1.Spec.K8s.IngressStrategy = v2Strategy + } + } else { + if v2Strategy != v1Strategy { + // only update if the v2Strategy doesn't correspond to the default determined from the v1 + v1.Spec.Server.ServerExposureStrategy = v2Strategy + } + } + } + } else { + // The below table specifies how to convert the v2Strategy back to v1 taking into the account the original state of v1 + // from which v2 was converted before (which could also be just the default v1, if v2 was created on its own) + // + // v2Strategy | orig v1Strategy | orig v1ExposureType | resulting v1Strategy | resulting v1ExposureType + // ---------------------------------------------------------------------------------------------------- + // single | single | native | single | orig + // single | single | gateway | single | orig + // single | default | NA | default | orig + // single | multi | NA | single | orig + // multi | single | native | multi | orig + // multi | single | gateway | multi | orig + // multi | default | NA | multi | orig + // multi | multi | NA | multi | orig + // + // Notice that we don't ever want to update the singlehost exposure type. This is only used on Kubernetes and dictates how + // we are going to expose the singlehost endpoints - either using ingresses (native) or using the gateway. + // Because this distinction is not made in DWCO, which always uses the gateway, we just keep whatever the value was originally. + // + // The default-host is actually not supported in v2... but it is quite similar to single host in that everything is exposed + // through the cluster hostname and when converting to v2, we convert it to single-host + if v1Strategy != "default-host" || v2Strategy != "single-host" { + v1.Spec.Server.ServerExposureStrategy = v2Strategy + } + } +} + +func v2alpha1ToV1_GatewayImage(v1 *v1.CheCluster, v2 *v2alpha1.CheCluster) { + v1.Spec.Server.SingleHostGatewayImage = v2.Spec.Gateway.Image +} + +func v2alpha1ToV1_GatewayConfigurerImage(v1 *v1.CheCluster, v2 *v2alpha1.CheCluster) { + v1.Spec.Server.SingleHostGatewayConfigSidecarImage = v2.Spec.Gateway.ConfigurerImage +} + +func v2alpha1ToV1_GatewayTlsSecretName(v1 *v1.CheCluster, v2 *v2alpha1.CheCluster) { + // see the comments in the v1 to v2alpha1 conversion method + v1.Spec.Server.CheHostTLSSecret = v2.Spec.Gateway.TlsSecretName +} + +func v2alpha1ToV1_K8sIngressAnnotations(v1 *v1.CheCluster, v2 *v2alpha1.CheCluster) { + ingressClass := v2.Spec.K8s.IngressAnnotations["kubernetes.io/ingress.class"] + if ingressClass == "" { + ingressClass = defaultV2alpha1IngressClass + } + if v1.Spec.K8s.IngressClass != "" || ingressClass != defaultV1IngressClass { + v1.Spec.K8s.IngressClass = ingressClass + } +} diff --git a/pkg/apis/org/conversion_test.go b/pkg/apis/org/conversion_test.go new file mode 100644 index 000000000..3126087b6 --- /dev/null +++ b/pkg/apis/org/conversion_test.go @@ -0,0 +1,712 @@ +package org + +import ( + "fmt" + "reflect" + "testing" + + "github.com/che-incubator/kubernetes-image-puller-operator/pkg/apis/che/v1alpha1" + v1 "github.com/eclipse-che/che-operator/pkg/apis/org/v1" + "github.com/eclipse-che/che-operator/pkg/apis/org/v2alpha1" + "github.com/eclipse-che/che-operator/pkg/util" + "github.com/google/go-cmp/cmp" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/utils/pointer" + "sigs.k8s.io/yaml" +) + +func TestV1ToV2alpha1(t *testing.T) { + v1Obj := v1.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: "che-cluster", + Annotations: map[string]string{ + "anno1": "annoValue1", + "anno2": "annoValue2", + }, + }, + Spec: v1.CheClusterSpec{ + Auth: v1.CheClusterSpecAuth{ + IdentityProviderURL: "kachny", + }, + Database: v1.CheClusterSpecDB{ + ExternalDb: true, + PostgresImage: "postgres:the-best-version", + }, + DevWorkspace: v1.CheClusterSpecDevWorkspace{}, + ImagePuller: v1.CheClusterSpecImagePuller{ + Spec: v1alpha1.KubernetesImagePullerSpec{ + ConfigMapName: "pulled-kachna", + }, + }, + K8s: v1.CheClusterSpecK8SOnly{ + IngressDomain: "ingressDomain", + IngressClass: "traefik", + TlsSecretName: "k8sSecret", + IngressStrategy: "single-host", + }, + Metrics: v1.CheClusterSpecMetrics{ + Enable: true, + }, + Server: v1.CheClusterSpecServer{ + CheHost: "cheHost", + CheImage: "teh-che-severe", + SingleHostGatewayImage: "single-host-image-of-the-year", + CheHostTLSSecret: "cheSecret", + CustomCheProperties: map[string]string{ + "CHE_INFRA_OPENSHIFT_ROUTE_HOST_DOMAIN__SUFFIX": "routeDomain", + }, + }, + Storage: v1.CheClusterSpecStorage{ + PvcStrategy: "common", + }, + }, + } + + t.Run("origInAnnos", func(t *testing.T) { + v2 := &v2alpha1.CheCluster{} + err := V1ToV2alpha1(&v1Obj, v2) + if err != nil { + t.Error(err) + } + + anno1 := v2.Annotations["anno1"] + anno2 := v2.Annotations["anno2"] + storedV1 := v2.Annotations[v1StorageAnnotation] + + if anno1 != "annoValue1" { + t.Errorf("anno1 not copied") + } + + if anno2 != "annoValue2" { + t.Errorf("anno2 not copied") + } + + if storedV1 == "" { + t.Errorf("v2 should contain v1 data in annnotation") + } + + restoredV1Spec := v1.CheClusterSpec{} + if err = yaml.Unmarshal([]byte(storedV1), &restoredV1Spec); err != nil { + t.Error(err) + } + + if !reflect.DeepEqual(&v1Obj.Spec, &restoredV1Spec) { + t.Errorf("The spec should be restored verbatim from the annotations, but there's a diff %s", cmp.Diff(&v1Obj.Spec, &restoredV1Spec)) + } + }) + + t.Run("Enabled", func(t *testing.T) { + v2 := &v2alpha1.CheCluster{} + err := V1ToV2alpha1(&v1Obj, v2) + if err != nil { + t.Error(err) + } + + if *v2.Spec.Enabled { + t.Errorf("Unexpected v2.Spec.Enabled: %s", v2.Spec.Gateway.Host) + } + }) + + t.Run("Host-k8s", func(t *testing.T) { + v2 := &v2alpha1.CheCluster{} + err := V1ToV2alpha1(&v1Obj, v2) + if err != nil { + t.Error(err) + } + + if v2.Spec.Gateway.Host != "cheHost" { + t.Errorf("Unexpected v2.Spec.Host: %s", v2.Spec.Gateway.Host) + } + }) + + t.Run("WorkspaceDomainEndpointsBaseDomain-k8s", func(t *testing.T) { + onFakeKubernetes(func() { + v2 := &v2alpha1.CheCluster{} + err := V1ToV2alpha1(&v1Obj, v2) + if err != nil { + t.Error(err) + } + + if v2.Spec.WorkspaceDomainEndpoints.BaseDomain != "ingressDomain" { + t.Errorf("Unexpected v2.Spec.WorkspaceDomainEndpoints.BaseDomain: %s", v2.Spec.WorkspaceDomainEndpoints.BaseDomain) + } + }) + }) + + t.Run("WorkspaceDomainEndpointsBaseDomain-opensfhit", func(t *testing.T) { + onFakeOpenShift(func() { + v2 := &v2alpha1.CheCluster{} + err := V1ToV2alpha1(&v1Obj, v2) + if err != nil { + t.Error(err) + } + + if v2.Spec.WorkspaceDomainEndpoints.BaseDomain != "routeDomain" { + t.Errorf("Unexpected v2.Spec.WorkspaceWorkspaceDomainEndpoints.BaseDomainBaseDomain: %s", v2.Spec.WorkspaceDomainEndpoints.BaseDomain) + } + }) + }) + + t.Run("WorkspaceDomainEndpointsTlsSecretName_k8s", func(t *testing.T) { + onFakeKubernetes(func() { + v2 := &v2alpha1.CheCluster{} + err := V1ToV2alpha1(&v1Obj, v2) + if err != nil { + t.Error(err) + } + + if v2.Spec.WorkspaceDomainEndpoints.TlsSecretName != "k8sSecret" { + t.Errorf("Unexpected TlsSecretName") + } + }) + }) + + t.Run("WorkspaceDomainEndpointsTlsSecretName_OpenShift", func(t *testing.T) { + onFakeOpenShift(func() { + v2 := &v2alpha1.CheCluster{} + err := V1ToV2alpha1(&v1Obj, v2) + if err != nil { + t.Error(err) + } + + if v2.Spec.WorkspaceDomainEndpoints.TlsSecretName != "" { + t.Errorf("Unexpected TlsSecretName") + } + }) + }) + + t.Run("GatewayEnabled", func(t *testing.T) { + onFakeOpenShift(func() { + v2 := &v2alpha1.CheCluster{} + err := V1ToV2alpha1(&v1Obj, v2) + if err != nil { + t.Error(err) + } + + if v2.Spec.Gateway.Enabled == nil { + t.Logf("The gateway.enabled attribute should be set explicitly after the conversion.") + t.FailNow() + } + + if *v2.Spec.Gateway.Enabled { + t.Errorf("The default for OpenShift without devworkspace enabled (which is our testing object) is multihost, but we found v2 in singlehost.") + } + }) + }) + + t.Run("GatewayImage", func(t *testing.T) { + v2 := &v2alpha1.CheCluster{} + err := V1ToV2alpha1(&v1Obj, v2) + if err != nil { + t.Error(err) + } + + if v2.Spec.Gateway.Image != "single-host-image-of-the-year" { + t.Errorf("Unexpected gateway image") + } + }) + + t.Run("GatewayTlsSecretName", func(t *testing.T) { + v2 := &v2alpha1.CheCluster{} + err := V1ToV2alpha1(&v1Obj, v2) + if err != nil { + t.Error(err) + } + + if v2.Spec.Gateway.TlsSecretName != "cheSecret" { + t.Errorf("Unexpected TlsSecretName") + } + }) +} + +func TestV2alpha1ToV1(t *testing.T) { + v2Obj := v2alpha1.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: "che-cluster", + Annotations: map[string]string{ + "anno1": "annoValue1", + "anno2": "annoValue2", + }, + }, + Spec: v2alpha1.CheClusterSpec{ + Enabled: pointer.BoolPtr(true), + WorkspaceDomainEndpoints: v2alpha1.WorkspaceDomainEndpoints{ + BaseDomain: "baseDomain", + TlsSecretName: "workspaceSecret", + }, + Gateway: v2alpha1.CheGatewaySpec{ + Host: "v2Host", + Enabled: pointer.BoolPtr(true), + Image: "gateway-image", + ConfigurerImage: "configurer-image", + TlsSecretName: "superSecret", + }, + K8s: v2alpha1.CheClusterSpecK8s{ + IngressAnnotations: map[string]string{ + "kubernetes.io/ingress.class": "some-other-ingress", + "a": "b", + }, + }, + }, + } + + t.Run("origInAnnos", func(t *testing.T) { + v1 := &v1.CheCluster{} + err := V2alpha1ToV1(&v2Obj, v1) + if err != nil { + t.Error(err) + } + + anno1 := v1.Annotations["anno1"] + anno2 := v1.Annotations["anno2"] + storedV2 := v1.Annotations[v2alpha1StorageAnnotation] + + if anno1 != "annoValue1" { + t.Errorf("anno1 not copied") + } + + if anno2 != "annoValue2" { + t.Errorf("anno2 not copied") + } + + if storedV2 == "" { + t.Errorf("v1 should contain v2 data in annnotation") + } + + restoredV2Spec := v2alpha1.CheClusterSpec{} + yaml.Unmarshal([]byte(storedV2), &restoredV2Spec) + + if !reflect.DeepEqual(&v2Obj.Spec, &restoredV2Spec) { + t.Errorf("The spec should be restored verbatim from the annotations, but there's a diff %s", cmp.Diff(&v2Obj.Spec, &restoredV2Spec)) + } + }) + + t.Run("Enabled", func(t *testing.T) { + v1 := &v1.CheCluster{} + err := V2alpha1ToV1(&v2Obj, v1) + if err != nil { + t.Error(err) + } + + if !v1.Spec.DevWorkspace.Enable { + t.Errorf("Unexpected v1.Spec.DevWorkspace.Enable: %v", v1.Spec.DevWorkspace.Enable) + } + }) + + t.Run("Host", func(t *testing.T) { + v1 := &v1.CheCluster{} + err := V2alpha1ToV1(&v2Obj, v1) + if err != nil { + t.Error(err) + } + + if v1.Spec.Server.CheHost != "v2Host" { + t.Errorf("Unexpected v1.Spec.Server.CheHost: %s", v1.Spec.Server.CheHost) + } + }) + + t.Run("WorkspaceDomainEndpointsBaseDomain-k8s", func(t *testing.T) { + onFakeKubernetes(func() { + v1 := &v1.CheCluster{} + err := V2alpha1ToV1(&v2Obj, v1) + if err != nil { + t.Error(err) + } + + if v1.Spec.K8s.IngressDomain != "baseDomain" { + t.Errorf("Unexpected v1.Spec.K8s.IngressDomain: %s", v1.Spec.K8s.IngressDomain) + } + }) + }) + + t.Run("WorkspaceDomainEndpointsBaseDomain-openshift", func(t *testing.T) { + onFakeOpenShift(func() { + v1 := &v1.CheCluster{} + err := V2alpha1ToV1(&v2Obj, v1) + if err != nil { + t.Error(err) + } + + if v1.Spec.Server.CustomCheProperties[routeDomainSuffixPropertyKey] != "baseDomain" { + t.Errorf("Unexpected v1.Spec.Server.CustomCheProperties[%s]: %s", routeDomainSuffixPropertyKey, v1.Spec.Server.CustomCheProperties[routeDomainSuffixPropertyKey]) + } + }) + }) + + t.Run("WorkspaceDomainEndpointsTlsSecretName_k8s", func(t *testing.T) { + onFakeKubernetes(func() { + v1 := &v1.CheCluster{} + err := V2alpha1ToV1(&v2Obj, v1) + if err != nil { + t.Error(err) + } + + if v1.Spec.K8s.TlsSecretName != "workspaceSecret" { + t.Errorf("Unexpected TlsSecretName: %s", v1.Spec.K8s.TlsSecretName) + } + }) + }) + + t.Run("WorkspaceDomainEndpointsTlsSecretName_OpenShift", func(t *testing.T) { + onFakeOpenShift(func() { + v1 := &v1.CheCluster{} + err := V2alpha1ToV1(&v2Obj, v1) + if err != nil { + t.Error(err) + } + + if v1.Spec.K8s.TlsSecretName != "" { + t.Errorf("Unexpected TlsSecretName") + } + }) + }) + + t.Run("GatewayEnabled", func(t *testing.T) { + onFakeOpenShift(func() { + v1 := &v1.CheCluster{} + err := V2alpha1ToV1(&v2Obj, v1) + if err != nil { + t.Error(err) + } + + if util.GetServerExposureStrategy(v1) != "single-host" { + t.Logf("When gateway.enabled is true in v2, v1 is single-host.") + t.FailNow() + } + }) + }) + + t.Run("GatewayImage", func(t *testing.T) { + v1 := &v1.CheCluster{} + err := V2alpha1ToV1(&v2Obj, v1) + if err != nil { + t.Error(err) + } + + if v1.Spec.Server.SingleHostGatewayImage != "gateway-image" { + t.Errorf("Unexpected gateway image") + } + }) + + t.Run("GatewayTlsSecretName", func(t *testing.T) { + v1 := &v1.CheCluster{} + err := V2alpha1ToV1(&v2Obj, v1) + if err != nil { + t.Error(err) + } + + if v1.Spec.Server.CheHostTLSSecret != "superSecret" { + t.Errorf("Unexpected TlsSecretName: %s", v1.Spec.Server.CheHostTLSSecret) + } + }) +} + +func TestExposureStrategyConversions(t *testing.T) { + testWithExposure := func(v1ExposureStrategy string, v1IngressStrategy string, v1DevWorkspaceEnabled bool, v2GatewayEnabledChange *bool, test func(*testing.T, *v1.CheCluster)) { + origV1 := &v1.CheCluster{ + Spec: v1.CheClusterSpec{ + Server: v1.CheClusterSpecServer{ + ServerExposureStrategy: v1ExposureStrategy, + }, + K8s: v1.CheClusterSpecK8SOnly{ + IngressStrategy: v1IngressStrategy, + }, + DevWorkspace: v1.CheClusterSpecDevWorkspace{ + Enable: v1DevWorkspaceEnabled, + }, + }, + } + + t.Run(fmt.Sprintf("[v1ExposureStrategy=%v/v1IngressStrategy=%v/v1DevworkspaceEnabled=%v/v2GatewayEnabledChange=%v]", v1ExposureStrategy, v1IngressStrategy, v1DevWorkspaceEnabled, v2GatewayEnabledChange), func(t *testing.T) { + v2 := &v2alpha1.CheCluster{} + if err := V1ToV2alpha1(origV1, v2); err != nil { + t.Error(err) + } + + if v2GatewayEnabledChange != nil { + v2.Spec.Gateway.Enabled = v2GatewayEnabledChange + } + + // now convert back and run the test + v1Tested := &v1.CheCluster{} + if err := V2alpha1ToV1(v2, v1Tested); err != nil { + t.Error(err) + } + + test(t, v1Tested) + }) + } + + testWithExposure("single-host", "", true, nil, func(t *testing.T, old *v1.CheCluster) { + if old.Spec.Server.ServerExposureStrategy != "single-host" { + t.Errorf("The v1 should have single-host exposure after conversion") + } + }) + + testWithExposure("single-host", "", true, pointer.BoolPtr(false), func(t *testing.T, old *v1.CheCluster) { + if old.Spec.Server.ServerExposureStrategy != "multi-host" { + t.Errorf("The v1 should have multi-host exposure after conversion") + } + }) + + testWithExposure("multi-host", "", true, nil, func(t *testing.T, old *v1.CheCluster) { + if old.Spec.Server.ServerExposureStrategy != "multi-host" { + t.Errorf("The v1 should have multi-host exposure after conversion") + } + }) + + testWithExposure("multi-host", "", true, pointer.BoolPtr(true), func(t *testing.T, old *v1.CheCluster) { + if old.Spec.Server.ServerExposureStrategy != "single-host" { + t.Errorf("The v1 should have single-host exposure after conversion") + } + }) + + testWithExposure("default-host", "", true, nil, func(t *testing.T, old *v1.CheCluster) { + if old.Spec.Server.ServerExposureStrategy != "default-host" { + t.Errorf("The v1 should have default-host exposure after conversion") + } + }) + + testWithExposure("default-host", "", true, pointer.BoolPtr(true), func(t *testing.T, old *v1.CheCluster) { + if old.Spec.Server.ServerExposureStrategy != "default-host" { + t.Errorf("The v1 should have default-host exposure after conversion") + } + }) + + testWithExposure("default-host", "", true, pointer.BoolPtr(false), func(t *testing.T, old *v1.CheCluster) { + if old.Spec.Server.ServerExposureStrategy != "multi-host" { + t.Errorf("The v1 should have multi-host exposure after conversion") + } + }) + + onFakeKubernetes(func() { + testWithExposure("", "single-host", true, nil, func(t *testing.T, old *v1.CheCluster) { + if old.Spec.Server.ServerExposureStrategy != "" { + t.Errorf("The server exposure strategy should have been left empty after conversion but was: %v", old.Spec.Server.ServerExposureStrategy) + } + if old.Spec.K8s.IngressStrategy != "single-host" { + t.Errorf("The ingress strategy should have been unchanged after conversion but was: %v", old.Spec.K8s.IngressStrategy) + } + }) + + testWithExposure("", "single-host", true, pointer.BoolPtr(false), func(t *testing.T, old *v1.CheCluster) { + if old.Spec.Server.ServerExposureStrategy != "" { + t.Errorf("The server exposure strategy should have been left empty after conversion but was: %v", old.Spec.Server.ServerExposureStrategy) + } + if old.Spec.K8s.IngressStrategy != "multi-host" { + t.Errorf("The ingress strategy should have been set to multi-host after conversion but was: %v", old.Spec.K8s.IngressStrategy) + } + }) + + testWithExposure("", "single-host", true, pointer.BoolPtr(true), func(t *testing.T, old *v1.CheCluster) { + if old.Spec.Server.ServerExposureStrategy != "" { + t.Errorf("The server exposure strategy should have been left empty after conversion but was: %v", old.Spec.Server.ServerExposureStrategy) + } + if old.Spec.K8s.IngressStrategy != "single-host" { + t.Errorf("The ingress strategy should have been unchanged after conversion but was: %v", old.Spec.K8s.IngressStrategy) + } + }) + + testWithExposure("", "multi-host", true, nil, func(t *testing.T, old *v1.CheCluster) { + if old.Spec.Server.ServerExposureStrategy != "" { + t.Errorf("The server exposure strategy should have been left empty after conversion but was: %v", old.Spec.Server.ServerExposureStrategy) + } + if old.Spec.K8s.IngressStrategy != "multi-host" { + t.Errorf("The ingress strategy should have been unchanged after conversion but was: %v", old.Spec.K8s.IngressStrategy) + } + }) + + // the below two tests test that we're leaving the ingress strategy unchanged if it doesn't affect the effective exposure + // strategy + testWithExposure("", "multi-host", true, pointer.BoolPtr(false), func(t *testing.T, old *v1.CheCluster) { + if old.Spec.Server.ServerExposureStrategy != "" { + t.Errorf("The server exposure strategy should have been left empty after conversion but was: %v", old.Spec.Server.ServerExposureStrategy) + } + if old.Spec.K8s.IngressStrategy != "multi-host" { + t.Errorf("The ingress strategy should have been unchanged after conversion but was: %v", old.Spec.K8s.IngressStrategy) + } + }) + + testWithExposure("", "multi-host", true, pointer.BoolPtr(true), func(t *testing.T, old *v1.CheCluster) { + if old.Spec.Server.ServerExposureStrategy != "" { + t.Errorf("The server exposure strategy should have been left empty after conversion but was: %v", old.Spec.Server.ServerExposureStrategy) + } + if old.Spec.K8s.IngressStrategy != "multi-host" { + t.Errorf("The ingress strategy should have been unchanged after conversion but was: %v", old.Spec.K8s.IngressStrategy) + } + }) + + testWithExposure("", "default-host", true, nil, func(t *testing.T, old *v1.CheCluster) { + if old.Spec.Server.ServerExposureStrategy != "" { + t.Errorf("The server exposure strategy should have been left empty after conversion but was: %v", old.Spec.Server.ServerExposureStrategy) + } + if old.Spec.K8s.IngressStrategy != "default-host" { + t.Errorf("The ingress strategy should have been unchanged after conversion but was: %v", old.Spec.K8s.IngressStrategy) + } + }) + }) + + onFakeOpenShift(func() { + testWithExposure("", "", true, nil, func(t *testing.T, old *v1.CheCluster) { + if old.Spec.Server.ServerExposureStrategy != "" { + t.Errorf("The server exposure strategy should have been left empty after conversion but was: %v", old.Spec.Server.ServerExposureStrategy) + } + }) + + testWithExposure("", "", false, nil, func(t *testing.T, old *v1.CheCluster) { + if old.Spec.Server.ServerExposureStrategy != "" { + t.Errorf("The server exposure strategy should have been left empty after conversion but was: %v", old.Spec.Server.ServerExposureStrategy) + } + }) + + testWithExposure("", "", true, pointer.BoolPtr(false), func(t *testing.T, old *v1.CheCluster) { + // default on openshift with devworkspace enabled in v1 is single-host, but we've disabled the gateway in v2. So after the conversion + // v1 should change to an explicit multi-host. + if old.Spec.Server.ServerExposureStrategy != "multi-host" { + t.Errorf("The server exposure strategy should have been set to multi-host after conversion but was: %v", old.Spec.Server.ServerExposureStrategy) + } + }) + + testWithExposure("", "", true, pointer.BoolPtr(true), func(t *testing.T, old *v1.CheCluster) { + if old.Spec.Server.ServerExposureStrategy != "" { + t.Errorf("The server exposure strategy should have been left empty after conversion but was: %v", old.Spec.Server.ServerExposureStrategy) + } + }) + }) +} + +func TestFullCircleV1(t *testing.T) { + v1Obj := v1.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: "che-cluster", + Annotations: map[string]string{ + "anno1": "annoValue1", + "anno2": "annoValue2", + }, + }, + Spec: v1.CheClusterSpec{ + Auth: v1.CheClusterSpecAuth{ + IdentityProviderURL: "kachny", + }, + Database: v1.CheClusterSpecDB{ + ExternalDb: true, + PostgresImage: "postgres:the-best-version", + }, + DevWorkspace: v1.CheClusterSpecDevWorkspace{}, + ImagePuller: v1.CheClusterSpecImagePuller{ + Spec: v1alpha1.KubernetesImagePullerSpec{ + ConfigMapName: "pulled-kachna", + }, + }, + K8s: v1.CheClusterSpecK8SOnly{ + IngressDomain: "ingressDomain", + IngressClass: "traefik", + TlsSecretName: "k8sSecret", + IngressStrategy: "single-host", + }, + Metrics: v1.CheClusterSpecMetrics{ + Enable: true, + }, + Server: v1.CheClusterSpecServer{ + CheHost: "cheHost", + CheImage: "teh-che-severe", + SingleHostGatewayImage: "single-host-image-of-the-year", + CheHostTLSSecret: "cheSecret", + CustomCheProperties: map[string]string{ + "CHE_INFRA_OPENSHIFT_ROUTE_HOST_DOMAIN__SUFFIX": "routeDomain", + }, + }, + Storage: v1.CheClusterSpecStorage{ + PvcStrategy: "common", + }, + }, + } + + v2Obj := v2alpha1.CheCluster{} + V1ToV2alpha1(&v1Obj, &v2Obj) + + convertedV1 := v1.CheCluster{} + V2alpha1ToV1(&v2Obj, &convertedV1) + + if !reflect.DeepEqual(&v1Obj, &convertedV1) { + t.Errorf("V1 not equal to itself after the conversion through v2alpha1: %v", cmp.Diff(&v1Obj, &convertedV1)) + } +} + +func TestFullCircleV2(t *testing.T) { + v2Obj := v2alpha1.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: "che-cluster", + Annotations: map[string]string{ + "anno1": "annoValue1", + "anno2": "annoValue2", + }, + }, + Spec: v2alpha1.CheClusterSpec{ + Enabled: pointer.BoolPtr(true), + WorkspaceDomainEndpoints: v2alpha1.WorkspaceDomainEndpoints{ + BaseDomain: "baseDomain", + TlsSecretName: "workspaceSecret", + }, + Gateway: v2alpha1.CheGatewaySpec{ + Host: "v2Host", + Enabled: pointer.BoolPtr(true), + Image: "gateway-image", + ConfigurerImage: "configurer-image", + TlsSecretName: "superSecret", + }, + K8s: v2alpha1.CheClusterSpecK8s{ + IngressAnnotations: map[string]string{ + "kubernetes.io/ingress.class": "some-other-ingress", + "a": "b", + }, + }, + }, + } + + v1Obj := v1.CheCluster{} + V2alpha1ToV1(&v2Obj, &v1Obj) + + convertedV2 := v2alpha1.CheCluster{} + V1ToV2alpha1(&v1Obj, &convertedV2) + + if !reflect.DeepEqual(&v2Obj, &convertedV2) { + t.Errorf("V2alpha1 not equal to itself after the conversion through v1: %v", cmp.Diff(&v2Obj, &convertedV2)) + } +} + +func onFakeOpenShift(f func()) { + origOpenshift := util.IsOpenShift + origOpenshift4 := util.IsOpenShift4 + + util.IsOpenShift = true + util.IsOpenShift4 = true + + f() + + util.IsOpenShift = origOpenshift + util.IsOpenShift4 = origOpenshift4 +} + +func onFakeKubernetes(f func()) { + origOpenshift := util.IsOpenShift + origOpenshift4 := util.IsOpenShift4 + + util.IsOpenShift = false + util.IsOpenShift4 = false + + f() + + util.IsOpenShift = origOpenshift + util.IsOpenShift4 = origOpenshift4 +} + +func toString(b *bool) string { + if b == nil { + return "nil" + } else if *b { + return "true" + } else { + return "false" + } +} diff --git a/pkg/apis/org/v1/che_types.go b/pkg/apis/org/v1/che_types.go index ff3495ca0..cc6bf8314 100644 --- a/pkg/apis/org/v1/che_types.go +++ b/pkg/apis/org/v1/che_types.go @@ -20,6 +20,7 @@ package v1 import ( chev1alpha1 "github.com/che-incubator/kubernetes-image-puller-operator/pkg/apis/che/v1alpha1" + "github.com/eclipse-che/che-operator/pkg/apis/org/v2alpha1" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" @@ -671,6 +672,10 @@ type CheClusterStatus struct { // +operator-sdk:gen-csv:customresourcedefinitions.statusDescriptors.displayName="Help link" // +operator-sdk:gen-csv:customresourcedefinitions.statusDescriptors.x-descriptors="urn:alm:descriptor:org.w3:link" HelpLink string `json:"helpLink,omitempty"` + + // The status of the Devworkspace subsystem + // +optional + DevworkspaceStatus v2alpha1.CheClusterStatus `json:"devworkspaceStatus,omitempty"` } // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object @@ -679,6 +684,7 @@ type CheClusterStatus struct { // +k8s:openapi-gen=true // +kubebuilder:subresource:status // +operator-sdk:gen-csv:customresourcedefinitions.displayName="Eclipse Che Cluster" +// +kubebuilder:storageversion type CheCluster struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` diff --git a/pkg/apis/org/v1/zz_generated.deepcopy.go b/pkg/apis/org/v1/zz_generated.deepcopy.go index 631a2bb7f..e46e9d6fa 100644 --- a/pkg/apis/org/v1/zz_generated.deepcopy.go +++ b/pkg/apis/org/v1/zz_generated.deepcopy.go @@ -265,6 +265,7 @@ func (in *CheClusterSpecStorage) DeepCopy() *CheClusterSpecStorage { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *CheClusterStatus) DeepCopyInto(out *CheClusterStatus) { *out = *in + out.DevworkspaceStatus = in.DevworkspaceStatus return } diff --git a/pkg/apis/org/v2alpha1/checluster.go b/pkg/apis/org/v2alpha1/checluster.go new file mode 100644 index 000000000..b4ad95ffa --- /dev/null +++ b/pkg/apis/org/v2alpha1/checluster.go @@ -0,0 +1,203 @@ +// +// Copyright (c) 2012-2021 Red Hat, Inc. +// This program and the accompanying materials are made +// available under the terms of the Eclipse Public License 2.0 +// which is available at https://www.eclipse.org/legal/epl-2.0/ +// +// SPDX-License-Identifier: EPL-2.0 +// +// Contributors: +// Red Hat, Inc. - initial API and implementation +// + +package v2alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +// NOTE THAT THIS IS CURRENTLY INTENTIONALLY NOT PART OF THE GENERATED API +// +// (the generator comments are switched off by using a '\' instead of a '+') +// +// This is so that we can start using this spec in the code before we are +// actually ready to start deploying it in the cluster. +// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +// CheClusterSpec holds the configuration of the Che controller. +// \k8s:openapi-gen=true +type CheClusterSpec struct { + // If false, Che is disabled and does not resolve the devworkspaces with the che routingClass. + Enabled *bool `json:"enabled,omitempty"` + + // Configuration of the workspace endpoints that are exposed on separate domains, as opposed to the subpaths + // of the gateway. + WorkspaceDomainEndpoints `json:"workspaceDomainEndpoints,omitempty"` + + // Gateway contains the configuration of the gateway used for workspace endpoint routing. + Gateway CheGatewaySpec `json:"gateway,omitempty"` + + // K8s contains the configuration specific only to Kubernetes + K8s CheClusterSpecK8s `json:"k8s,omitempty"` +} + +type WorkspaceDomainEndpoints struct { + // The workspace endpoints that need to be deployed on a subdomain will be deployed on subdomains of this base domain. + // This is mandatory on Kubernetes. On OpenShift, an attempt is made to automatically figure out the base domain of + // the routes. The resolved value of this property is written to the status. + BaseDomain string `json:"baseDomain,omitempty"` + + // Name of a secret that will be used to setup ingress/route TLS certificate for the workspace endpoints. The endpoints + // will be on randomly named subdomains of the `BaseDomain` and therefore the TLS certificate should a wildcard certificate. + // + // When the field is empty string, the endpoints are served over unecrypted HTTP. This might be OK because the workspace endpoints + // generally only expose applications in debugging sessions during development. + // + // The secret is assumed to exist in the same namespace as the CheCluster CR is copied to the namespace of the devworkspace on + // devworkspace start (so that the ingresses on Kubernetes can reference it). + // + // The secret has to be of type "tls". + // + // +optional + TlsSecretName string `json:"tlsSecretName,omitempty"` +} + +type CheGatewaySpec struct { + // Enabled enables or disables routing of the url rewrite supporting devworkspace endpoints + // through a common gateway (the hostname of which is defined by the Host). + // + // Default value is "true" meaning that the gateway is enabled. + // + // If set to true (i.e. the gateway is enabled), endpoints marked using the "urlRewriteSupported" attribute + // are exposed on unique subpaths of the Host, while the rest of the devworkspace endpoints are exposed + // on subdomains of the Host. + // + // If set to false (i.e. the gateway is disabled), all endpoints are deployed on subdomains of + // the Host. + Enabled *bool `json:"enabled,omitempty"` + + // Host is the full host name used to expose devworkspace endpoints that support url rewriting reverse proxy. + // See the gateway.enabled attribute for a more detailed description of where and how are devworkspace endpoints + // exposed in various configurations. + // + // This attribute is mandatory on Kubernetes, optional on OpenShift. + Host string `json:"host,omitempty"` + + // Name of a secret that will be used to setup ingress/route TLS certificate for the gateway host. + // When the field is empty string, the default cluster certificate will be used. + // The secret is assumed to exist in the same namespace as the CheCluster CR. + // + // The secret has to be of type "tls". + // + // +optional + TlsSecretName string `json:"tlsSecretName,omitempty"` + + // Image is the docker image to use for the Che gateway. This is only used if Enabled is true. + // If not defined in the CR, it is taken from + // the `RELATED_IMAGE_gateway` environment variable of the operator deployment/pod. If not defined there, + // it defaults to a hardcoded value. + Image string `json:"image,omitempty"` + + // ConfigurerImage is the docker image to use for the sidecar of the Che gateway that is + // used to configure it. This is only used when Enabled is true. If not defined in the CR, + // it is taken from the `RELATED_IMAGE_gateway_configurer` environment variable of the operator + // deployment/pod. If not defined there, it defaults to a hardcoded value. + ConfigurerImage string `json:"configurerImage,omitempty"` +} + +// CheClusterSpecK8s contains the configuration options specific to Kubernetes only. +type CheClusterSpecK8s struct { + // IngressAnnotations are the annotations to be put on the generated ingresses. This can be used to + // configure the ingress class and the ingress-controller-specific behavior for both the gateway + // and the ingresses created to expose the Devworkspace component endpoints. + // When not specified, this defaults to: + // + // kubernetes.io/ingress.class: "nginx" + // nginx.ingress.kubernetes.io/proxy-read-timeout: "3600", + // nginx.ingress.kubernetes.io/proxy-connect-timeout: "3600", + // nginx.ingress.kubernetes.io/ssl-redirect: "true" + // + // +optional + IngressAnnotations map[string]string `json:"ingressAnnotations,omitempty"` +} + +// GatewayPhase describes the different phases of the Che gateway lifecycle +type GatewayPhase string + +const ( + GatewayPhaseInitializing = "Initializing" + GatewayPhaseEstablished = "Established" + GatewayPhaseInactive = "Inactive" +) + +// ClusterPhase describes the different phases of the Che cluster lifecycle +type ClusterPhase string + +const ( + ClusterPhaseActive = "Active" + ClusterPhaseInactive = "Inactive" + ClusterPhasePendingDeletion = "PendingDeletion" +) + +// CheClusterStatus contains the status of the CheCluster object +// \k8s:openapi-gen=true +type CheClusterStatus struct { + // GatewayPhase specifies the phase in which the gateway deployment currently is. + // If the gateway is disabled, the phase is "Inactive". + GatewayPhase GatewayPhase `json:"gatewayPhase,omitempty"` + + // GatewayHost is the resolved host of the ingress/route. This is equal to the Host in the spec + // on Kubernetes but contains the actual host name of the route if Host is unspecified on OpenShift. + GatewayHost string `json:"gatewayHost,omitempty"` + + // Phase is the phase in which the Che cluster as a whole finds itself in. + Phase ClusterPhase `json:"phase,omitempty"` + + // A brief CamelCase message indicating details about why the Che cluster is in this state. + Reason string `json:"reason,omitempty"` + + // Message contains further human-readable info for why the Che cluster is in the phase it currently is. + Message string `json:"message,omitempty"` + + // The resolved workspace base domain. This is either the copy of the explicitly defined property of the + // same name in the spec or, if it is undefined in the spec and we're running on OpenShift, the automatically + // resolved basedomain for routes. + WorkspaceBaseDomain string `json:"workspaceBaseDomain,omitempty"` +} + +// CheCluster is the configuration of the CheCluster layer of Devworkspace. +// \k8s:openapi-gen=true +// \kubebuilder:subresource:status +// \kubebuilder:resource:path=checlusters,scope=Namespaced +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +type CheCluster struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec CheClusterSpec `json:"spec,omitempty"` + Status CheClusterStatus `json:"status,omitempty"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// CheClusterList is the list type for CheCluster +type CheClusterList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []CheCluster `json:"items"` +} + +func init() { + SchemeBuilder.Register(&CheCluster{}, &CheClusterList{}) +} + +// IsEnabled is a utility method checking the `Enabled` property using its optional value or the default. +func (s *CheClusterSpec) IsEnabled() bool { + return s.Enabled == nil || *s.Enabled +} + +// IsEnabled is a utility method checking the `Enabled` property using its optional value or the default. +func (s *CheGatewaySpec) IsEnabled() bool { + return s.Enabled == nil || *s.Enabled +} diff --git a/pkg/apis/org/v2alpha1/doc.go b/pkg/apis/org/v2alpha1/doc.go new file mode 100644 index 000000000..d1125f42c --- /dev/null +++ b/pkg/apis/org/v2alpha1/doc.go @@ -0,0 +1,25 @@ +// +// Copyright (c) 2012-2021 Red Hat, Inc. +// This program and the accompanying materials are made +// available under the terms of the Eclipse Public License 2.0 +// which is available at https://www.eclipse.org/legal/epl-2.0/ +// +// SPDX-License-Identifier: EPL-2.0 +// +// Contributors: +// Red Hat, Inc. - initial API and implementation +// + +// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +// NOTE THAT THIS IS CURRENTLY INTENTIONALLY NOT PART OF THE GENERATED API +// +// (the generator comments are switched off by using a '\' instead of a '+') +// +// This is so that we can start using this spec in the code before we are +// actually ready to start deploying it in the cluster. +// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +// Package v2alpha1 contains API Schema definitions for the org v2alpha1 API group +// \k8s:deepcopy-gen=package,register +// \groupName=org.eclipse.che +package v2alpha1 diff --git a/pkg/apis/org/v2alpha1/register.go b/pkg/apis/org/v2alpha1/register.go new file mode 100644 index 000000000..94715b5ca --- /dev/null +++ b/pkg/apis/org/v2alpha1/register.go @@ -0,0 +1,42 @@ +// +// Copyright (c) 2012-2021 Red Hat, Inc. +// This program and the accompanying materials are made +// available under the terms of the Eclipse Public License 2.0 +// which is available at https://www.eclipse.org/legal/epl-2.0/ +// +// SPDX-License-Identifier: EPL-2.0 +// +// Contributors: +// Red Hat, Inc. - initial API and implementation +// +// NOTE: Boilerplate only. Ignore this file. + +// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +// NOTE THAT THIS IS CURRENTLY INTENTIONALLY NOT PART OF THE GENERATED API +// +// (the generator comments are switched off by using a '\' instead of a '+') +// +// This is so that we can start using this spec in the code before we are +// actually ready to start deploying it in the cluster. +// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +// Package v2alpha1 contains API Schema definitions for the org v2alpha1 API group +// \k8s:deepcopy-gen=package,register +// \groupName=org.eclipse.che +package v2alpha1 + +import ( + "k8s.io/apimachinery/pkg/runtime/schema" + "sigs.k8s.io/controller-runtime/pkg/scheme" +) + +var ( + // SchemeGroupVersion is group version used to register these objects + SchemeGroupVersion = schema.GroupVersion{Group: "org.eclipse.che", Version: "v2alpha1"} + + // SchemeBuilder is used to add go types to the GroupVersionKind scheme + SchemeBuilder = &scheme.Builder{GroupVersion: SchemeGroupVersion} + + // AddToScheme adds the types in this group-version to the given scheme. + AddToScheme = SchemeBuilder.AddToScheme +) diff --git a/pkg/apis/org/v2alpha1/zz_generated.deepcopy.go b/pkg/apis/org/v2alpha1/zz_generated.deepcopy.go new file mode 100644 index 000000000..28a31da29 --- /dev/null +++ b/pkg/apis/org/v2alpha1/zz_generated.deepcopy.go @@ -0,0 +1,148 @@ +// +build !ignore_autogenerated + +// Code generated by operator-sdk. DO NOT EDIT. + +package v2alpha1 + +import ( + runtime "k8s.io/apimachinery/pkg/runtime" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CheCluster) DeepCopyInto(out *CheCluster) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + out.Status = in.Status + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CheCluster. +func (in *CheCluster) DeepCopy() *CheCluster { + if in == nil { + return nil + } + out := new(CheCluster) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *CheCluster) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CheClusterList) DeepCopyInto(out *CheClusterList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]CheCluster, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CheClusterList. +func (in *CheClusterList) DeepCopy() *CheClusterList { + if in == nil { + return nil + } + out := new(CheClusterList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *CheClusterList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CheClusterSpec) DeepCopyInto(out *CheClusterSpec) { + *out = *in + in.Gateway.DeepCopyInto(&out.Gateway) + in.K8s.DeepCopyInto(&out.K8s) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CheClusterSpec. +func (in *CheClusterSpec) DeepCopy() *CheClusterSpec { + if in == nil { + return nil + } + out := new(CheClusterSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CheClusterSpecK8s) DeepCopyInto(out *CheClusterSpecK8s) { + *out = *in + if in.IngressAnnotations != nil { + in, out := &in.IngressAnnotations, &out.IngressAnnotations + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CheClusterSpecK8s. +func (in *CheClusterSpecK8s) DeepCopy() *CheClusterSpecK8s { + if in == nil { + return nil + } + out := new(CheClusterSpecK8s) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CheClusterStatus) DeepCopyInto(out *CheClusterStatus) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CheClusterStatus. +func (in *CheClusterStatus) DeepCopy() *CheClusterStatus { + if in == nil { + return nil + } + out := new(CheClusterStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CheGatewaySpec) DeepCopyInto(out *CheGatewaySpec) { + *out = *in + if in.Enabled != nil { + in, out := &in.Enabled, &out.Enabled + *out = new(bool) + **out = **in + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CheGatewaySpec. +func (in *CheGatewaySpec) DeepCopy() *CheGatewaySpec { + if in == nil { + return nil + } + out := new(CheGatewaySpec) + in.DeepCopyInto(out) + return out +} diff --git a/vendor/modules.txt b/vendor/modules.txt index d0b38c866..34d5124e7 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -404,6 +404,7 @@ k8s.io/klog # k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a k8s.io/kube-openapi/pkg/util/proto # k8s.io/utils v0.0.0-20191010214722-8d271d903fe4 +## explicit k8s.io/utils/buffer k8s.io/utils/integer k8s.io/utils/pointer