Sync the certificate from che-git-self-signed-cert to user namespaces (#1222)

feat: Sync the git tls certificate configuration in a format digestable by DWO.

Other slightly related changes:
* Make sure the version content annotations survive across multiple conversions
between v1 and v2alpha1.
* Add DWO watch labels so that our stuff is picked up.
* irifrance/gini has moved to go-air/gini
pull/1249/head
Lukas Krejci 2021-12-15 15:41:55 +01:00 committed by GitHub
parent 777f2687bf
commit 0e72bb2ca8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
79 changed files with 2896 additions and 240 deletions

View File

@ -300,7 +300,7 @@
| [github.com/golangplus/bytes@45c989fe545070ef7c9003cf1998bb195c61731a](https://github.com/golangplus/bytes.git) | BSD-3-Clause | [clearlydefined](https://clearlydefined.io/definitions/git/github/golangplus/bytes/45c989fe545070ef7c9003cf1998bb195c61731a) | | [github.com/golangplus/bytes@45c989fe545070ef7c9003cf1998bb195c61731a](https://github.com/golangplus/bytes.git) | BSD-3-Clause | [clearlydefined](https://clearlydefined.io/definitions/git/github/golangplus/bytes/45c989fe545070ef7c9003cf1998bb195c61731a) |
| [github.com/grpc-ecosystem/grpc-health-probe@v0.3.2](https://github.com/grpc-ecosystem/grpc-health-probe.git) | Apache-2.0 | [clearlydefined](https://clearlydefined.io/definitions/git/github/grpc-ecosystem/grpc-health-probe/71ebd03865797e785bdc7ae6fababe548b75188e) | | [github.com/grpc-ecosystem/grpc-health-probe@v0.3.2](https://github.com/grpc-ecosystem/grpc-health-probe.git) | Apache-2.0 | [clearlydefined](https://clearlydefined.io/definitions/git/github/grpc-ecosystem/grpc-health-probe/71ebd03865797e785bdc7ae6fababe548b75188e) |
| [github.com/hokaccha/go-prettyjson@108c894c2c0e4a3236172e3698c14f1e3199548d](https://github.com/hokaccha/go-prettyjson.git) | MIT | [clearlydefined](https://clearlydefined.io/definitions/git/github/hokaccha/go-prettyjson/108c894c2c0e4a3236172e3698c14f1e3199548d) | | [github.com/hokaccha/go-prettyjson@108c894c2c0e4a3236172e3698c14f1e3199548d](https://github.com/hokaccha/go-prettyjson.git) | MIT | [clearlydefined](https://clearlydefined.io/definitions/git/github/hokaccha/go-prettyjson/108c894c2c0e4a3236172e3698c14f1e3199548d) |
| [github.com/irifrance/gini@v1.0.1](https://github.com/irifrance/gini.git) | MIT | [clearlydefined](https://clearlydefined.io/definitions/git/github/irifrance/gini/ab553a98575687dd98cf73371b2fc398f41d10fc) | | [github.com/go-air/gini@v1.0.1](https://github.com/go-air/gini.git) | MIT | [clearlydefined](https://clearlydefined.io/definitions/git/github/go-air/gini/ab553a98575687dd98cf73371b2fc398f41d10fc) |
| [github.com/itchyny/astgen-go@cf3ea398f64584ef328f8fa3e0281536dbaffa4b](https://github.com/itchyny/astgen-go.git) | MIT | [clearlydefined](https://clearlydefined.io/definitions/git/github/itchyny/astgen-go/cf3ea398f64584ef328f8fa3e0281536dbaffa4b) | | [github.com/itchyny/astgen-go@cf3ea398f64584ef328f8fa3e0281536dbaffa4b](https://github.com/itchyny/astgen-go.git) | MIT | [clearlydefined](https://clearlydefined.io/definitions/git/github/itchyny/astgen-go/cf3ea398f64584ef328f8fa3e0281536dbaffa4b) |
| [github.com/itchyny/gojq@v0.11.0](https://github.com/itchyny/gojq.git) | MIT | [clearlydefined](https://clearlydefined.io/definitions/git/github/itchyny/gojq/d33449f4c07af896f91db06c7b64052c92ebe42b) | | [github.com/itchyny/gojq@v0.11.0](https://github.com/itchyny/gojq.git) | MIT | [clearlydefined](https://clearlydefined.io/definitions/git/github/itchyny/gojq/d33449f4c07af896f91db06c7b64052c92ebe42b) |
| [github.com/jackc/pgmock@13a1b77aafa2641ad31b655a18e8c3605ef55e2d](https://github.com/jackc/pgmock.git) | MIT | [clearlydefined](https://clearlydefined.io/definitions/git/github/jackc/pgmock/13a1b77aafa2641ad31b655a18e8c3605ef55e2d) | | [github.com/jackc/pgmock@13a1b77aafa2641ad31b655a18e8c3605ef55e2d](https://github.com/jackc/pgmock.git) | MIT | [clearlydefined](https://clearlydefined.io/definitions/git/github/jackc/pgmock/13a1b77aafa2641ad31b655a18e8c3605ef55e2d) |

View File

@ -50,7 +50,7 @@ func V1ToV2alpha1(v1 *v1.CheCluster, v2 *v2alpha1.CheCluster) error {
} }
} }
v2.ObjectMeta = v1.ObjectMeta v2.ObjectMeta = *v1.ObjectMeta.DeepCopy()
v2.Spec = v2Spec v2.Spec = v2Spec
v1Spec, err := yaml.Marshal(v1.Spec) v1Spec, err := yaml.Marshal(v1.Spec)
@ -89,7 +89,7 @@ func V2alpha1ToV1(v2 *v2alpha1.CheCluster, v1Obj *v1.CheCluster) error {
} }
} }
v1Obj.ObjectMeta = v2.ObjectMeta v1Obj.ObjectMeta = *v2.ObjectMeta.DeepCopy()
v1Obj.Spec = v1Spec v1Obj.Spec = v1Spec
v1Obj.Status = v1.CheClusterStatus{} v1Obj.Status = v1.CheClusterStatus{}

View File

@ -17,6 +17,8 @@ import (
"reflect" "reflect"
"testing" "testing"
"github.com/stretchr/testify/assert"
"github.com/che-incubator/kubernetes-image-puller-operator/api/v1alpha1" "github.com/che-incubator/kubernetes-image-puller-operator/api/v1alpha1"
v1 "github.com/eclipse-che/che-operator/api/v1" v1 "github.com/eclipse-che/che-operator/api/v1"
"github.com/eclipse-che/che-operator/api/v2alpha1" "github.com/eclipse-che/che-operator/api/v2alpha1"
@ -691,17 +693,13 @@ func TestFullCircleV1(t *testing.T) {
convertedV1 := v1.CheCluster{} convertedV1 := v1.CheCluster{}
V2alpha1ToV1(&v2Obj, &convertedV1) V2alpha1ToV1(&v2Obj, &convertedV1)
if !reflect.DeepEqual(&v1Obj, &convertedV1) { assert.Empty(t, convertedV1.Annotations[v1StorageAnnotation])
t.Errorf("V1 not equal to itself after the conversion through v2alpha1: %v", cmp.Diff(&v1Obj, &convertedV1)) assert.NotEmpty(t, convertedV1.Annotations[v2alpha1StorageAnnotation])
}
if convertedV1.Annotations[v1StorageAnnotation] != "" { // remove v2 content annotation on the convertedV1 so that it doesn't interfere with the equality.
t.Errorf("The v1 storage annotations should not be present on the v1 object") delete(convertedV1.Annotations, v2alpha1StorageAnnotation)
}
if convertedV1.Annotations[v2alpha1StorageAnnotation] == "" { assert.Equal(t, &v1Obj, &convertedV1)
t.Errorf("The v2alpha1 storage annotation should be present on the v1 object")
}
} }
func TestFullCircleV2(t *testing.T) { func TestFullCircleV2(t *testing.T) {
@ -744,17 +742,13 @@ func TestFullCircleV2(t *testing.T) {
convertedV2 := v2alpha1.CheCluster{} convertedV2 := v2alpha1.CheCluster{}
V1ToV2alpha1(&v1Obj, &convertedV2) V1ToV2alpha1(&v1Obj, &convertedV2)
if !reflect.DeepEqual(&v2Obj, &convertedV2) { assert.Empty(t, convertedV2.Annotations[v2alpha1StorageAnnotation])
t.Errorf("V2alpha1 not equal to itself after the conversion through v1: %v", cmp.Diff(&v2Obj, &convertedV2)) assert.NotEmpty(t, convertedV2.Annotations[v1StorageAnnotation])
}
if convertedV2.Annotations[v2alpha1StorageAnnotation] != "" { // remove v1 content annotation on the convertedV1 so that it doesn't interfere with the equality.
t.Errorf("The v2alpha1 storage annotations should not be present on the v2alpha1 object") delete(convertedV2.Annotations, v1StorageAnnotation)
}
if convertedV2.Annotations[v1StorageAnnotation] == "" { assert.Equal(t, &v2Obj, &convertedV2)
t.Errorf("The v1 storage annotation should be present on the v2alpha1 object")
}
} }
func onFakeOpenShift(f func()) { func onFakeOpenShift(f func()) {

View File

@ -19,6 +19,8 @@ import (
"strconv" "strconv"
"strings" "strings"
"k8s.io/utils/pointer"
"github.com/eclipse-che/che-operator/pkg/util" "github.com/eclipse-che/che-operator/pkg/util"
"github.com/eclipse-che/che-operator/pkg/deploy/gateway" "github.com/eclipse-che/che-operator/pkg/deploy/gateway"
@ -672,7 +674,7 @@ func determineEndpointScheme(e dw.Endpoint) string {
scheme = string(e.Protocol) scheme = string(e.Protocol)
} }
upgradeToSecure := e.Secure upgradeToSecure := pointer.BoolPtrDerefOr(e.Secure, false)
// gateway is always on HTTPS, so if the endpoint is served through the gateway, we need to use the TLS'd variant. // gateway is always on HTTPS, so if the endpoint is served through the gateway, we need to use the TLS'd variant.
if e.Attributes.GetString(urlRewriteSupportedEndpointAttributeName, nil) == "true" { if e.Attributes.GetString(urlRewriteSupportedEndpointAttributeName, nil) == "true" {

View File

@ -18,6 +18,8 @@ import (
"strings" "strings"
"testing" "testing"
"k8s.io/utils/pointer"
"github.com/eclipse-che/che-operator/pkg/util" "github.com/eclipse-che/che-operator/pkg/util"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -145,7 +147,7 @@ func subdomainDevWorkspaceRouting() *dwo.DevWorkspaceRouting {
Exposure: dw.PublicEndpointExposure, Exposure: dw.PublicEndpointExposure,
Protocol: "http", Protocol: "http",
Path: "/2.js", Path: "/2.js",
Secure: true, Secure: pointer.BoolPtr(true),
}, },
{ {
Name: "e3", Name: "e3",
@ -186,7 +188,7 @@ func relocatableDevWorkspaceRouting() *dwo.DevWorkspaceRouting {
Exposure: dw.PublicEndpointExposure, Exposure: dw.PublicEndpointExposure,
Protocol: "http", Protocol: "http",
Path: "/2.js", Path: "/2.js",
Secure: true, Secure: pointer.BoolPtr(true),
Attributes: attributes.Attributes{ Attributes: attributes.Attributes{
urlRewriteSupportedEndpointAttributeName: apiext.JSON{Raw: []byte("\"true\"")}, urlRewriteSupportedEndpointAttributeName: apiext.JSON{Raw: []byte("\"true\"")},
}, },

View File

@ -78,7 +78,7 @@ func (r *CheUserNamespaceReconciler) SetupWithManager(mgr ctrl.Manager) error {
For(obj). For(obj).
Watches(&source.Kind{Type: &corev1.Secret{}}, r.watchRulesForSecrets(ctx)). Watches(&source.Kind{Type: &corev1.Secret{}}, r.watchRulesForSecrets(ctx)).
Watches(&source.Kind{Type: &corev1.ConfigMap{}}, r.watchRulesForConfigMaps(ctx)). Watches(&source.Kind{Type: &corev1.ConfigMap{}}, r.watchRulesForConfigMaps(ctx)).
Watches(&source.Kind{Type: &v1.CheCluster{}}, r.triggerAllNamespaces(ctx)) Watches(&source.Kind{Type: &v1.CheCluster{}}, r.triggerAllNamespaces())
return bld.Complete(r) return bld.Complete(r)
} }
@ -155,7 +155,7 @@ func (r *CheUserNamespaceReconciler) isInManagedNamespace(ctx context.Context, o
return err == nil && info != nil && info.OwnerUid != "" return err == nil && info != nil && info.OwnerUid != ""
} }
func (r *CheUserNamespaceReconciler) triggerAllNamespaces(ctx context.Context) handler.EventHandler { func (r *CheUserNamespaceReconciler) triggerAllNamespaces() handler.EventHandler {
return handler.EnqueueRequestsFromMapFunc( return handler.EnqueueRequestsFromMapFunc(
handler.MapFunc(func(obj client.Object) []reconcile.Request { handler.MapFunc(func(obj client.Object) []reconcile.Request {
nss := r.namespaceCache.GetAllKnownNamespaces() nss := r.namespaceCache.GetAllKnownNamespaces()
@ -228,6 +228,11 @@ func (r *CheUserNamespaceReconciler) Reconcile(ctx context.Context, req ctrl.Req
return ctrl.Result{}, err return ctrl.Result{}, err
} }
if err = r.reconcileGitTlsCertificate(ctx, req.Name, checluster, deployContext); err != nil {
logrus.Errorf("Failed to reconcile Che git TLS certificate into namespace '%s': %v", req.Name, err)
return ctrl.Result{}, err
}
return ctrl.Result{}, nil return ctrl.Result{}, nil
} }
@ -286,7 +291,8 @@ func (r *CheUserNamespaceReconciler) reconcileSelfSignedCert(ctx context.Context
Name: targetCertName, Name: targetCertName,
Namespace: targetNs, Namespace: targetNs,
Labels: defaults.AddStandardLabelsForComponent(checluster, userSettingsComponentLabelValue, map[string]string{ Labels: defaults.AddStandardLabelsForComponent(checluster, userSettingsComponentLabelValue, map[string]string{
constants.DevWorkspaceMountLabel: "true", constants.DevWorkspaceMountLabel: "true",
constants.DevWorkspaceWatchSecretLabel: "true",
}), }),
Annotations: map[string]string{ Annotations: map[string]string{
constants.DevWorkspaceMountAsAnnotation: "file", constants.DevWorkspaceMountAsAnnotation: "file",
@ -330,7 +336,8 @@ func (r *CheUserNamespaceReconciler) reconcileTrustedCerts(ctx context.Context,
Name: targetConfigMapName, Name: targetConfigMapName,
Namespace: targetNs, Namespace: targetNs,
Labels: defaults.AddStandardLabelsForComponent(checluster, userSettingsComponentLabelValue, map[string]string{ Labels: defaults.AddStandardLabelsForComponent(checluster, userSettingsComponentLabelValue, map[string]string{
constants.DevWorkspaceMountLabel: "true", constants.DevWorkspaceMountLabel: "true",
constants.DevWorkspaceWatchConfigMapLabel: "true",
}), }),
Annotations: addToFirst(sourceMap.Annotations, map[string]string{ Annotations: addToFirst(sourceMap.Annotations, map[string]string{
constants.DevWorkspaceMountAsAnnotation: "file", constants.DevWorkspaceMountAsAnnotation: "file",
@ -397,7 +404,8 @@ func (r *CheUserNamespaceReconciler) reconcileProxySettings(ctx context.Context,
} }
requiredLabels := defaults.AddStandardLabelsForComponent(checluster, userSettingsComponentLabelValue, map[string]string{ requiredLabels := defaults.AddStandardLabelsForComponent(checluster, userSettingsComponentLabelValue, map[string]string{
constants.DevWorkspaceMountLabel: "true", constants.DevWorkspaceMountLabel: "true",
constants.DevWorkspaceWatchConfigMapLabel: "true",
}) })
requiredAnnos := map[string]string{ requiredAnnos := map[string]string{
constants.DevWorkspaceMountAsAnnotation: "env", constants.DevWorkspaceMountAsAnnotation: "env",
@ -421,6 +429,50 @@ func (r *CheUserNamespaceReconciler) reconcileProxySettings(ctx context.Context,
return err return err
} }
func (r *CheUserNamespaceReconciler) reconcileGitTlsCertificate(ctx context.Context, targetNs string, checluster *v2alpha1.CheCluster, deployContext *deploy.DeployContext) error {
targetName := prefixedName(checluster, "git-tls-creds")
delConfigMap := func() error {
_, err := deploy.Delete(deployContext, client.ObjectKey{Name: targetName, Namespace: targetNs}, &corev1.Secret{})
return err
}
clusterv1 := org.AsV1(checluster)
if !clusterv1.Spec.Server.GitSelfSignedCert {
return delConfigMap()
}
gitCert := &corev1.ConfigMap{}
if err := deployContext.ClusterAPI.Client.Get(ctx, client.ObjectKey{Name: deploy.GitSelfSignedCertsConfigMapName, Namespace: checluster.Namespace}, gitCert); err != nil {
if !errors.IsNotFound(err) {
return err
}
return delConfigMap()
}
target := corev1.ConfigMap{
TypeMeta: metav1.TypeMeta{
Kind: "ConfigMap",
APIVersion: "v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: targetName,
Namespace: targetNs,
Labels: defaults.AddStandardLabelsForComponent(checluster, userSettingsComponentLabelValue, map[string]string{
constants.DevWorkspaceGitTLSLabel: "true",
constants.DevWorkspaceWatchConfigMapLabel: "true",
}),
},
Data: map[string]string{
"host": gitCert.Data["githost"],
"certificate": gitCert.Data["ca.crt"],
},
}
_, err := deploy.DoSync(deployContext, &target, deploy.ConfigMapDiffOpts)
return err
}
func prefixedName(checluster *v2alpha1.CheCluster, name string) string { func prefixedName(checluster *v2alpha1.CheCluster, name string) string {
return checluster.Name + "-" + checluster.Namespace + "-" + name return checluster.Name + "-" + checluster.Namespace + "-" + name
} }

View File

@ -58,7 +58,8 @@ func setupCheCluster(t *testing.T, ctx context.Context, cl client.Client, scheme
}, },
Spec: v1.CheClusterSpec{ Spec: v1.CheClusterSpec{
Server: v1.CheClusterSpecServer{ Server: v1.CheClusterSpecServer{
CheHost: "che-host", CheHost: "che-host",
GitSelfSignedCert: true,
CustomCheProperties: map[string]string{ CustomCheProperties: map[string]string{
"CHE_INFRA_OPENSHIFT_ROUTE_HOST_DOMAIN__SUFFIX": "root-domain", "CHE_INFRA_OPENSHIFT_ROUTE_HOST_DOMAIN__SUFFIX": "root-domain",
}, },
@ -106,6 +107,20 @@ func setupCheCluster(t *testing.T, ctx context.Context, cl client.Client, scheme
t.Fatal(err) t.Fatal(err)
} }
gitTlsCredentials := &corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: deploy.GitSelfSignedCertsConfigMapName,
Namespace: cheNamespaceName,
},
Data: map[string]string{
"githost": "the.host.of.git",
"ca.crt": "the public certificate of the.host.of.git",
},
}
if err := cl.Create(ctx, gitTlsCredentials); err != nil {
t.Fatal(err)
}
r := devworkspace.New(cl, scheme) r := devworkspace.New(cl, scheme)
// the reconciliation needs to run twice for it to be truly finished - we're setting up finalizers etc... // the reconciliation needs to run twice for it to be truly finished - we're setting up finalizers etc...
if _, err := r.Reconcile(context.TODO(), reconcile.Request{NamespacedName: types.NamespacedName{Name: cheName, Namespace: cheNamespaceName}}); err != nil { if _, err := r.Reconcile(context.TODO(), reconcile.Request{NamespacedName: types.NamespacedName{Name: cheName, Namespace: cheNamespaceName}}); err != nil {
@ -301,6 +316,13 @@ func TestCreatesDataInNamespace(t *testing.T) {
assert.Equal(t, 2, len(caCerts.Data), "Expecting exactly 2 data entries in the trusted cert config map") assert.Equal(t, 2, len(caCerts.Data), "Expecting exactly 2 data entries in the trusted cert config map")
assert.Equal(t, "trusted cert 1", string(caCerts.Data["trusted1"]), "Unexpected trusted cert 1 value") assert.Equal(t, "trusted cert 1", string(caCerts.Data["trusted1"]), "Unexpected trusted cert 1 value")
assert.Equal(t, "trusted cert 2", string(caCerts.Data["trusted2"]), "Unexpected trusted cert 2 value") assert.Equal(t, "trusted cert 2", string(caCerts.Data["trusted2"]), "Unexpected trusted cert 2 value")
gitTlsConfig := corev1.ConfigMap{}
assert.NoError(t, cl.Get(ctx, client.ObjectKey{Name: "che-eclipse-che-git-tls-creds", Namespace: namespace.GetName()}, &gitTlsConfig))
assert.Equal(t, "true", gitTlsConfig.Labels[constants.DevWorkspaceGitTLSLabel])
assert.Equal(t, "true", gitTlsConfig.Labels[constants.DevWorkspaceWatchConfigMapLabel])
assert.Equal(t, "the.host.of.git", gitTlsConfig.Data["host"])
assert.Equal(t, "the public certificate of the.host.of.git", gitTlsConfig.Data["certificate"])
} }
t.Run("k8s", func(t *testing.T) { t.Run("k8s", func(t *testing.T) {

6
go.mod
View File

@ -7,8 +7,8 @@ require (
github.com/bitly/go-simplejson v0.0.0-00010101000000-000000000000 // indirect github.com/bitly/go-simplejson v0.0.0-00010101000000-000000000000 // indirect
github.com/blang/semver/v4 v4.0.0 github.com/blang/semver/v4 v4.0.0
github.com/che-incubator/kubernetes-image-puller-operator v0.0.0-20210929175054-0128446f5af7 github.com/che-incubator/kubernetes-image-puller-operator v0.0.0-20210929175054-0128446f5af7
github.com/devfile/api/v2 v2.0.0-20210713124824-03e023e7078b github.com/devfile/api/v2 v2.0.0-20210917193329-089a48011460
github.com/devfile/devworkspace-operator v0.2.1-0.20211005102315-728dff7e987c github.com/devfile/devworkspace-operator v0.2.1-0.20211213140302-4226bdb05e56
github.com/go-logr/logr v0.4.0 github.com/go-logr/logr v0.4.0
github.com/golang/mock v1.5.0 github.com/golang/mock v1.5.0
github.com/google/go-cmp v0.5.6 github.com/google/go-cmp v0.5.6
@ -212,7 +212,7 @@ replace (
github.com/huandu/xstrings => github.com/huandu/xstrings v1.2.0 github.com/huandu/xstrings => github.com/huandu/xstrings v1.2.0
github.com/imdario/mergo => github.com/imdario/mergo v0.3.5 github.com/imdario/mergo => github.com/imdario/mergo v0.3.5
github.com/inconshreveable/mousetrap => github.com/inconshreveable/mousetrap v1.0.0 github.com/inconshreveable/mousetrap => github.com/inconshreveable/mousetrap v1.0.0
github.com/irifrance/gini => github.com/irifrance/gini v1.0.1 github.com/irifrance/gini => github.com/go-air/gini v1.0.1
github.com/itchyny/astgen-go => github.com/itchyny/astgen-go v0.0.0-20200519013840-cf3ea398f645 github.com/itchyny/astgen-go => github.com/itchyny/astgen-go v0.0.0-20200519013840-cf3ea398f645
github.com/itchyny/go-flags => github.com/itchyny/go-flags v1.5.0 github.com/itchyny/go-flags => github.com/itchyny/go-flags v1.5.0
github.com/itchyny/gojq => github.com/itchyny/gojq v0.11.0 github.com/itchyny/gojq => github.com/itchyny/gojq v0.11.0

10
go.sum
View File

@ -115,10 +115,10 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
github.com/daviddengcn/go-colortext v0.0.0-20160507010035-511bcaf42ccd/go.mod h1:dv4zxwHi5C/8AeI+4gX4dCWOIvNi7I6JCSX0HvlKPgE= github.com/daviddengcn/go-colortext v0.0.0-20160507010035-511bcaf42ccd/go.mod h1:dv4zxwHi5C/8AeI+4gX4dCWOIvNi7I6JCSX0HvlKPgE=
github.com/deislabs/oras v0.8.1/go.mod h1:Mx0rMSbBNaNfY9hjpccEnxkOqJL6KGjtxNHPLC4G4As= github.com/deislabs/oras v0.8.1/go.mod h1:Mx0rMSbBNaNfY9hjpccEnxkOqJL6KGjtxNHPLC4G4As=
github.com/denisenkom/go-mssqldb v0.0.0-20190204142019-df6d76eb9289/go.mod h1:xN/JuLBIz4bjkxNmByTiV1IbhfnYb6oo99phBn4Eqhc= github.com/denisenkom/go-mssqldb v0.0.0-20190204142019-df6d76eb9289/go.mod h1:xN/JuLBIz4bjkxNmByTiV1IbhfnYb6oo99phBn4Eqhc=
github.com/devfile/api/v2 v2.0.0-20210713124824-03e023e7078b h1:N00ORHA5iamvPKpDFfSAkAczAaCBvK8l0EzAphsgFSI= github.com/devfile/api/v2 v2.0.0-20210917193329-089a48011460 h1:cmd+3poyUwevcWchYdvE02YT1nQU4SJpA5/wrdLrpWE=
github.com/devfile/api/v2 v2.0.0-20210713124824-03e023e7078b/go.mod h1:QNzaIVQnCsYfXed+QZOn1uvEQFzyhvpi/uc3g/b2ws0= github.com/devfile/api/v2 v2.0.0-20210917193329-089a48011460/go.mod h1:kLX/nW93gigOHXK3NLeJL2fSS/sgEe+OHu8bo3aoOi4=
github.com/devfile/devworkspace-operator v0.2.1-0.20211005102315-728dff7e987c h1:ua0f1tLDmxUhKPFHbSIxKE/oWB7GHZNhVRoh3V/NStU= github.com/devfile/devworkspace-operator v0.2.1-0.20211213140302-4226bdb05e56 h1:2BDnr7bPGoAs4vCiW5XIwp4M0L9Mecbvm5N48AuNgoc=
github.com/devfile/devworkspace-operator v0.2.1-0.20211005102315-728dff7e987c/go.mod h1:NJBkFTNuP1vusHXm8yky1hjA1JVtRdrWl49vaQs0eiw= github.com/devfile/devworkspace-operator v0.2.1-0.20211213140302-4226bdb05e56/go.mod h1:XP3lnHXRIItnXEa4YKyk2ZnEthVQQWjcA219FZs2ZpM=
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dhui/dktest v0.3.2/go.mod h1:l1/ib23a/CmxAe7yixtrYPc8Iy90Zy2udyaHINM5p58= github.com/dhui/dktest v0.3.2/go.mod h1:l1/ib23a/CmxAe7yixtrYPc8Iy90Zy2udyaHINM5p58=
@ -166,6 +166,7 @@ github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680 h1:ZktWZesgun21uEDrwW7
github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
github.com/globalsign/mgo v0.0.0-20160323214708-72aab81a5dec/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= github.com/globalsign/mgo v0.0.0-20160323214708-72aab81a5dec/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
github.com/go-air/gini v1.0.1/go.mod h1:swH5OTtiG/X/YrU06r288qZwq6I1agpbuXQOB55xqGU=
github.com/go-bindata/go-bindata/v3 v3.1.3/go.mod h1:1/zrpXsLD8YDIbhZRqXzm1Ghc7NhEvIN9+Z6R5/xH4I= github.com/go-bindata/go-bindata/v3 v3.1.3/go.mod h1:1/zrpXsLD8YDIbhZRqXzm1Ghc7NhEvIN9+Z6R5/xH4I=
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E= github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E=
@ -274,7 +275,6 @@ github.com/imdario/mergo v0.3.5 h1:JboBksRwiiAJWvIYJVo46AfV+IAIKZpfrSzVKj42R4Q=
github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/irifrance/gini v1.0.1/go.mod h1:swH5OTtiG/X/YrU06r288qZwq6I1agpbuXQOB55xqGU=
github.com/itchyny/astgen-go v0.0.0-20200519013840-cf3ea398f645/go.mod h1:296z3W7Xsrp2mlIY88ruDKscuvrkL6zXCNRtaYVshzw= github.com/itchyny/astgen-go v0.0.0-20200519013840-cf3ea398f645/go.mod h1:296z3W7Xsrp2mlIY88ruDKscuvrkL6zXCNRtaYVshzw=
github.com/itchyny/go-flags v1.5.0/go.mod h1:lenkYuCobuxLBAd/HGFE4LRoW8D3B6iXRQfWYJ+MNbA= github.com/itchyny/go-flags v1.5.0/go.mod h1:lenkYuCobuxLBAd/HGFE4LRoW8D3B6iXRQfWYJ+MNbA=
github.com/itchyny/gojq v0.11.0/go.mod h1:my6D2qN2Sm6qa+/5GsPDUZlCWGR+U8Qsa9he76sudv0= github.com/itchyny/gojq v0.11.0/go.mod h1:my6D2qN2Sm6qa+/5GsPDUZlCWGR+U8Qsa9he76sudv0=

View File

@ -18,14 +18,15 @@ const (
) )
// CommandGroupKind describes the kind of command group. // CommandGroupKind describes the kind of command group.
// +kubebuilder:validation:Enum=build;run;test;debug // +kubebuilder:validation:Enum=build;run;test;debug;deploy
type CommandGroupKind string type CommandGroupKind string
const ( const (
BuildCommandGroupKind CommandGroupKind = "build" BuildCommandGroupKind CommandGroupKind = "build"
RunCommandGroupKind CommandGroupKind = "run" RunCommandGroupKind CommandGroupKind = "run"
TestCommandGroupKind CommandGroupKind = "test" TestCommandGroupKind CommandGroupKind = "test"
DebugCommandGroupKind CommandGroupKind = "debug" DebugCommandGroupKind CommandGroupKind = "debug"
DeployCommandGroupKind CommandGroupKind = "deploy"
) )
type CommandGroup struct { type CommandGroup struct {
@ -34,7 +35,7 @@ type CommandGroup struct {
// +optional // +optional
// Identifies the default command for a given group kind // Identifies the default command for a given group kind
IsDefault bool `json:"isDefault,omitempty"` IsDefault *bool `json:"isDefault,omitempty"`
} }
type BaseCommand struct { type BaseCommand struct {
@ -144,7 +145,7 @@ type ExecCommand struct {
// If set to `true` the command won't be restarted and it is expected to handle file changes on its own. // If set to `true` the command won't be restarted and it is expected to handle file changes on its own.
// //
// Default value is `false` // Default value is `false`
HotReloadCapable bool `json:"hotReloadCapable,omitempty"` HotReloadCapable *bool `json:"hotReloadCapable,omitempty"`
} }
type ApplyCommand struct { type ApplyCommand struct {
@ -163,7 +164,7 @@ type CompositeCommand struct {
// Indicates if the sub-commands should be executed concurrently // Indicates if the sub-commands should be executed concurrently
// +optional // +optional
Parallel bool `json:"parallel,omitempty"` Parallel *bool `json:"parallel,omitempty"`
} }
type CustomCommand struct { type CustomCommand struct {

View File

@ -69,7 +69,7 @@ type Container struct {
// //
// Default value is `false` // Default value is `false`
// +optional // +optional
DedicatedPod bool `json:"dedicatedPod,omitempty"` DedicatedPod *bool `json:"dedicatedPod,omitempty"`
} }
type EnvVar struct { type EnvVar struct {

View File

@ -0,0 +1,38 @@
package v1alpha2
// ImageType describes the type of image.
// Only one of the following image type may be specified.
// +kubebuilder:validation:Enum=Dockerfile
type ImageType string
const (
DockerfileImageType ImageType = "Dockerfile"
)
type BaseImage struct {
}
// Component that allows the developer to build a runtime image for outerloop
type ImageComponent struct {
BaseComponent `json:",inline"`
Image `json:",inline"`
}
type Image struct {
// Name of the image for the resulting outerloop build
ImageName string `json:"imageName"`
ImageUnion `json:",inline"`
}
// +union
type ImageUnion struct {
// Type of image
//
// +unionDiscriminator
// +optional
ImageType ImageType `json:"imageType,omitempty"`
// Allows specifying dockerfile type build
// +optional
Dockerfile *DockerfileImage `json:"dockerfile,omitempty"`
}

View File

@ -0,0 +1,81 @@
package v1alpha2
// DockerfileSrcType describes the type of
// the src for the Dockerfile outerloop build.
// Only one of the following location type may be specified.
// +kubebuilder:validation:Enum=Uri;DevfileRegistry;Git
type DockerfileSrcType string
const (
UriLikeDockerfileSrcType DockerfileSrcType = "Uri"
DevfileRegistryLikeDockerfileSrcType DockerfileSrcType = "DevfileRegistry"
GitLikeDockerfileSrcType DockerfileSrcType = "Git"
)
// Dockerfile Image type to specify the outerloop build using a Dockerfile
type DockerfileImage struct {
BaseImage `json:",inline"`
DockerfileSrc `json:",inline"`
Dockerfile `json:",inline"`
}
// +union
type DockerfileSrc struct {
// Type of Dockerfile src
// +
// +unionDiscriminator
// +optional
SrcType DockerfileSrcType `json:"srcType,omitempty"`
// URI Reference of a Dockerfile.
// It can be a full URL or a relative URI from the current devfile as the base URI.
// +optional
Uri string `json:"uri,omitempty"`
// Dockerfile's Devfile Registry source
// +optional
DevfileRegistry *DockerfileDevfileRegistrySource `json:"devfileRegistry,omitempty"`
// Dockerfile's Git source
// +optional
Git *DockerfileGitProjectSource `json:"git,omitempty"`
}
type Dockerfile struct {
// Path of source directory to establish build context. Defaults to ${PROJECT_ROOT} in the container
// +optional
BuildContext string `json:"buildContext,omitempty"`
// The arguments to supply to the dockerfile build.
// +optional
Args []string `json:"args,omitempty" patchStrategy:"replace"`
// Specify if a privileged builder pod is required.
//
// Default value is `false`
// +optional
RootRequired *bool `json:"rootRequired,omitempty"`
}
type DockerfileDevfileRegistrySource struct {
// Id in a devfile registry that contains a Dockerfile. The src in the OCI registry
// required for the Dockerfile build will be downloaded for building the image.
Id string `json:"id"`
// Devfile Registry URL to pull the Dockerfile from when using the Devfile Registry as Dockerfile src.
// To ensure the Dockerfile gets resolved consistently in different environments,
// it is recommended to always specify the `devfileRegistryUrl` when `Id` is used.
// +optional
RegistryUrl string `json:"registryUrl,omitempty"`
}
type DockerfileGitProjectSource struct {
// Git src for the Dockerfile build. The src required for the Dockerfile build will need to be
// cloned for building the image.
GitProjectSource `json:",inline"`
// Location of the Dockerfile in the Git repository when using git as Dockerfile src.
// Defaults to Dockerfile.
// +optional
FileLocation string `json:"fileLocation,omitempty"`
}

View File

@ -15,5 +15,5 @@ type Volume struct {
// +optional // +optional
// Ephemeral volumes are not stored persistently across restarts. Defaults // Ephemeral volumes are not stored persistently across restarts. Defaults
// to false // to false
Ephemeral bool `json:"ephemeral,omitempty"` Ephemeral *bool `json:"ephemeral,omitempty"`
} }

View File

@ -7,7 +7,7 @@ import (
// ComponentType describes the type of component. // ComponentType describes the type of component.
// Only one of the following component type may be specified. // Only one of the following component type may be specified.
// +kubebuilder:validation:Enum=Container;Kubernetes;Openshift;Volume;Plugin;Custom // +kubebuilder:validation:Enum=Container;Kubernetes;Openshift;Volume;Image;Plugin;Custom
type ComponentType string type ComponentType string
const ( const (
@ -16,6 +16,7 @@ const (
OpenshiftComponentType ComponentType = "Openshift" OpenshiftComponentType ComponentType = "Openshift"
PluginComponentType ComponentType = "Plugin" PluginComponentType ComponentType = "Plugin"
VolumeComponentType ComponentType = "Volume" VolumeComponentType ComponentType = "Volume"
ImageComponentType ComponentType = "Image"
CustomComponentType ComponentType = "Custom" CustomComponentType ComponentType = "Custom"
) )
@ -72,6 +73,10 @@ type ComponentUnion struct {
// +optional // +optional
Volume *VolumeComponent `json:"volume,omitempty"` Volume *VolumeComponent `json:"volume,omitempty"`
// Allows specifying the definition of an image for outer loop builds
// +optional
Image *ImageComponent `json:"image,omitempty"`
// Allows importing a plugin. // Allows importing a plugin.
// //
// Plugins are mainly imported devfiles that contribute components, commands // Plugins are mainly imported devfiles that contribute components, commands

View File

@ -14,7 +14,7 @@ type DevWorkspaceTemplateSpec struct {
// +devfile:overrides:generate // +devfile:overrides:generate
type DevWorkspaceTemplateSpecContent struct { type DevWorkspaceTemplateSpecContent struct {
// Map of key-value variables used for string replacement in the devfile. Values can can be referenced via {{variable-key}} // Map of key-value variables used for string replacement in the devfile. Values can be referenced via {{variable-key}}
// to replace the corresponding value in string fields in the devfile. Replacement cannot be used for // to replace the corresponding value in string fields in the devfile. Replacement cannot be used for
// //
// - schemaVersion, metadata, parent source // - schemaVersion, metadata, parent source

View File

@ -94,7 +94,7 @@ type Endpoint struct {
// Describes whether the endpoint should be secured and protected by some // Describes whether the endpoint should be secured and protected by some
// authentication process. This requires a protocol of `https` or `wss`. // authentication process. This requires a protocol of `https` or `wss`.
// +optional // +optional
Secure bool `json:"secure,omitempty"` Secure *bool `json:"secure,omitempty"`
// Path of the endpoint URL // Path of the endpoint URL
// +optional // +optional

View File

@ -47,7 +47,7 @@ type ImportReference struct {
// Registry URL to pull the parent devfile from when using id in the parent reference. // Registry URL to pull the parent devfile from when using id in the parent reference.
// To ensure the parent devfile gets resolved consistently in different environments, // To ensure the parent devfile gets resolved consistently in different environments,
// it is recommended to always specify the `regsitryURL` when `Id` is used. // it is recommended to always specify the `registryUrl` when `id` is used.
// +optional // +optional
RegistryUrl string `json:"registryUrl,omitempty"` RegistryUrl string `json:"registryUrl,omitempty"`
} }

View File

@ -109,7 +109,8 @@ type GitLikeProjectSource struct {
// +optional // +optional
CheckoutFrom *CheckoutFrom `json:"checkoutFrom,omitempty"` CheckoutFrom *CheckoutFrom `json:"checkoutFrom,omitempty"`
// The remotes map which should be initialized in the git project. Must have at least one remote configured // The remotes map which should be initialized in the git project.
// Projects must have at least one remote configured while StarterProjects & Image Component's Git source can only have at most one remote configured.
Remotes map[string]string `json:"remotes"` Remotes map[string]string `json:"remotes"`
} }

File diff suppressed because it is too large Load Diff

View File

@ -142,7 +142,7 @@ type CommandParentOverride struct {
// +union // +union
type ComponentUnionParentOverride struct { type ComponentUnionParentOverride struct {
// +kubebuilder:validation:Enum=Container;Kubernetes;Openshift;Volume;Plugin // +kubebuilder:validation:Enum=Container;Kubernetes;Openshift;Volume;Image;Plugin
// Type of component // Type of component
// //
// +unionDiscriminator // +unionDiscriminator
@ -172,6 +172,10 @@ type ComponentUnionParentOverride struct {
// +optional // +optional
Volume *VolumeComponentParentOverride `json:"volume,omitempty"` Volume *VolumeComponentParentOverride `json:"volume,omitempty"`
// Allows specifying the definition of an image for outer loop builds
// +optional
Image *ImageComponentParentOverride `json:"image,omitempty"`
// Allows importing a plugin. // Allows importing a plugin.
// //
// Plugins are mainly imported devfiles that contribute components, commands // Plugins are mainly imported devfiles that contribute components, commands
@ -262,6 +266,12 @@ type VolumeComponentParentOverride struct {
VolumeParentOverride `json:",inline"` VolumeParentOverride `json:",inline"`
} }
// Component that allows the developer to build a runtime image for outerloop
type ImageComponentParentOverride struct {
BaseComponentParentOverride `json:",inline"`
ImageParentOverride `json:",inline"`
}
type PluginComponentParentOverride struct { type PluginComponentParentOverride struct {
BaseComponentParentOverride `json:",inline"` BaseComponentParentOverride `json:",inline"`
ImportReferenceParentOverride `json:",inline"` ImportReferenceParentOverride `json:",inline"`
@ -330,7 +340,7 @@ type ExecCommandParentOverride struct {
// If set to `true` the command won't be restarted and it is expected to handle file changes on its own. // If set to `true` the command won't be restarted and it is expected to handle file changes on its own.
// //
// Default value is `false` // Default value is `false`
HotReloadCapable bool `json:"hotReloadCapable,omitempty"` HotReloadCapable *bool `json:"hotReloadCapable,omitempty"`
} }
type ApplyCommandParentOverride struct { type ApplyCommandParentOverride struct {
@ -350,7 +360,7 @@ type CompositeCommandParentOverride struct {
// Indicates if the sub-commands should be executed concurrently // Indicates if the sub-commands should be executed concurrently
// +optional // +optional
Parallel bool `json:"parallel,omitempty"` Parallel *bool `json:"parallel,omitempty"`
} }
// DevWorkspace component: Anything that will bring additional features / tooling / behaviour / context // DevWorkspace component: Anything that will bring additional features / tooling / behaviour / context
@ -420,7 +430,7 @@ type ContainerParentOverride struct {
// //
// Default value is `false` // Default value is `false`
// +optional // +optional
DedicatedPod bool `json:"dedicatedPod,omitempty"` DedicatedPod *bool `json:"dedicatedPod,omitempty"`
} }
type EndpointParentOverride struct { type EndpointParentOverride struct {
@ -471,7 +481,7 @@ type EndpointParentOverride struct {
// Describes whether the endpoint should be secured and protected by some // Describes whether the endpoint should be secured and protected by some
// authentication process. This requires a protocol of `https` or `wss`. // authentication process. This requires a protocol of `https` or `wss`.
// +optional // +optional
Secure bool `json:"secure,omitempty"` Secure *bool `json:"secure,omitempty"`
// Path of the endpoint URL // Path of the endpoint URL
// +optional // +optional
@ -507,7 +517,15 @@ type VolumeParentOverride struct {
// +optional // +optional
// Ephemeral volumes are not stored persistently across restarts. Defaults // Ephemeral volumes are not stored persistently across restarts. Defaults
// to false // to false
Ephemeral bool `json:"ephemeral,omitempty"` Ephemeral *bool `json:"ephemeral,omitempty"`
}
type ImageParentOverride struct {
// +optional
// Name of the image for the resulting outerloop build
ImageName string `json:"imageName,omitempty"`
ImageUnionParentOverride `json:",inline"`
} }
type ImportReferenceParentOverride struct { type ImportReferenceParentOverride struct {
@ -515,7 +533,7 @@ type ImportReferenceParentOverride struct {
// Registry URL to pull the parent devfile from when using id in the parent reference. // Registry URL to pull the parent devfile from when using id in the parent reference.
// To ensure the parent devfile gets resolved consistently in different environments, // To ensure the parent devfile gets resolved consistently in different environments,
// it is recommended to always specify the `regsitryURL` when `Id` is used. // it is recommended to always specify the `registryUrl` when `id` is used.
// +optional // +optional
RegistryUrl string `json:"registryUrl,omitempty"` RegistryUrl string `json:"registryUrl,omitempty"`
} }
@ -548,7 +566,8 @@ type GitLikeProjectSourceParentOverride struct {
CheckoutFrom *CheckoutFromParentOverride `json:"checkoutFrom,omitempty"` CheckoutFrom *CheckoutFromParentOverride `json:"checkoutFrom,omitempty"`
// +optional // +optional
// The remotes map which should be initialized in the git project. Must have at least one remote configured // The remotes map which should be initialized in the git project.
// Projects must have at least one remote configured while StarterProjects & Image Component's Git source can only have at most one remote configured.
Remotes map[string]string `json:"remotes,omitempty"` Remotes map[string]string `json:"remotes,omitempty"`
} }
@ -615,6 +634,21 @@ type K8sLikeComponentLocationParentOverride struct {
Inlined string `json:"inlined,omitempty"` Inlined string `json:"inlined,omitempty"`
} }
// +union
type ImageUnionParentOverride struct {
// +kubebuilder:validation:Enum=Dockerfile
// Type of image
//
// +unionDiscriminator
// +optional
ImageType ImageTypeParentOverride `json:"imageType,omitempty"`
// Allows specifying dockerfile type build
// +optional
Dockerfile *DockerfileImageParentOverride `json:"dockerfile,omitempty"`
}
// Location from where the an import reference is retrieved // Location from where the an import reference is retrieved
// +union // +union
type ImportReferenceUnionParentOverride struct { type ImportReferenceUnionParentOverride struct {
@ -705,6 +739,17 @@ type BaseCommandParentOverride struct {
// Only one of the following component type may be specified. // Only one of the following component type may be specified.
type K8sLikeComponentLocationTypeParentOverride string type K8sLikeComponentLocationTypeParentOverride string
// ImageType describes the type of image.
// Only one of the following image type may be specified.
type ImageTypeParentOverride string
// Dockerfile Image type to specify the outerloop build using a Dockerfile
type DockerfileImageParentOverride struct {
BaseImageParentOverride `json:",inline"`
DockerfileSrcParentOverride `json:",inline"`
DockerfileParentOverride `json:",inline"`
}
// ImportReferenceType describes the type of location // ImportReferenceType describes the type of location
// from where the referenced template structure should be retrieved. // from where the referenced template structure should be retrieved.
// Only one of the following parent locations may be specified. // Only one of the following parent locations may be specified.
@ -721,7 +766,7 @@ type KubernetesCustomResourceImportReferenceParentOverride struct {
// +union // +union
type ComponentUnionPluginOverrideParentOverride struct { type ComponentUnionPluginOverrideParentOverride struct {
// +kubebuilder:validation:Enum=Container;Kubernetes;Openshift;Volume // +kubebuilder:validation:Enum=Container;Kubernetes;Openshift;Volume;Image
// Type of component // Type of component
// //
// +unionDiscriminator // +unionDiscriminator
@ -750,6 +795,10 @@ type ComponentUnionPluginOverrideParentOverride struct {
// shared by several other components // shared by several other components
// +optional // +optional
Volume *VolumeComponentPluginOverrideParentOverride `json:"volume,omitempty"` Volume *VolumeComponentPluginOverrideParentOverride `json:"volume,omitempty"`
// Allows specifying the definition of an image for outer loop builds
// +optional
Image *ImageComponentPluginOverrideParentOverride `json:"image,omitempty"`
} }
// +union // +union
@ -793,7 +842,51 @@ type CommandGroupParentOverride struct {
// +optional // +optional
// Identifies the default command for a given group kind // Identifies the default command for a given group kind
IsDefault bool `json:"isDefault,omitempty"` IsDefault *bool `json:"isDefault,omitempty"`
}
type BaseImageParentOverride struct {
}
// +union
type DockerfileSrcParentOverride struct {
// +kubebuilder:validation:Enum=Uri;DevfileRegistry;Git
// Type of Dockerfile src
// +
// +unionDiscriminator
// +optional
SrcType DockerfileSrcTypeParentOverride `json:"srcType,omitempty"`
// URI Reference of a Dockerfile.
// It can be a full URL or a relative URI from the current devfile as the base URI.
// +optional
Uri string `json:"uri,omitempty"`
// Dockerfile's Devfile Registry source
// +optional
DevfileRegistry *DockerfileDevfileRegistrySourceParentOverride `json:"devfileRegistry,omitempty"`
// Dockerfile's Git source
// +optional
Git *DockerfileGitProjectSourceParentOverride `json:"git,omitempty"`
}
type DockerfileParentOverride struct {
// Path of source directory to establish build context. Defaults to ${PROJECT_ROOT} in the container
// +optional
BuildContext string `json:"buildContext,omitempty"`
// The arguments to supply to the dockerfile build.
// +optional
Args []string `json:"args,omitempty" patchStrategy:"replace"`
// Specify if a privileged builder pod is required.
//
// Default value is `false`
// +optional
RootRequired *bool `json:"rootRequired,omitempty"`
} }
// ComponentType describes the type of component. // ComponentType describes the type of component.
@ -823,6 +916,12 @@ type VolumeComponentPluginOverrideParentOverride struct {
VolumePluginOverrideParentOverride `json:",inline"` VolumePluginOverrideParentOverride `json:",inline"`
} }
// Component that allows the developer to build a runtime image for outerloop
type ImageComponentPluginOverrideParentOverride struct {
BaseComponentPluginOverrideParentOverride `json:",inline"`
ImagePluginOverrideParentOverride `json:",inline"`
}
// CommandType describes the type of command. // CommandType describes the type of command.
// Only one of the following command type may be specified. // Only one of the following command type may be specified.
type CommandTypePluginOverrideParentOverride string type CommandTypePluginOverrideParentOverride string
@ -867,7 +966,7 @@ type ExecCommandPluginOverrideParentOverride struct {
// If set to `true` the command won't be restarted and it is expected to handle file changes on its own. // If set to `true` the command won't be restarted and it is expected to handle file changes on its own.
// //
// Default value is `false` // Default value is `false`
HotReloadCapable bool `json:"hotReloadCapable,omitempty"` HotReloadCapable *bool `json:"hotReloadCapable,omitempty"`
} }
type ApplyCommandPluginOverrideParentOverride struct { type ApplyCommandPluginOverrideParentOverride struct {
@ -887,13 +986,44 @@ type CompositeCommandPluginOverrideParentOverride struct {
// Indicates if the sub-commands should be executed concurrently // Indicates if the sub-commands should be executed concurrently
// +optional // +optional
Parallel bool `json:"parallel,omitempty"` Parallel *bool `json:"parallel,omitempty"`
} }
// CommandGroupKind describes the kind of command group. // CommandGroupKind describes the kind of command group.
// +kubebuilder:validation:Enum=build;run;test;debug // +kubebuilder:validation:Enum=build;run;test;debug;deploy
type CommandGroupKindParentOverride string type CommandGroupKindParentOverride string
// DockerfileSrcType describes the type of
// the src for the Dockerfile outerloop build.
// Only one of the following location type may be specified.
type DockerfileSrcTypeParentOverride string
type DockerfileDevfileRegistrySourceParentOverride struct {
// +optional
// Id in a devfile registry that contains a Dockerfile. The src in the OCI registry
// required for the Dockerfile build will be downloaded for building the image.
Id string `json:"id,omitempty"`
// Devfile Registry URL to pull the Dockerfile from when using the Devfile Registry as Dockerfile src.
// To ensure the Dockerfile gets resolved consistently in different environments,
// it is recommended to always specify the `devfileRegistryUrl` when `Id` is used.
// +optional
RegistryUrl string `json:"registryUrl,omitempty"`
}
type DockerfileGitProjectSourceParentOverride struct {
// Git src for the Dockerfile build. The src required for the Dockerfile build will need to be
// cloned for building the image.
GitProjectSourceParentOverride `json:",inline"`
// Location of the Dockerfile in the Git repository when using git as Dockerfile src.
// Defaults to Dockerfile.
// +optional
FileLocation string `json:"fileLocation,omitempty"`
}
// DevWorkspace component: Anything that will bring additional features / tooling / behaviour / context // DevWorkspace component: Anything that will bring additional features / tooling / behaviour / context
// to the devworkspace, in order to make working in it easier. // to the devworkspace, in order to make working in it easier.
type BaseComponentPluginOverrideParentOverride struct { type BaseComponentPluginOverrideParentOverride struct {
@ -962,7 +1092,7 @@ type ContainerPluginOverrideParentOverride struct {
// //
// Default value is `false` // Default value is `false`
// +optional // +optional
DedicatedPod bool `json:"dedicatedPod,omitempty"` DedicatedPod *bool `json:"dedicatedPod,omitempty"`
} }
type EndpointPluginOverrideParentOverride struct { type EndpointPluginOverrideParentOverride struct {
@ -1013,7 +1143,7 @@ type EndpointPluginOverrideParentOverride struct {
// Describes whether the endpoint should be secured and protected by some // Describes whether the endpoint should be secured and protected by some
// authentication process. This requires a protocol of `https` or `wss`. // authentication process. This requires a protocol of `https` or `wss`.
// +optional // +optional
Secure bool `json:"secure,omitempty"` Secure *bool `json:"secure,omitempty"`
// Path of the endpoint URL // Path of the endpoint URL
// +optional // +optional
@ -1049,7 +1179,15 @@ type VolumePluginOverrideParentOverride struct {
// +optional // +optional
// Ephemeral volumes are not stored persistently across restarts. Defaults // Ephemeral volumes are not stored persistently across restarts. Defaults
// to false // to false
Ephemeral bool `json:"ephemeral,omitempty"` Ephemeral *bool `json:"ephemeral,omitempty"`
}
type ImagePluginOverrideParentOverride struct {
// +optional
// Name of the image for the resulting outerloop build
ImageName string `json:"imageName,omitempty"`
ImageUnionPluginOverrideParentOverride `json:",inline"`
} }
type LabeledCommandPluginOverrideParentOverride struct { type LabeledCommandPluginOverrideParentOverride struct {
@ -1113,6 +1251,21 @@ type K8sLikeComponentLocationPluginOverrideParentOverride struct {
Inlined string `json:"inlined,omitempty"` Inlined string `json:"inlined,omitempty"`
} }
// +union
type ImageUnionPluginOverrideParentOverride struct {
// +kubebuilder:validation:Enum=Dockerfile
// Type of image
//
// +unionDiscriminator
// +optional
ImageType ImageTypePluginOverrideParentOverride `json:"imageType,omitempty"`
// Allows specifying dockerfile type build
// +optional
Dockerfile *DockerfileImagePluginOverrideParentOverride `json:"dockerfile,omitempty"`
}
type BaseCommandPluginOverrideParentOverride struct { type BaseCommandPluginOverrideParentOverride struct {
// +optional // +optional
@ -1125,6 +1278,17 @@ type BaseCommandPluginOverrideParentOverride struct {
// Only one of the following component type may be specified. // Only one of the following component type may be specified.
type K8sLikeComponentLocationTypePluginOverrideParentOverride string type K8sLikeComponentLocationTypePluginOverrideParentOverride string
// ImageType describes the type of image.
// Only one of the following image type may be specified.
type ImageTypePluginOverrideParentOverride string
// Dockerfile Image type to specify the outerloop build using a Dockerfile
type DockerfileImagePluginOverrideParentOverride struct {
BaseImagePluginOverrideParentOverride `json:",inline"`
DockerfileSrcPluginOverrideParentOverride `json:",inline"`
DockerfilePluginOverrideParentOverride `json:",inline"`
}
type CommandGroupPluginOverrideParentOverride struct { type CommandGroupPluginOverrideParentOverride struct {
// +optional // +optional
@ -1133,11 +1297,118 @@ type CommandGroupPluginOverrideParentOverride struct {
// +optional // +optional
// Identifies the default command for a given group kind // Identifies the default command for a given group kind
IsDefault bool `json:"isDefault,omitempty"` IsDefault *bool `json:"isDefault,omitempty"`
}
type BaseImagePluginOverrideParentOverride struct {
}
// +union
type DockerfileSrcPluginOverrideParentOverride struct {
// +kubebuilder:validation:Enum=Uri;DevfileRegistry;Git
// Type of Dockerfile src
// +
// +unionDiscriminator
// +optional
SrcType DockerfileSrcTypePluginOverrideParentOverride `json:"srcType,omitempty"`
// URI Reference of a Dockerfile.
// It can be a full URL or a relative URI from the current devfile as the base URI.
// +optional
Uri string `json:"uri,omitempty"`
// Dockerfile's Devfile Registry source
// +optional
DevfileRegistry *DockerfileDevfileRegistrySourcePluginOverrideParentOverride `json:"devfileRegistry,omitempty"`
// Dockerfile's Git source
// +optional
Git *DockerfileGitProjectSourcePluginOverrideParentOverride `json:"git,omitempty"`
}
type DockerfilePluginOverrideParentOverride struct {
// Path of source directory to establish build context. Defaults to ${PROJECT_ROOT} in the container
// +optional
BuildContext string `json:"buildContext,omitempty"`
// The arguments to supply to the dockerfile build.
// +optional
Args []string `json:"args,omitempty" patchStrategy:"replace"`
// Specify if a privileged builder pod is required.
//
// Default value is `false`
// +optional
RootRequired *bool `json:"rootRequired,omitempty"`
} }
// CommandGroupKind describes the kind of command group. // CommandGroupKind describes the kind of command group.
// +kubebuilder:validation:Enum=build;run;test;debug // +kubebuilder:validation:Enum=build;run;test;debug;deploy
type CommandGroupKindPluginOverrideParentOverride string type CommandGroupKindPluginOverrideParentOverride string
// DockerfileSrcType describes the type of
// the src for the Dockerfile outerloop build.
// Only one of the following location type may be specified.
type DockerfileSrcTypePluginOverrideParentOverride string
type DockerfileDevfileRegistrySourcePluginOverrideParentOverride struct {
// +optional
// Id in a devfile registry that contains a Dockerfile. The src in the OCI registry
// required for the Dockerfile build will be downloaded for building the image.
Id string `json:"id,omitempty"`
// Devfile Registry URL to pull the Dockerfile from when using the Devfile Registry as Dockerfile src.
// To ensure the Dockerfile gets resolved consistently in different environments,
// it is recommended to always specify the `devfileRegistryUrl` when `Id` is used.
// +optional
RegistryUrl string `json:"registryUrl,omitempty"`
}
type DockerfileGitProjectSourcePluginOverrideParentOverride struct {
// Git src for the Dockerfile build. The src required for the Dockerfile build will need to be
// cloned for building the image.
GitProjectSourcePluginOverrideParentOverride `json:",inline"`
// Location of the Dockerfile in the Git repository when using git as Dockerfile src.
// Defaults to Dockerfile.
// +optional
FileLocation string `json:"fileLocation,omitempty"`
}
type GitProjectSourcePluginOverrideParentOverride struct {
GitLikeProjectSourcePluginOverrideParentOverride `json:",inline"`
}
type GitLikeProjectSourcePluginOverrideParentOverride struct {
CommonProjectSourcePluginOverrideParentOverride `json:",inline"`
// Defines from what the project should be checked out. Required if there are more than one remote configured
// +optional
CheckoutFrom *CheckoutFromPluginOverrideParentOverride `json:"checkoutFrom,omitempty"`
// +optional
// The remotes map which should be initialized in the git project.
// Projects must have at least one remote configured while StarterProjects & Image Component's Git source can only have at most one remote configured.
Remotes map[string]string `json:"remotes,omitempty"`
}
type CommonProjectSourcePluginOverrideParentOverride struct {
}
type CheckoutFromPluginOverrideParentOverride struct {
// The revision to checkout from. Should be branch name, tag or commit id.
// Default branch is used if missing or specified revision is not found.
// +optional
Revision string `json:"revision,omitempty"`
// The remote name should be used as init. Required if there are more than one remote configured
// +optional
Remote string `json:"remote,omitempty"`
}
func (overrides ParentOverrides) isOverride() {} func (overrides ParentOverrides) isOverride() {}

View File

@ -65,7 +65,7 @@ type CommandPluginOverride struct {
// +union // +union
type ComponentUnionPluginOverride struct { type ComponentUnionPluginOverride struct {
// +kubebuilder:validation:Enum=Container;Kubernetes;Openshift;Volume // +kubebuilder:validation:Enum=Container;Kubernetes;Openshift;Volume;Image
// Type of component // Type of component
// //
// +unionDiscriminator // +unionDiscriminator
@ -94,6 +94,10 @@ type ComponentUnionPluginOverride struct {
// shared by several other components // shared by several other components
// +optional // +optional
Volume *VolumeComponentPluginOverride `json:"volume,omitempty"` Volume *VolumeComponentPluginOverride `json:"volume,omitempty"`
// Allows specifying the definition of an image for outer loop builds
// +optional
Image *ImageComponentPluginOverride `json:"image,omitempty"`
} }
// +union // +union
@ -156,6 +160,12 @@ type VolumeComponentPluginOverride struct {
VolumePluginOverride `json:",inline"` VolumePluginOverride `json:",inline"`
} }
// Component that allows the developer to build a runtime image for outerloop
type ImageComponentPluginOverride struct {
BaseComponentPluginOverride `json:",inline"`
ImagePluginOverride `json:",inline"`
}
// CommandType describes the type of command. // CommandType describes the type of command.
// Only one of the following command type may be specified. // Only one of the following command type may be specified.
type CommandTypePluginOverride string type CommandTypePluginOverride string
@ -200,7 +210,7 @@ type ExecCommandPluginOverride struct {
// If set to `true` the command won't be restarted and it is expected to handle file changes on its own. // If set to `true` the command won't be restarted and it is expected to handle file changes on its own.
// //
// Default value is `false` // Default value is `false`
HotReloadCapable bool `json:"hotReloadCapable,omitempty"` HotReloadCapable *bool `json:"hotReloadCapable,omitempty"`
} }
type ApplyCommandPluginOverride struct { type ApplyCommandPluginOverride struct {
@ -220,7 +230,7 @@ type CompositeCommandPluginOverride struct {
// Indicates if the sub-commands should be executed concurrently // Indicates if the sub-commands should be executed concurrently
// +optional // +optional
Parallel bool `json:"parallel,omitempty"` Parallel *bool `json:"parallel,omitempty"`
} }
// DevWorkspace component: Anything that will bring additional features / tooling / behaviour / context // DevWorkspace component: Anything that will bring additional features / tooling / behaviour / context
@ -290,7 +300,7 @@ type ContainerPluginOverride struct {
// //
// Default value is `false` // Default value is `false`
// +optional // +optional
DedicatedPod bool `json:"dedicatedPod,omitempty"` DedicatedPod *bool `json:"dedicatedPod,omitempty"`
} }
type EndpointPluginOverride struct { type EndpointPluginOverride struct {
@ -341,7 +351,7 @@ type EndpointPluginOverride struct {
// Describes whether the endpoint should be secured and protected by some // Describes whether the endpoint should be secured and protected by some
// authentication process. This requires a protocol of `https` or `wss`. // authentication process. This requires a protocol of `https` or `wss`.
// +optional // +optional
Secure bool `json:"secure,omitempty"` Secure *bool `json:"secure,omitempty"`
// Path of the endpoint URL // Path of the endpoint URL
// +optional // +optional
@ -377,7 +387,15 @@ type VolumePluginOverride struct {
// +optional // +optional
// Ephemeral volumes are not stored persistently across restarts. Defaults // Ephemeral volumes are not stored persistently across restarts. Defaults
// to false // to false
Ephemeral bool `json:"ephemeral,omitempty"` Ephemeral *bool `json:"ephemeral,omitempty"`
}
type ImagePluginOverride struct {
// +optional
// Name of the image for the resulting outerloop build
ImageName string `json:"imageName,omitempty"`
ImageUnionPluginOverride `json:",inline"`
} }
type LabeledCommandPluginOverride struct { type LabeledCommandPluginOverride struct {
@ -440,6 +458,21 @@ type K8sLikeComponentLocationPluginOverride struct {
Inlined string `json:"inlined,omitempty"` Inlined string `json:"inlined,omitempty"`
} }
// +union
type ImageUnionPluginOverride struct {
// +kubebuilder:validation:Enum=Dockerfile
// Type of image
//
// +unionDiscriminator
// +optional
ImageType ImageTypePluginOverride `json:"imageType,omitempty"`
// Allows specifying dockerfile type build
// +optional
Dockerfile *DockerfileImagePluginOverride `json:"dockerfile,omitempty"`
}
type BaseCommandPluginOverride struct { type BaseCommandPluginOverride struct {
// +optional // +optional
@ -452,6 +485,17 @@ type BaseCommandPluginOverride struct {
// Only one of the following component type may be specified. // Only one of the following component type may be specified.
type K8sLikeComponentLocationTypePluginOverride string type K8sLikeComponentLocationTypePluginOverride string
// ImageType describes the type of image.
// Only one of the following image type may be specified.
type ImageTypePluginOverride string
// Dockerfile Image type to specify the outerloop build using a Dockerfile
type DockerfileImagePluginOverride struct {
BaseImagePluginOverride `json:",inline"`
DockerfileSrcPluginOverride `json:",inline"`
DockerfilePluginOverride `json:",inline"`
}
type CommandGroupPluginOverride struct { type CommandGroupPluginOverride struct {
// +optional // +optional
@ -460,11 +504,118 @@ type CommandGroupPluginOverride struct {
// +optional // +optional
// Identifies the default command for a given group kind // Identifies the default command for a given group kind
IsDefault bool `json:"isDefault,omitempty"` IsDefault *bool `json:"isDefault,omitempty"`
}
type BaseImagePluginOverride struct {
}
// +union
type DockerfileSrcPluginOverride struct {
// +kubebuilder:validation:Enum=Uri;DevfileRegistry;Git
// Type of Dockerfile src
// +
// +unionDiscriminator
// +optional
SrcType DockerfileSrcTypePluginOverride `json:"srcType,omitempty"`
// URI Reference of a Dockerfile.
// It can be a full URL or a relative URI from the current devfile as the base URI.
// +optional
Uri string `json:"uri,omitempty"`
// Dockerfile's Devfile Registry source
// +optional
DevfileRegistry *DockerfileDevfileRegistrySourcePluginOverride `json:"devfileRegistry,omitempty"`
// Dockerfile's Git source
// +optional
Git *DockerfileGitProjectSourcePluginOverride `json:"git,omitempty"`
}
type DockerfilePluginOverride struct {
// Path of source directory to establish build context. Defaults to ${PROJECT_ROOT} in the container
// +optional
BuildContext string `json:"buildContext,omitempty"`
// The arguments to supply to the dockerfile build.
// +optional
Args []string `json:"args,omitempty" patchStrategy:"replace"`
// Specify if a privileged builder pod is required.
//
// Default value is `false`
// +optional
RootRequired *bool `json:"rootRequired,omitempty"`
} }
// CommandGroupKind describes the kind of command group. // CommandGroupKind describes the kind of command group.
// +kubebuilder:validation:Enum=build;run;test;debug // +kubebuilder:validation:Enum=build;run;test;debug;deploy
type CommandGroupKindPluginOverride string type CommandGroupKindPluginOverride string
// DockerfileSrcType describes the type of
// the src for the Dockerfile outerloop build.
// Only one of the following location type may be specified.
type DockerfileSrcTypePluginOverride string
type DockerfileDevfileRegistrySourcePluginOverride struct {
// +optional
// Id in a devfile registry that contains a Dockerfile. The src in the OCI registry
// required for the Dockerfile build will be downloaded for building the image.
Id string `json:"id,omitempty"`
// Devfile Registry URL to pull the Dockerfile from when using the Devfile Registry as Dockerfile src.
// To ensure the Dockerfile gets resolved consistently in different environments,
// it is recommended to always specify the `devfileRegistryUrl` when `Id` is used.
// +optional
RegistryUrl string `json:"registryUrl,omitempty"`
}
type DockerfileGitProjectSourcePluginOverride struct {
// Git src for the Dockerfile build. The src required for the Dockerfile build will need to be
// cloned for building the image.
GitProjectSourcePluginOverride `json:",inline"`
// Location of the Dockerfile in the Git repository when using git as Dockerfile src.
// Defaults to Dockerfile.
// +optional
FileLocation string `json:"fileLocation,omitempty"`
}
type GitProjectSourcePluginOverride struct {
GitLikeProjectSourcePluginOverride `json:",inline"`
}
type GitLikeProjectSourcePluginOverride struct {
CommonProjectSourcePluginOverride `json:",inline"`
// Defines from what the project should be checked out. Required if there are more than one remote configured
// +optional
CheckoutFrom *CheckoutFromPluginOverride `json:"checkoutFrom,omitempty"`
// +optional
// The remotes map which should be initialized in the git project.
// Projects must have at least one remote configured while StarterProjects & Image Component's Git source can only have at most one remote configured.
Remotes map[string]string `json:"remotes,omitempty"`
}
type CommonProjectSourcePluginOverride struct {
}
type CheckoutFromPluginOverride struct {
// The revision to checkout from. Should be branch name, tag or commit id.
// Default branch is used if missing or specified revision is not found.
// +optional
Revision string `json:"revision,omitempty"`
// The remote name should be used as init. Required if there are more than one remote configured
// +optional
Remote string `json:"remote,omitempty"`
}
func (overrides PluginOverrides) isOverride() {} func (overrides PluginOverrides) isOverride() {}

View File

@ -27,6 +27,48 @@ type CommandUnionVisitor struct {
Custom func(*CustomCommand) error Custom func(*CustomCommand) error
} }
var imageUnion reflect.Type = reflect.TypeOf(ImageUnionVisitor{})
func (union ImageUnion) Visit(visitor ImageUnionVisitor) error {
return visitUnion(union, visitor)
}
func (union *ImageUnion) discriminator() *string {
return (*string)(&union.ImageType)
}
func (union *ImageUnion) Normalize() error {
return normalizeUnion(union, imageUnion)
}
func (union *ImageUnion) Simplify() {
simplifyUnion(union, imageUnion)
}
// +k8s:deepcopy-gen=false
type ImageUnionVisitor struct {
Dockerfile func(*DockerfileImage) error
}
var dockerfileSrc reflect.Type = reflect.TypeOf(DockerfileSrcVisitor{})
func (union DockerfileSrc) Visit(visitor DockerfileSrcVisitor) error {
return visitUnion(union, visitor)
}
func (union *DockerfileSrc) discriminator() *string {
return (*string)(&union.SrcType)
}
func (union *DockerfileSrc) Normalize() error {
return normalizeUnion(union, dockerfileSrc)
}
func (union *DockerfileSrc) Simplify() {
simplifyUnion(union, dockerfileSrc)
}
// +k8s:deepcopy-gen=false
type DockerfileSrcVisitor struct {
Uri func(string) error
DevfileRegistry func(*DockerfileDevfileRegistrySource) error
Git func(*DockerfileGitProjectSource) error
}
var k8sLikeComponentLocation reflect.Type = reflect.TypeOf(K8sLikeComponentLocationVisitor{}) var k8sLikeComponentLocation reflect.Type = reflect.TypeOf(K8sLikeComponentLocationVisitor{})
func (union K8sLikeComponentLocation) Visit(visitor K8sLikeComponentLocationVisitor) error { func (union K8sLikeComponentLocation) Visit(visitor K8sLikeComponentLocationVisitor) error {
@ -69,6 +111,7 @@ type ComponentUnionVisitor struct {
Kubernetes func(*KubernetesComponent) error Kubernetes func(*KubernetesComponent) error
Openshift func(*OpenshiftComponent) error Openshift func(*OpenshiftComponent) error
Volume func(*VolumeComponent) error Volume func(*VolumeComponent) error
Image func(*ImageComponent) error
Plugin func(*PluginComponent) error Plugin func(*PluginComponent) error
Custom func(*CustomComponent) error Custom func(*CustomComponent) error
} }
@ -138,6 +181,7 @@ type ComponentUnionParentOverrideVisitor struct {
Kubernetes func(*KubernetesComponentParentOverride) error Kubernetes func(*KubernetesComponentParentOverride) error
Openshift func(*OpenshiftComponentParentOverride) error Openshift func(*OpenshiftComponentParentOverride) error
Volume func(*VolumeComponentParentOverride) error Volume func(*VolumeComponentParentOverride) error
Image func(*ImageComponentParentOverride) error
Plugin func(*PluginComponentParentOverride) error Plugin func(*PluginComponentParentOverride) error
} }
@ -205,6 +249,26 @@ type K8sLikeComponentLocationParentOverrideVisitor struct {
Inlined func(string) error Inlined func(string) error
} }
var imageUnionParentOverride reflect.Type = reflect.TypeOf(ImageUnionParentOverrideVisitor{})
func (union ImageUnionParentOverride) Visit(visitor ImageUnionParentOverrideVisitor) error {
return visitUnion(union, visitor)
}
func (union *ImageUnionParentOverride) discriminator() *string {
return (*string)(&union.ImageType)
}
func (union *ImageUnionParentOverride) Normalize() error {
return normalizeUnion(union, imageUnionParentOverride)
}
func (union *ImageUnionParentOverride) Simplify() {
simplifyUnion(union, imageUnionParentOverride)
}
// +k8s:deepcopy-gen=false
type ImageUnionParentOverrideVisitor struct {
Dockerfile func(*DockerfileImageParentOverride) error
}
var importReferenceUnionParentOverride reflect.Type = reflect.TypeOf(ImportReferenceUnionParentOverrideVisitor{}) var importReferenceUnionParentOverride reflect.Type = reflect.TypeOf(ImportReferenceUnionParentOverrideVisitor{})
func (union ImportReferenceUnionParentOverride) Visit(visitor ImportReferenceUnionParentOverrideVisitor) error { func (union ImportReferenceUnionParentOverride) Visit(visitor ImportReferenceUnionParentOverrideVisitor) error {
@ -248,6 +312,7 @@ type ComponentUnionPluginOverrideParentOverrideVisitor struct {
Kubernetes func(*KubernetesComponentPluginOverrideParentOverride) error Kubernetes func(*KubernetesComponentPluginOverrideParentOverride) error
Openshift func(*OpenshiftComponentPluginOverrideParentOverride) error Openshift func(*OpenshiftComponentPluginOverrideParentOverride) error
Volume func(*VolumeComponentPluginOverrideParentOverride) error Volume func(*VolumeComponentPluginOverrideParentOverride) error
Image func(*ImageComponentPluginOverrideParentOverride) error
} }
var commandUnionPluginOverrideParentOverride reflect.Type = reflect.TypeOf(CommandUnionPluginOverrideParentOverrideVisitor{}) var commandUnionPluginOverrideParentOverride reflect.Type = reflect.TypeOf(CommandUnionPluginOverrideParentOverrideVisitor{})
@ -272,6 +337,28 @@ type CommandUnionPluginOverrideParentOverrideVisitor struct {
Composite func(*CompositeCommandPluginOverrideParentOverride) error Composite func(*CompositeCommandPluginOverrideParentOverride) error
} }
var dockerfileSrcParentOverride reflect.Type = reflect.TypeOf(DockerfileSrcParentOverrideVisitor{})
func (union DockerfileSrcParentOverride) Visit(visitor DockerfileSrcParentOverrideVisitor) error {
return visitUnion(union, visitor)
}
func (union *DockerfileSrcParentOverride) discriminator() *string {
return (*string)(&union.SrcType)
}
func (union *DockerfileSrcParentOverride) Normalize() error {
return normalizeUnion(union, dockerfileSrcParentOverride)
}
func (union *DockerfileSrcParentOverride) Simplify() {
simplifyUnion(union, dockerfileSrcParentOverride)
}
// +k8s:deepcopy-gen=false
type DockerfileSrcParentOverrideVisitor struct {
Uri func(string) error
DevfileRegistry func(*DockerfileDevfileRegistrySourceParentOverride) error
Git func(*DockerfileGitProjectSourceParentOverride) error
}
var k8sLikeComponentLocationPluginOverrideParentOverride reflect.Type = reflect.TypeOf(K8sLikeComponentLocationPluginOverrideParentOverrideVisitor{}) var k8sLikeComponentLocationPluginOverrideParentOverride reflect.Type = reflect.TypeOf(K8sLikeComponentLocationPluginOverrideParentOverrideVisitor{})
func (union K8sLikeComponentLocationPluginOverrideParentOverride) Visit(visitor K8sLikeComponentLocationPluginOverrideParentOverrideVisitor) error { func (union K8sLikeComponentLocationPluginOverrideParentOverride) Visit(visitor K8sLikeComponentLocationPluginOverrideParentOverrideVisitor) error {
@ -293,6 +380,48 @@ type K8sLikeComponentLocationPluginOverrideParentOverrideVisitor struct {
Inlined func(string) error Inlined func(string) error
} }
var imageUnionPluginOverrideParentOverride reflect.Type = reflect.TypeOf(ImageUnionPluginOverrideParentOverrideVisitor{})
func (union ImageUnionPluginOverrideParentOverride) Visit(visitor ImageUnionPluginOverrideParentOverrideVisitor) error {
return visitUnion(union, visitor)
}
func (union *ImageUnionPluginOverrideParentOverride) discriminator() *string {
return (*string)(&union.ImageType)
}
func (union *ImageUnionPluginOverrideParentOverride) Normalize() error {
return normalizeUnion(union, imageUnionPluginOverrideParentOverride)
}
func (union *ImageUnionPluginOverrideParentOverride) Simplify() {
simplifyUnion(union, imageUnionPluginOverrideParentOverride)
}
// +k8s:deepcopy-gen=false
type ImageUnionPluginOverrideParentOverrideVisitor struct {
Dockerfile func(*DockerfileImagePluginOverrideParentOverride) error
}
var dockerfileSrcPluginOverrideParentOverride reflect.Type = reflect.TypeOf(DockerfileSrcPluginOverrideParentOverrideVisitor{})
func (union DockerfileSrcPluginOverrideParentOverride) Visit(visitor DockerfileSrcPluginOverrideParentOverrideVisitor) error {
return visitUnion(union, visitor)
}
func (union *DockerfileSrcPluginOverrideParentOverride) discriminator() *string {
return (*string)(&union.SrcType)
}
func (union *DockerfileSrcPluginOverrideParentOverride) Normalize() error {
return normalizeUnion(union, dockerfileSrcPluginOverrideParentOverride)
}
func (union *DockerfileSrcPluginOverrideParentOverride) Simplify() {
simplifyUnion(union, dockerfileSrcPluginOverrideParentOverride)
}
// +k8s:deepcopy-gen=false
type DockerfileSrcPluginOverrideParentOverrideVisitor struct {
Uri func(string) error
DevfileRegistry func(*DockerfileDevfileRegistrySourcePluginOverrideParentOverride) error
Git func(*DockerfileGitProjectSourcePluginOverrideParentOverride) error
}
var componentUnionPluginOverride reflect.Type = reflect.TypeOf(ComponentUnionPluginOverrideVisitor{}) var componentUnionPluginOverride reflect.Type = reflect.TypeOf(ComponentUnionPluginOverrideVisitor{})
func (union ComponentUnionPluginOverride) Visit(visitor ComponentUnionPluginOverrideVisitor) error { func (union ComponentUnionPluginOverride) Visit(visitor ComponentUnionPluginOverrideVisitor) error {
@ -314,6 +443,7 @@ type ComponentUnionPluginOverrideVisitor struct {
Kubernetes func(*KubernetesComponentPluginOverride) error Kubernetes func(*KubernetesComponentPluginOverride) error
Openshift func(*OpenshiftComponentPluginOverride) error Openshift func(*OpenshiftComponentPluginOverride) error
Volume func(*VolumeComponentPluginOverride) error Volume func(*VolumeComponentPluginOverride) error
Image func(*ImageComponentPluginOverride) error
} }
var commandUnionPluginOverride reflect.Type = reflect.TypeOf(CommandUnionPluginOverrideVisitor{}) var commandUnionPluginOverride reflect.Type = reflect.TypeOf(CommandUnionPluginOverrideVisitor{})
@ -358,3 +488,45 @@ type K8sLikeComponentLocationPluginOverrideVisitor struct {
Uri func(string) error Uri func(string) error
Inlined func(string) error Inlined func(string) error
} }
var imageUnionPluginOverride reflect.Type = reflect.TypeOf(ImageUnionPluginOverrideVisitor{})
func (union ImageUnionPluginOverride) Visit(visitor ImageUnionPluginOverrideVisitor) error {
return visitUnion(union, visitor)
}
func (union *ImageUnionPluginOverride) discriminator() *string {
return (*string)(&union.ImageType)
}
func (union *ImageUnionPluginOverride) Normalize() error {
return normalizeUnion(union, imageUnionPluginOverride)
}
func (union *ImageUnionPluginOverride) Simplify() {
simplifyUnion(union, imageUnionPluginOverride)
}
// +k8s:deepcopy-gen=false
type ImageUnionPluginOverrideVisitor struct {
Dockerfile func(*DockerfileImagePluginOverride) error
}
var dockerfileSrcPluginOverride reflect.Type = reflect.TypeOf(DockerfileSrcPluginOverrideVisitor{})
func (union DockerfileSrcPluginOverride) Visit(visitor DockerfileSrcPluginOverrideVisitor) error {
return visitUnion(union, visitor)
}
func (union *DockerfileSrcPluginOverride) discriminator() *string {
return (*string)(&union.SrcType)
}
func (union *DockerfileSrcPluginOverride) Normalize() error {
return normalizeUnion(union, dockerfileSrcPluginOverride)
}
func (union *DockerfileSrcPluginOverride) Simplify() {
simplifyUnion(union, dockerfileSrcPluginOverride)
}
// +k8s:deepcopy-gen=false
type DockerfileSrcPluginOverrideVisitor struct {
Uri func(string) error
DevfileRegistry func(*DockerfileDevfileRegistrySourcePluginOverride) error
Git func(*DockerfileGitProjectSourcePluginOverride) error
}

View File

@ -81,4 +81,12 @@ type DevfileMetadata struct {
// Optional devfile website // Optional devfile website
// +optional // +optional
Website string `json:"website,omitempty"` Website string `json:"website,omitempty"`
// Optional devfile provider information
// +optional
Provider string `json:"provider,omitempty"`
// Optional link to a page that provides support information
// +optional
SupportUrl string `json:"supportUrl,omitempty"`
} }

View File

@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
// //
package v1alpha1 package v1alpha1
import v1 "k8s.io/api/core/v1" import v1 "k8s.io/api/core/v1"

View File

@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
// //
package v1alpha1 package v1alpha1
type EndpointAttribute string type EndpointAttribute string

View File

@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
// //
package v1alpha1 package v1alpha1
import ( import (

View File

@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
// //
package v1alpha1 package v1alpha1
import ( import (

View File

@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
// //
package devworkspacerouting package devworkspacerouting
import ( import (

View File

@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
// //
package devworkspacerouting package devworkspacerouting
import ( import (

View File

@ -12,11 +12,10 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
// //
package solvers package solvers
import ( import (
"errors"
controllerv1alpha1 "github.com/devfile/devworkspace-operator/apis/controller/v1alpha1" controllerv1alpha1 "github.com/devfile/devworkspace-operator/apis/controller/v1alpha1"
"github.com/devfile/devworkspace-operator/pkg/config" "github.com/devfile/devworkspace-operator/pkg/config"
"github.com/devfile/devworkspace-operator/pkg/constants" "github.com/devfile/devworkspace-operator/pkg/constants"
@ -60,7 +59,7 @@ func (s *BasicSolver) GetSpecObjects(routing *controllerv1alpha1.DevWorkspaceRou
routingSuffix := config.Routing.ClusterHostSuffix routingSuffix := config.Routing.ClusterHostSuffix
if routingSuffix == "" { if routingSuffix == "" {
return routingObjects, errors.New("basic routing requires .config.routing.clusterHostSuffix to be set in operator config") return routingObjects, &RoutingInvalid{"basic routing requires .config.routing.clusterHostSuffix to be set in operator config"}
} }
spec := routing.Spec spec := routing.Spec

View File

@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
// //
package solvers package solvers
import ( import (

View File

@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
// //
package solvers package solvers
import ( import (

View File

@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
// //
package solvers package solvers
import ( import (

View File

@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
// //
package solvers package solvers
import ( import (
@ -76,7 +77,7 @@ func resolveURLForEndpoint(
func getURLForEndpoint(endpoint dw.Endpoint, host, basePath string, secure bool) string { func getURLForEndpoint(endpoint dw.Endpoint, host, basePath string, secure bool) string {
protocol := endpoint.Protocol protocol := endpoint.Protocol
if secure && endpoint.Secure { if secure && endpoint.Secure != nil && *endpoint.Secure {
protocol = dw.EndpointProtocol(getSecureProtocol(string(protocol))) protocol = dw.EndpointProtocol(getSecureProtocol(string(protocol)))
} }
var p string var p string

View File

@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
// //
package solvers package solvers
import ( import (

View File

@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
// //
package devworkspacerouting package devworkspacerouting
import ( import (
@ -19,22 +20,15 @@ import (
"fmt" "fmt"
"github.com/devfile/devworkspace-operator/pkg/constants" "github.com/devfile/devworkspace-operator/pkg/constants"
"github.com/devfile/devworkspace-operator/pkg/provision/sync"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
networkingv1 "k8s.io/api/networking/v1" networkingv1 "k8s.io/api/networking/v1"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/labels"
"sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client"
controllerv1alpha1 "github.com/devfile/devworkspace-operator/apis/controller/v1alpha1" controllerv1alpha1 "github.com/devfile/devworkspace-operator/apis/controller/v1alpha1"
) )
var ingressDiffOpts = cmp.Options{
cmpopts.IgnoreFields(networkingv1.Ingress{}, "TypeMeta", "ObjectMeta", "Status"),
cmpopts.IgnoreFields(networkingv1.HTTPIngressPath{}, "PathType"),
}
func (r *DevWorkspaceRoutingReconciler) syncIngresses(routing *controllerv1alpha1.DevWorkspaceRouting, specIngresses []networkingv1.Ingress) (ok bool, clusterIngresses []networkingv1.Ingress, err error) { func (r *DevWorkspaceRoutingReconciler) syncIngresses(routing *controllerv1alpha1.DevWorkspaceRouting, specIngresses []networkingv1.Ingress) (ok bool, clusterIngresses []networkingv1.Ingress, err error) {
ingressesInSync := true ingressesInSync := true
@ -52,32 +46,31 @@ func (r *DevWorkspaceRoutingReconciler) syncIngresses(routing *controllerv1alpha
ingressesInSync = false ingressesInSync = false
} }
for _, specIngress := range specIngresses { clusterAPI := sync.ClusterAPI{
if contains, idx := listContainsIngressByName(specIngress, clusterIngresses); contains { Client: r.Client,
clusterIngress := clusterIngresses[idx] Scheme: r.Scheme,
if !cmp.Equal(specIngress, clusterIngress, ingressDiffOpts) { Logger: r.Log.WithValues("Request.Namespace", routing.Namespace, "Request.Name", routing.Name),
r.Log.Info(fmt.Sprintf("Updating ingress: %s", clusterIngress.Name)) Ctx: context.TODO(),
if r.DebugLogging {
r.Log.Info(fmt.Sprintf("Diff: %s", cmp.Diff(specIngress, clusterIngress, ingressDiffOpts)))
}
// Update ingress's spec
clusterIngress.Spec = specIngress.Spec
err := r.Update(context.TODO(), &clusterIngress)
if err != nil && !errors.IsConflict(err) {
return false, nil, err
}
ingressesInSync = false
}
} else {
err := r.Create(context.TODO(), &specIngress)
if err != nil {
return false, nil, err
}
ingressesInSync = false
}
} }
return ingressesInSync, clusterIngresses, nil var updatedClusterIngresses []networkingv1.Ingress
for _, specIngress := range specIngresses {
clusterObj, err := sync.SyncObjectWithCluster(&specIngress, clusterAPI)
switch t := err.(type) {
case nil:
break
case *sync.NotInSyncError:
ingressesInSync = false
continue
case *sync.UnrecoverableSyncError:
return false, nil, t.Cause
default:
return false, nil, err
}
updatedClusterIngresses = append(updatedClusterIngresses, *clusterObj.(*networkingv1.Ingress))
}
return ingressesInSync, updatedClusterIngresses, nil
} }
func (r *DevWorkspaceRoutingReconciler) getClusterIngresses(routing *controllerv1alpha1.DevWorkspaceRouting) ([]networkingv1.Ingress, error) { func (r *DevWorkspaceRoutingReconciler) getClusterIngresses(routing *controllerv1alpha1.DevWorkspaceRouting) ([]networkingv1.Ingress, error) {

View File

@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
// //
package devworkspacerouting package devworkspacerouting
import ( import (
@ -19,23 +20,14 @@ import (
"fmt" "fmt"
"github.com/devfile/devworkspace-operator/pkg/constants" "github.com/devfile/devworkspace-operator/pkg/constants"
"github.com/devfile/devworkspace-operator/pkg/provision/sync"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
routeV1 "github.com/openshift/api/route/v1" routeV1 "github.com/openshift/api/route/v1"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/labels"
"sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client"
controllerv1alpha1 "github.com/devfile/devworkspace-operator/apis/controller/v1alpha1" controllerv1alpha1 "github.com/devfile/devworkspace-operator/apis/controller/v1alpha1"
) )
var routeDiffOpts = cmp.Options{
cmpopts.IgnoreFields(routeV1.Route{}, "TypeMeta", "ObjectMeta", "Status"),
cmpopts.IgnoreFields(routeV1.RouteSpec{}, "WildcardPolicy", "Host"),
cmpopts.IgnoreFields(routeV1.RouteTargetReference{}, "Weight"),
}
func (r *DevWorkspaceRoutingReconciler) syncRoutes(routing *controllerv1alpha1.DevWorkspaceRouting, specRoutes []routeV1.Route) (ok bool, clusterRoutes []routeV1.Route, err error) { func (r *DevWorkspaceRoutingReconciler) syncRoutes(routing *controllerv1alpha1.DevWorkspaceRouting, specRoutes []routeV1.Route) (ok bool, clusterRoutes []routeV1.Route, err error) {
routesInSync := true routesInSync := true
@ -53,33 +45,31 @@ func (r *DevWorkspaceRoutingReconciler) syncRoutes(routing *controllerv1alpha1.D
routesInSync = false routesInSync = false
} }
for _, specRoute := range specRoutes { clusterAPI := sync.ClusterAPI{
if contains, idx := listContainsRouteByName(specRoute, clusterRoutes); contains { Client: r.Client,
clusterRoute := clusterRoutes[idx] Scheme: r.Scheme,
if !cmp.Equal(specRoute, clusterRoute, routeDiffOpts) { Logger: r.Log.WithValues("Request.Namespace", routing.Namespace, "Request.Name", routing.Name),
r.Log.Info(fmt.Sprintf("Updating route: %s", clusterRoute.Name)) Ctx: context.TODO(),
if r.DebugLogging {
r.Log.Info(fmt.Sprintf("Diff: %s", cmp.Diff(specRoute, clusterRoute, routeDiffOpts)))
}
// Update route's spec
clusterRoute.Spec = specRoute.Spec
err := r.Update(context.TODO(), &clusterRoute)
if err != nil && !errors.IsConflict(err) {
return false, nil, err
}
routesInSync = false
}
} else {
err := r.Create(context.TODO(), &specRoute)
if err != nil {
return false, nil, err
}
routesInSync = false
}
} }
return routesInSync, clusterRoutes, nil var updatedClusterRoutes []routeV1.Route
for _, specIngress := range specRoutes {
clusterObj, err := sync.SyncObjectWithCluster(&specIngress, clusterAPI)
switch t := err.(type) {
case nil:
break
case *sync.NotInSyncError:
routesInSync = false
continue
case *sync.UnrecoverableSyncError:
return false, nil, t.Cause
default:
return false, nil, err
}
updatedClusterRoutes = append(updatedClusterRoutes, *clusterObj.(*routeV1.Route))
}
return routesInSync, updatedClusterRoutes, nil
} }
func (r *DevWorkspaceRoutingReconciler) getClusterRoutes(routing *controllerv1alpha1.DevWorkspaceRouting) ([]routeV1.Route, error) { func (r *DevWorkspaceRoutingReconciler) getClusterRoutes(routing *controllerv1alpha1.DevWorkspaceRouting) ([]routeV1.Route, error) {

View File

@ -12,50 +12,22 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
// //
package devworkspacerouting package devworkspacerouting
import ( import (
"context" "context"
"fmt" "fmt"
"sort"
"strings"
"github.com/devfile/devworkspace-operator/pkg/constants" "github.com/devfile/devworkspace-operator/pkg/constants"
"github.com/devfile/devworkspace-operator/pkg/provision/sync"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/labels"
"sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client"
controllerv1alpha1 "github.com/devfile/devworkspace-operator/apis/controller/v1alpha1" controllerv1alpha1 "github.com/devfile/devworkspace-operator/apis/controller/v1alpha1"
) )
var serviceDiffOpts = cmp.Options{
cmpopts.IgnoreFields(corev1.Service{}, "TypeMeta", "ObjectMeta", "Status"),
cmp.Comparer(func(x, y corev1.ServiceSpec) bool {
xCopy := x.DeepCopy()
yCopy := y.DeepCopy()
if !cmp.Equal(xCopy.Selector, yCopy.Selector) {
return false
}
// Function that takes a slice of servicePorts and returns the appropriate comparison
// function to pass to sort.Slice() for that slice of servicePorts.
servicePortSorter := func(servicePorts []corev1.ServicePort) func(i, j int) bool {
return func(i, j int) bool {
return strings.Compare(servicePorts[i].Name, servicePorts[j].Name) > 0
}
}
sort.Slice(xCopy.Ports, servicePortSorter(xCopy.Ports))
sort.Slice(yCopy.Ports, servicePortSorter(yCopy.Ports))
if !cmp.Equal(xCopy.Ports, yCopy.Ports) {
return false
}
return xCopy.Type == yCopy.Type
}),
}
func (r *DevWorkspaceRoutingReconciler) syncServices(routing *controllerv1alpha1.DevWorkspaceRouting, specServices []corev1.Service) (ok bool, clusterServices []corev1.Service, err error) { func (r *DevWorkspaceRoutingReconciler) syncServices(routing *controllerv1alpha1.DevWorkspaceRouting, specServices []corev1.Service) (ok bool, clusterServices []corev1.Service, err error) {
servicesInSync := true servicesInSync := true
@ -73,34 +45,31 @@ func (r *DevWorkspaceRoutingReconciler) syncServices(routing *controllerv1alpha1
servicesInSync = false servicesInSync = false
} }
for _, specService := range specServices { clusterAPI := sync.ClusterAPI{
if contains, idx := listContainsByName(specService, clusterServices); contains { Client: r.Client,
clusterService := clusterServices[idx] Scheme: r.Scheme,
if !cmp.Equal(specService, clusterService, serviceDiffOpts) { Logger: r.Log.WithValues("Request.Namespace", routing.Namespace, "Request.Name", routing.Name),
r.Log.Info(fmt.Sprintf("Updating service: %s", clusterService.Name)) Ctx: context.TODO(),
if r.DebugLogging {
r.Log.Info(fmt.Sprintf("Diff: %s", cmp.Diff(specService, clusterService, serviceDiffOpts)))
}
// Cannot naively copy spec, as clusterIP is unmodifiable
clusterIP := clusterService.Spec.ClusterIP
clusterService.Spec = specService.Spec
clusterService.Spec.ClusterIP = clusterIP
err := r.Update(context.TODO(), &clusterService)
if err != nil && !errors.IsConflict(err) {
return false, nil, err
}
servicesInSync = false
}
} else {
err := r.Create(context.TODO(), &specService)
if err != nil {
return false, nil, err
}
servicesInSync = false
}
} }
return servicesInSync, clusterServices, nil var updatedClusterServices []corev1.Service
for _, specIngress := range specServices {
clusterObj, err := sync.SyncObjectWithCluster(&specIngress, clusterAPI)
switch t := err.(type) {
case nil:
break
case *sync.NotInSyncError:
servicesInSync = false
continue
case *sync.UnrecoverableSyncError:
return false, nil, t.Cause
default:
return false, nil, err
}
updatedClusterServices = append(updatedClusterServices, *clusterObj.(*corev1.Service))
}
return servicesInSync, updatedClusterServices, nil
} }
func (r *DevWorkspaceRoutingReconciler) getClusterServices(routing *controllerv1alpha1.DevWorkspaceRouting) ([]corev1.Service, error) { func (r *DevWorkspaceRoutingReconciler) getClusterServices(routing *controllerv1alpha1.DevWorkspaceRouting) ([]corev1.Service, error) {

View File

@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
// //
package maputils package maputils
func Append(target map[string]string, key, value string) map[string]string { func Append(target map[string]string, key, value string) map[string]string {

View File

@ -12,12 +12,15 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
// //
package common package common
import ( import (
"fmt" "fmt"
"regexp" "regexp"
"strings" "strings"
"github.com/devfile/devworkspace-operator/pkg/constants"
) )
var NonAlphaNumRegexp = regexp.MustCompile(`[^a-z0-9]+`) var NonAlphaNumRegexp = regexp.MustCompile(`[^a-z0-9]+`)
@ -94,3 +97,11 @@ func AutoMountSecretVolumeName(volumeName string) string {
func AutoMountPVCVolumeName(pvcName string) string { func AutoMountPVCVolumeName(pvcName string) string {
return fmt.Sprintf("automount-pvc-%s", pvcName) return fmt.Sprintf("automount-pvc-%s", pvcName)
} }
func WorkspaceRoleName() string {
return "workspace"
}
func WorkspaceRolebindingName() string {
return constants.ServiceAccount + "dw"
}

View File

@ -0,0 +1,145 @@
//
// Copyright (c) 2019-2021 Red Hat, Inc.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
package configmap
import (
"context"
"fmt"
"os"
corev1 "k8s.io/api/core/v1"
k8sErrors "k8s.io/apimachinery/pkg/api/errors"
"sigs.k8s.io/controller-runtime/pkg/client"
logf "sigs.k8s.io/controller-runtime/pkg/log"
"github.com/devfile/devworkspace-operator/pkg/infrastructure"
)
var ControllerCfg ControllerConfig
var log = logf.Log.WithName("controller_devworkspace_config")
const (
ConfigMapNameEnvVar = "CONTROLLER_CONFIG_MAP_NAME"
ConfigMapNamespaceEnvVar = "CONTROLLER_CONFIG_MAP_NAMESPACE"
)
var ConfigMapReference = client.ObjectKey{
Namespace: "",
Name: "devworkspace-controller-configmap",
}
type ControllerConfig struct {
configMap *corev1.ConfigMap
}
func (wc *ControllerConfig) update(configMap *corev1.ConfigMap) {
log.Info("Updating the configuration from config map '%s' in namespace '%s'", configMap.Name, configMap.Namespace)
wc.configMap = configMap
}
func (wc *ControllerConfig) GetWorkspacePVCName() *string {
return wc.GetProperty(workspacePVCName)
}
func (wc *ControllerConfig) GetDefaultRoutingClass() *string {
return wc.GetProperty(routingClass)
}
func (wc *ControllerConfig) GetClusterRoutingSuffix() *string {
return wc.GetProperty(routingSuffix)
}
//GetExperimentalFeaturesEnabled returns true if experimental features should be enabled.
//DO NOT TURN ON IT IN THE PRODUCTION.
//Experimental features are not well tested and may be totally removed without announcement.
func (wc *ControllerConfig) GetExperimentalFeaturesEnabled() *string {
return wc.GetProperty(experimentalFeaturesEnabled)
}
func (wc *ControllerConfig) GetPVCStorageClassName() *string {
return wc.GetProperty(workspacePVCStorageClassName)
}
func (wc *ControllerConfig) GetSidecarPullPolicy() *string {
return wc.GetProperty(sidecarPullPolicy)
}
func (wc *ControllerConfig) GetProperty(name string) *string {
val, exists := wc.configMap.Data[name]
if exists {
return &val
}
return nil
}
func (wc *ControllerConfig) Validate() error {
return nil
}
func (wc *ControllerConfig) GetWorkspaceIdleTimeout() *string {
return wc.GetProperty(devworkspaceIdleTimeout)
}
func syncConfigmapFromCluster(client client.Client, obj client.Object) {
if obj.GetNamespace() != ConfigMapReference.Namespace ||
obj.GetName() != ConfigMapReference.Name {
return
}
if cm, isConfigMap := obj.(*corev1.ConfigMap); isConfigMap {
ControllerCfg.update(cm)
return
}
}
func LoadControllerConfig(nonCachedClient client.Client) (found bool, err error) {
configMapName, found := os.LookupEnv(ConfigMapNameEnvVar)
if found && len(configMapName) > 0 {
ConfigMapReference.Name = configMapName
}
configMapNamespace, found := os.LookupEnv(ConfigMapNamespaceEnvVar)
if found && len(configMapNamespace) > 0 {
ConfigMapReference.Namespace = configMapNamespace
} else {
namespace, err := infrastructure.GetNamespace()
if err != nil {
return false, err
}
ConfigMapReference.Namespace = namespace
}
if ConfigMapReference.Namespace == "" {
return false, fmt.Errorf("you should set the namespace of the controller config map through the '%s' environment variable", ConfigMapNamespaceEnvVar)
}
configMap := &corev1.ConfigMap{}
log.Info(fmt.Sprintf("Searching for config map '%s' in namespace '%s'", ConfigMapReference.Name, ConfigMapReference.Namespace))
err = nonCachedClient.Get(context.TODO(), ConfigMapReference, configMap)
if err != nil {
if !k8sErrors.IsNotFound(err) {
return false, err
}
return false, nil
} else {
log.Info(fmt.Sprintf(" => found config map '%s' in namespace '%s'", configMap.GetObjectMeta().GetName(), configMap.GetObjectMeta().GetNamespace()))
}
if configMap.Data == nil {
configMap.Data = map[string]string{}
}
syncConfigmapFromCluster(nonCachedClient, configMap)
return true, nil
}

View File

@ -0,0 +1,26 @@
//
// Copyright (c) 2019-2021 Red Hat, Inc.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Package config is used by components to get configuration.
//
// Typically each configuration property has the default value.
// Default value is supposed to be overridden via config map.
//
// There is the following configuration names convention:
// - words are lower-cased
// - . is used to separate subcomponents
// - _ is used to separate words in the component name
//
package configmap

View File

@ -0,0 +1,43 @@
//
// Copyright (c) 2019-2021 Red Hat, Inc.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
package configmap
const (
// image pull policy that is applied to every container within workspace
sidecarPullPolicy = "devworkspace.sidecar.image_pull_policy"
defaultSidecarPullPolicy = "Always"
// workspacePVCName config property handles the PVC name that should be created and used for all workspaces within one kubernetes namespace
workspacePVCName = "devworkspace.pvc.name"
defaultWorkspacePVCName = "claim-devworkspace"
workspacePVCStorageClassName = "devworkspace.pvc.storage_class.name"
// routingClass defines the default routing class that should be used if user does not specify it explicitly
routingClass = "devworkspace.default_routing_class"
defaultRoutingClass = "basic"
// routingSuffix is the base domain for routes/ingresses created on the cluster. All
// routes/ingresses will be created with URL http(s)://<unique-to-workspace-part>.<routingSuffix>
// is supposed to be used by embedded routing solvers only
routingSuffix = "devworkspace.routing.cluster_host_suffix"
experimentalFeaturesEnabled = "devworkspace.experimental_features_enabled"
defaultExperimentalFeaturesEnabled = "false"
devworkspaceIdleTimeout = "devworkspace.idle_timeout"
defaultDevWorkspaceIdleTimeout = "15m"
)

View File

@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
// //
package config package config
import "github.com/devfile/devworkspace-operator/apis/controller/v1alpha1" import "github.com/devfile/devworkspace-operator/apis/controller/v1alpha1"

View File

@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
// //
package config package config
import ( import (

View File

@ -0,0 +1,151 @@
//
// Copyright (c) 2019-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 config
import (
"context"
"fmt"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/equality"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
crclient "sigs.k8s.io/controller-runtime/pkg/client"
dw "github.com/devfile/devworkspace-operator/apis/controller/v1alpha1"
"github.com/devfile/devworkspace-operator/pkg/config/configmap"
"github.com/devfile/devworkspace-operator/pkg/infrastructure"
)
func MigrateConfigFromConfigMap(client crclient.Client) error {
migratedConfig, err := convertConfigMapToConfigCRD(client)
if err != nil {
return err
}
if migratedConfig == nil {
return nil
}
namespace, err := infrastructure.GetNamespace()
if err != nil {
return err
}
clusterConfig, err := getClusterConfig(namespace, client)
if err != nil {
return err
}
if clusterConfig != nil {
// Check using DeepDerivative in case cluster config contains default/additional values -- we only care
// that values in migratedConfig are propagated to the cluster DWOC.
if equality.Semantic.DeepDerivative(migratedConfig.Config, clusterConfig.Config) {
log.Info("Found deprecated operator configmap matching config custom resource. Deleting.")
// In case we migrated before but failed to delete
return deleteMigratedConfigmap(client)
}
return fmt.Errorf("found both DevWorkspaceOperatorConfig and configmap on cluster -- cannot migrate")
}
// Set namespace in case obsolete env vars were used to specify a custom namespace for the configmap
migratedConfig.Namespace = namespace
if err := client.Create(context.Background(), migratedConfig); err != nil {
return err
}
log.Info("Migrated operator configuration from configmap")
return deleteMigratedConfigmap(client)
}
func deleteMigratedConfigmap(client crclient.Client) error {
obsoleteConfigmap := &corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: configmap.ConfigMapReference.Name,
Namespace: configmap.ConfigMapReference.Namespace,
},
}
return client.Delete(context.Background(), obsoleteConfigmap)
}
// convertConfigMapToConfigCRD converts a earlier devworkspace configuration configmap (if present)
// into a DevWorkspaceOperatorConfig. Values matching the current default config settings are ignored.
// If the configmap is not present, or if the configmap is present but all values are default, returns
// nil. Returns an error if we fail to load the controller config from configmap.
func convertConfigMapToConfigCRD(client crclient.Client) (*dw.DevWorkspaceOperatorConfig, error) {
found, err := configmap.LoadControllerConfig(client)
if err != nil {
return nil, err
}
if !found {
return nil, nil
}
migratedRoutingConfig := &dw.RoutingConfig{}
setRoutingConfig := false
routingSuffix := configmap.ControllerCfg.GetClusterRoutingSuffix()
if routingSuffix != nil && *routingSuffix != DefaultConfig.Routing.ClusterHostSuffix {
migratedRoutingConfig.ClusterHostSuffix = *routingSuffix
setRoutingConfig = true
}
defaultRoutingClass := configmap.ControllerCfg.GetDefaultRoutingClass()
if defaultRoutingClass != nil && *defaultRoutingClass != DefaultConfig.Routing.DefaultRoutingClass {
migratedRoutingConfig.DefaultRoutingClass = *defaultRoutingClass
setRoutingConfig = true
}
migratedWorkspaceConfig := &dw.WorkspaceConfig{}
setWorkspaceConfig := false
storageClassName := configmap.ControllerCfg.GetPVCStorageClassName()
if storageClassName != DefaultConfig.Workspace.StorageClassName {
migratedWorkspaceConfig.StorageClassName = storageClassName
setWorkspaceConfig = true
}
sidecarPullPolicy := configmap.ControllerCfg.GetSidecarPullPolicy()
if sidecarPullPolicy != nil && *sidecarPullPolicy != DefaultConfig.Workspace.ImagePullPolicy {
migratedWorkspaceConfig.ImagePullPolicy = *sidecarPullPolicy
setWorkspaceConfig = true
}
idleTimeout := configmap.ControllerCfg.GetWorkspaceIdleTimeout()
if idleTimeout != nil && *idleTimeout != DefaultConfig.Workspace.IdleTimeout {
migratedWorkspaceConfig.IdleTimeout = *idleTimeout
setWorkspaceConfig = true
}
pvcName := configmap.ControllerCfg.GetWorkspacePVCName()
if pvcName != nil && *pvcName != DefaultConfig.Workspace.PVCName {
migratedWorkspaceConfig.PVCName = *pvcName
setWorkspaceConfig = true
}
var experimentalFeatures *bool
experimentalFeaturesStr := configmap.ControllerCfg.GetExperimentalFeaturesEnabled()
if experimentalFeaturesStr != nil && *experimentalFeaturesStr == "true" {
trueBool := true
experimentalFeatures = &trueBool
}
if !setRoutingConfig && !setWorkspaceConfig && experimentalFeatures == nil {
return nil, nil
}
migratedConfig := &dw.DevWorkspaceOperatorConfig{
ObjectMeta: metav1.ObjectMeta{
Name: OperatorConfigName,
Namespace: configmap.ConfigMapReference.Namespace,
},
Config: &dw.OperatorConfiguration{},
}
migratedConfig.Config.EnableExperimentalFeatures = experimentalFeatures
if setRoutingConfig {
migratedConfig.Config.Routing = migratedRoutingConfig
}
if setWorkspaceConfig {
migratedConfig.Config.Workspace = migratedWorkspaceConfig
}
return migratedConfig, nil
}

View File

@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
// //
package config package config
import ( import (

View File

@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
// //
package config package config
import ( import (
@ -73,14 +74,13 @@ func SetupControllerConfig(client crclient.Client) error {
} else { } else {
syncConfigFrom(config) syncConfigFrom(config)
} }
defaultRoutingSuffix, err := discoverRouteSuffix(client)
if err != nil {
return err
}
DefaultConfig.Routing.ClusterHostSuffix = defaultRoutingSuffix
if internalConfig.Routing.ClusterHostSuffix == "" { if internalConfig.Routing.ClusterHostSuffix == "" {
routeSuffix, err := discoverRouteSuffix(client) internalConfig.Routing.ClusterHostSuffix = defaultRoutingSuffix
if err != nil {
return err
}
internalConfig.Routing.ClusterHostSuffix = routeSuffix
// Set routing suffix in default config as well to ensure value is persisted across config changes
DefaultConfig.Routing.ClusterHostSuffix = routeSuffix
updatePublicConfig() updatePublicConfig()
} }
return nil return nil

View File

@ -12,14 +12,60 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
// //
package constants package constants
// Constants that are used in attributes on DevWorkspace elements (components, endpoints, etc.) // Constants that are used in attributes on DevWorkspace elements (components, endpoints, etc.)
const ( const (
// DevWorkspaceStorageTypeAttribute defines the strategy used for provisioning storage for the workspace.
// If empty, the common PVC strategy is used.
// Supported options:
// - "common": Create one PVC per namespace, and store data for all workspaces in that namespace in that PVC
// - "async" : Create one PVC per namespace, and create a remote server that syncs data from workspaces to the PVC.
// All volumeMounts used for devworkspaces are emptyDir
// - "ephemeral": Use emptyDir volumes for all volumes in the DevWorkspace. All data is lost when the workspace is
// stopped.
DevWorkspaceStorageTypeAttribute = "controller.devfile.io/storage-type"
// WorkspaceEnvAttribute is an attribute that specifies a set of environment variables provided by a component
// that should be added to all workspace containers. The structure of the attribute value should be a list of
// Devfile 2.0 EnvVar, e.g.
//
// attributes:
// workspaceEnv:
// - name: ENV_1
// value: VAL_1
// - name: ENV_2
// value: VAL_2
WorkspaceEnvAttribute = "workspaceEnv"
// WorkspaceSCCAttribute defines additional SCCs that should be added to the DevWorkspace. The user adding
// this attribute to a workspace must have the RBAC permissions to "use" the SCC with the given name. For example,
// to add the 'anyuid' SCC to the workspace Pod, the DevWorkspace should contain
//
// spec:
// template:
// attributes:
// controller.devfile.io/scc: "anyuid"
//
// Creating a workspace with this attribute, or updating an existing workspace to include this attribute will fail
// if the user making the request does not have the "use" permission for the "anyuid" SCC.
// Only supported on OpenShift.
WorkspaceSCCAttribute = "controller.devfile.io/scc"
// ProjectCloneAttribute configures how the DevWorkspace will treat project cloning. By default, an init container
// will be added to the workspace deployment to clone projects to the workspace before it starts. This attribute
// must be applied to top-level attributes field in the DevWorkspace.
// Supported options:
// - "disable" - Disable automatic project cloning. No init container will be added to the workspace and projects
// will not be cloned into the workspace on start.
ProjectCloneAttribute = "controller.devfile.io/project-clone"
// PluginSourceAttribute is an attribute added to components, commands, and projects in a flattened // PluginSourceAttribute is an attribute added to components, commands, and projects in a flattened
// DevWorkspace representation to signify where the respective component came from (i.e. which plugin // DevWorkspace representation to signify where the respective component came from (i.e. which plugin
// or parent imported it) // or parent imported it)
PluginSourceAttribute = "controller.devfile.io/imported-by" PluginSourceAttribute = "controller.devfile.io/imported-by"
// EndpointURLAttribute is an attribute added to endpoints to denote the endpoint on the cluster that // EndpointURLAttribute is an attribute added to endpoints to denote the endpoint on the cluster that
// was created to route to this endpoint // was created to route to this endpoint
EndpointURLAttribute = "controller.devfile.io/endpoint-url" EndpointURLAttribute = "controller.devfile.io/endpoint-url"

View File

@ -12,7 +12,8 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
// //
// package constants defines constant values used throughout the DevWorkspace Operator
// Package constants defines constant values used throughout the DevWorkspace Operator
package constants package constants
// Labels which should be used for controller related objects // Labels which should be used for controller related objects
@ -62,6 +63,7 @@ const (
ProjectCloneCPURequest = "100m" ProjectCloneCPURequest = "100m"
// Constants describing storage classes supported by the controller // Constants describing storage classes supported by the controller
// CommonStorageClassType defines the 'common' storage policy -- one PVC is provisioned per namespace and all devworkspace storage // CommonStorageClassType defines the 'common' storage policy -- one PVC is provisioned per namespace and all devworkspace storage
// is mounted in it on subpaths according to devworkspace ID. // is mounted in it on subpaths according to devworkspace ID.
CommonStorageClassType = "common" CommonStorageClassType = "common"
@ -71,4 +73,9 @@ const (
// EphemeralStorageClassType defines the 'ephemeral' storage policy: all volumes are allocated as emptyDir volumes and // EphemeralStorageClassType defines the 'ephemeral' storage policy: all volumes are allocated as emptyDir volumes and
// so do not require cleanup. When a DevWorkspace is stopped, all local changes are lost. // so do not require cleanup. When a DevWorkspace is stopped, all local changes are lost.
EphemeralStorageClassType = "ephemeral" EphemeralStorageClassType = "ephemeral"
// Constants describing configuration for automatic project cloning
// ProjectCloneDisable specifies that project cloning should be disabled.
ProjectCloneDisable = "disable"
) )

View File

@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
// //
package constants package constants
const ( const (

View File

@ -12,9 +12,10 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
// //
package constants package constants
// Constants that are used in labels and annotation on DevWorkspace-related resources. // Constants that are used in labels and annotations on DevWorkspace-related resources.
const ( const (
// DevWorkspaceIDLabel is the label key to store workspace identifier // DevWorkspaceIDLabel is the label key to store workspace identifier
DevWorkspaceIDLabel = "controller.devfile.io/devworkspace_id" DevWorkspaceIDLabel = "controller.devfile.io/devworkspace_id"
@ -25,6 +26,14 @@ const (
// DevWorkspaceNameLabel is the label key to store workspace name // DevWorkspaceNameLabel is the label key to store workspace name
DevWorkspaceNameLabel = "controller.devfile.io/devworkspace_name" DevWorkspaceNameLabel = "controller.devfile.io/devworkspace_name"
// DevWorkspaceWatchConfigMapLabel marks a configmap so that it is watched by the controller. This label is required on all
// configmaps that should be seen by the controller
DevWorkspaceWatchConfigMapLabel = "controller.devfile.io/watch-configmap"
// DevWorkspaceWatchSecretLabel marks a secret so that it is watched by the controller. This label is required on all
// secrets that should be seen by the controller
DevWorkspaceWatchSecretLabel = "controller.devfile.io/watch-secret"
// DevWorkspaceMountLabel is the label key to store if a configmap or secret should be mounted to the devworkspace // DevWorkspaceMountLabel is the label key to store if a configmap or secret should be mounted to the devworkspace
DevWorkspaceMountLabel = "controller.devfile.io/mount-to-devworkspace" DevWorkspaceMountLabel = "controller.devfile.io/mount-to-devworkspace"
@ -36,6 +45,14 @@ const (
// see https://git-scm.com/docs/git-credential-store#_storage_format for more details // see https://git-scm.com/docs/git-credential-store#_storage_format for more details
DevWorkspaceGitCredentialLabel = "controller.devfile.io/git-credential" DevWorkspaceGitCredentialLabel = "controller.devfile.io/git-credential"
// DevWorkspaceGitTLSLabel is the label key to specify if the configmap is credentials for accessing a git server.
// Configmap must contain the following data:
// certificate: the certificate used to access the git server in Base64 ASCII
// You can also optionally define the git host.
// host: the url of the git server
// If the git host is not defined then the certificate will be used for all http repositories.
DevWorkspaceGitTLSLabel = "controller.devfile.io/git-tls-credential"
// DevWorkspaceMountPathAnnotation is the annotation key to store the mount path for the secret or configmap. // DevWorkspaceMountPathAnnotation is the annotation key to store the mount path for the secret or configmap.
// If no mount path is provided, configmaps will be mounted at /etc/config/<configmap-name>, secrets will // If no mount path is provided, configmaps will be mounted at /etc/config/<configmap-name>, secrets will
// be mounted at /etc/secret/<secret-name>, and persistent volume claims will be mounted to /tmp/<claim-name> // be mounted at /etc/secret/<secret-name>, and persistent volume claims will be mounted to /tmp/<claim-name>
@ -74,26 +91,21 @@ const (
// WebhookRestartedAtAnnotation holds the the time (unixnano) of when the webhook server was forced to restart by controller // WebhookRestartedAtAnnotation holds the the time (unixnano) of when the webhook server was forced to restart by controller
WebhookRestartedAtAnnotation = "controller.devfile.io/restarted-at" WebhookRestartedAtAnnotation = "controller.devfile.io/restarted-at"
// DevWorkspaceStartedAtAnnotation holds the the time (unixnano) of when the devworkspace was started
DevWorkspaceStartedAtAnnotation = "controller.devfile.io/started-at"
// RoutingAnnotationInfix is the infix of the annotations of DevWorkspace that are passed down as annotation to the DevWorkspaceRouting objects. // RoutingAnnotationInfix is the infix of the annotations of DevWorkspace that are passed down as annotation to the DevWorkspaceRouting objects.
// The full annotation name is supposed to be "<routingClass>.routing.controller.devfile.io/<anything>" // The full annotation name is supposed to be "<routingClass>.routing.controller.devfile.io/<anything>"
RoutingAnnotationInfix = ".routing.controller.devfile.io/" RoutingAnnotationInfix = ".routing.controller.devfile.io/"
// DevWorkspaceStorageTypeAtrr defines the strategy used for provisioning storage for the workspace. // DevWorkspaceEndpointNameAnnotation is the annotation key for storing an endpoint's name from the devfile representation
// If empty, the common PVC strategy is used.
// Supported options:
// - "common": Create one PVC per namespace, and store data for all workspaces in that namespace in that PVC
// - "async" : Create one PVC per namespace, and create a remote server that syncs data from workspaces to the PVC.
// All volumeMounts used for devworkspaces are emptyDir
DevWorkspaceStorageTypeAtrr = "controller.devfile.io/storage-type"
// WorkspaceEndpointNameAnnotation is the annotation key for storing an endpoint's name from the devfile representation
DevWorkspaceEndpointNameAnnotation = "controller.devfile.io/endpoint_name" DevWorkspaceEndpointNameAnnotation = "controller.devfile.io/endpoint_name"
// DevWorkspaceDiscoverableServiceAnnotation marks a service in a devworkspace as created for a discoverable endpoint, // DevWorkspaceDiscoverableServiceAnnotation marks a service in a devworkspace as created for a discoverable endpoint,
// as opposed to a service created to support the devworkspace itself. // as opposed to a service created to support the devworkspace itself.
DevWorkspaceDiscoverableServiceAnnotation = "controller.devfile.io/discoverable-service" DevWorkspaceDiscoverableServiceAnnotation = "controller.devfile.io/discoverable-service"
// PullSecretLabel marks the intention that secret should be used as pull secret for devworkspaces withing namespace // DevWorkspacePullSecretLabel marks the intention that this secret should be used as a pull secret for devworkspaces within namespace
// Only secrets with 'true' value will be mount as pull secret // Only secrets with 'true' value will be mount as pull secret
// Should be assigned to secrets with type docker config types (kubernetes.io/dockercfg and kubernetes.io/dockerconfigjson) // Should be assigned to secrets with type docker config types (kubernetes.io/dockercfg and kubernetes.io/dockerconfigjson)
DevWorkspacePullSecretLabel = "controller.devfile.io/devworkspace_pullsecret" DevWorkspacePullSecretLabel = "controller.devfile.io/devworkspace_pullsecret"

View File

@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
// //
package infrastructure package infrastructure
import ( import (

View File

@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
// //
package infrastructure package infrastructure
import ( import (

View File

@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
// //
package infrastructure package infrastructure
import ( import (

View File

@ -0,0 +1,69 @@
// Copyright (c) 2019-2021 Red Hat, Inc.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package sync
import (
"context"
"fmt"
"reflect"
"github.com/go-logr/logr"
"k8s.io/apimachinery/pkg/runtime"
crclient "sigs.k8s.io/controller-runtime/pkg/client"
)
type ClusterAPI struct {
Client crclient.Client
NonCachingClient crclient.Client
Scheme *runtime.Scheme
Logger logr.Logger
Ctx context.Context
}
// NotInSyncError is returned when a spec object is out-of-sync with its cluster counterpart
type NotInSyncError struct {
Reason NotInSyncReason
Object crclient.Object
}
type NotInSyncReason string
const (
UpdatedObjectReason NotInSyncReason = "Updated object"
CreatedObjectReason NotInSyncReason = "Created object"
DeletedObjectReason NotInSyncReason = "Deleted object"
NeedRetryReason NotInSyncReason = "Need to retry"
)
func (e *NotInSyncError) Error() string {
return fmt.Sprintf("%s %s is not ready: %s", reflect.TypeOf(e.Object).Elem().String(), e.Object.GetName(), e.Reason)
}
// NewNotInSync wraps creation of NotInSyncErrors for simplicity
func NewNotInSync(obj crclient.Object, reason NotInSyncReason) *NotInSyncError {
return &NotInSyncError{
Reason: reason,
Object: obj,
}
}
// UnrecoverableSyncError is returned when provided objects cannot be synced with the cluster due to
// an unexpected error (e.g. they are invalid according to the object's spec).
type UnrecoverableSyncError struct {
Cause error
}
func (e *UnrecoverableSyncError) Error() string {
return e.Cause.Error()
}

View File

@ -0,0 +1,135 @@
// Copyright (c) 2019-2021 Red Hat, Inc.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package sync
import (
"reflect"
"sort"
"strings"
"github.com/devfile/devworkspace-operator/apis/controller/v1alpha1"
"github.com/google/go-cmp/cmp"
routev1 "github.com/openshift/api/route/v1"
appsv1 "k8s.io/api/apps/v1"
batchv1 "k8s.io/api/batch/v1"
corev1 "k8s.io/api/core/v1"
networkingv1 "k8s.io/api/networking/v1"
rbacv1 "k8s.io/api/rbac/v1"
"k8s.io/apimachinery/pkg/api/equality"
crclient "sigs.k8s.io/controller-runtime/pkg/client"
)
// diffFunc represents a function that compares a spec object against the corresponding cluster object and
// returns whether the object should be deleted or updated.
type diffFunc func(spec crclient.Object, cluster crclient.Object) (delete, update bool)
var diffFuncs = map[reflect.Type]diffFunc{
reflect.TypeOf(rbacv1.Role{}): basicDiffFunc(roleDiffOpts),
reflect.TypeOf(rbacv1.RoleBinding{}): basicDiffFunc(rolebindingDiffOpts),
reflect.TypeOf(corev1.ServiceAccount{}): labelsAndAnnotationsDiffFunc,
reflect.TypeOf(appsv1.Deployment{}): allDiffFuncs(deploymentDiffFunc, basicDiffFunc(deploymentDiffOpts)),
reflect.TypeOf(corev1.ConfigMap{}): basicDiffFunc(configmapDiffOpts),
reflect.TypeOf(v1alpha1.DevWorkspaceRouting{}): allDiffFuncs(routingDiffFunc, labelsAndAnnotationsDiffFunc, basicDiffFunc(routingDiffOpts)),
reflect.TypeOf(batchv1.Job{}): jobDiffFunc,
reflect.TypeOf(corev1.Service{}): serviceDiffFunc,
reflect.TypeOf(networkingv1.Ingress{}): basicDiffFunc(ingressDiffOpts),
reflect.TypeOf(routev1.Route{}): basicDiffFunc(routeDiffOpts),
}
// basicDiffFunc returns a diffFunc that specifies an object needs an update if cmp.Equal fails
func basicDiffFunc(diffOpt cmp.Options) diffFunc {
return func(spec, cluster crclient.Object) (delete, update bool) {
return false, !cmp.Equal(spec, cluster, diffOpt)
}
}
// labelsAndAnnotationsDiffFunc requires an object to be updated if any label or annotation present in the spec
// object is not present in the cluster object.
func labelsAndAnnotationsDiffFunc(spec, cluster crclient.Object) (delete, update bool) {
clusterAnnotations := cluster.GetAnnotations()
for k, v := range spec.GetAnnotations() {
if clusterAnnotations[k] != v {
return false, true
}
}
clusterLabels := cluster.GetLabels()
for k, v := range spec.GetLabels() {
if clusterLabels[k] != v {
return false, true
}
}
return false, false
}
// allDiffFuncs represents an 'and' condition across specified diffFuncs. Functions are checked in provided order,
// returning the result of the first function to require an update/deletion.
func allDiffFuncs(funcs ...diffFunc) diffFunc {
return func(spec, cluster crclient.Object) (delete, update bool) {
for _, df := range funcs {
shouldDelete, shouldUpdate := df(spec, cluster)
if shouldDelete || shouldUpdate {
return shouldDelete, shouldUpdate
}
}
return false, false
}
}
func deploymentDiffFunc(spec, cluster crclient.Object) (delete, update bool) {
specDeploy := spec.(*appsv1.Deployment)
clusterDeploy := cluster.(*appsv1.Deployment)
if !cmp.Equal(specDeploy.Spec.Selector, clusterDeploy.Spec.Selector) {
return true, false
}
return false, false
}
func routingDiffFunc(spec, cluster crclient.Object) (delete, update bool) {
specRouting := spec.(*v1alpha1.DevWorkspaceRouting)
clusterRouting := cluster.(*v1alpha1.DevWorkspaceRouting)
if specRouting.Spec.RoutingClass != clusterRouting.Spec.RoutingClass {
return true, false
}
return false, false
}
func jobDiffFunc(spec, cluster crclient.Object) (delete, update bool) {
specJob := spec.(*batchv1.Job)
clusterJob := cluster.(*batchv1.Job)
// TODO: previously, this delete was specified with a background deletion policy, which is currently unsupported.
return !equality.Semantic.DeepDerivative(specJob.Spec, clusterJob.Spec), false
}
func serviceDiffFunc(spec, cluster crclient.Object) (delete, update bool) {
specService := spec.(*corev1.Service)
clusterService := cluster.(*corev1.Service)
specCopy := specService.DeepCopy()
clusterCopy := clusterService.DeepCopy()
if !cmp.Equal(specCopy.Spec.Selector, clusterCopy.Spec.Selector) {
return false, true
}
// Function that takes a slice of servicePorts and returns the appropriate comparison
// function to pass to sort.Slice() for that slice of servicePorts.
servicePortSorter := func(servicePorts []corev1.ServicePort) func(i, j int) bool {
return func(i, j int) bool {
return strings.Compare(servicePorts[i].Name, servicePorts[j].Name) > 0
}
}
sort.Slice(specCopy.Spec.Ports, servicePortSorter(specCopy.Spec.Ports))
sort.Slice(clusterCopy.Spec.Ports, servicePortSorter(clusterCopy.Spec.Ports))
if !cmp.Equal(specCopy.Spec.Ports, clusterCopy.Spec.Ports) {
return false, true
}
return false, specCopy.Spec.Type != clusterCopy.Spec.Type
}

View File

@ -0,0 +1,72 @@
// Copyright (c) 2019-2021 Red Hat, Inc.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package sync
import (
"strings"
"github.com/devfile/devworkspace-operator/apis/controller/v1alpha1"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
routeV1 "github.com/openshift/api/route/v1"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
networkingv1 "k8s.io/api/networking/v1"
rbacv1 "k8s.io/api/rbac/v1"
)
var roleDiffOpts = cmp.Options{
cmpopts.IgnoreFields(rbacv1.Role{}, "TypeMeta", "ObjectMeta"),
}
var rolebindingDiffOpts = cmp.Options{
cmpopts.IgnoreFields(rbacv1.RoleBinding{}, "TypeMeta", "ObjectMeta"),
cmpopts.IgnoreFields(rbacv1.RoleRef{}, "APIGroup"),
cmpopts.IgnoreFields(rbacv1.Subject{}, "APIGroup"),
}
var deploymentDiffOpts = cmp.Options{
cmpopts.IgnoreFields(appsv1.Deployment{}, "TypeMeta", "ObjectMeta", "Status"),
cmpopts.IgnoreFields(appsv1.DeploymentSpec{}, "RevisionHistoryLimit", "ProgressDeadlineSeconds"),
cmpopts.IgnoreFields(corev1.PodSpec{}, "DNSPolicy", "SchedulerName", "DeprecatedServiceAccount"),
cmpopts.IgnoreFields(corev1.Container{}, "TerminationMessagePath", "TerminationMessagePolicy", "ImagePullPolicy"),
cmpopts.SortSlices(func(a, b corev1.Container) bool {
return strings.Compare(a.Name, b.Name) > 0
}),
cmpopts.SortSlices(func(a, b corev1.Volume) bool {
return strings.Compare(a.Name, b.Name) > 0
}),
cmpopts.SortSlices(func(a, b corev1.VolumeMount) bool {
return strings.Compare(a.Name, b.Name) > 0
}),
}
var configmapDiffOpts = cmp.Options{
cmpopts.IgnoreFields(corev1.ConfigMap{}, "TypeMeta", "ObjectMeta"),
}
var routingDiffOpts = cmp.Options{
cmpopts.IgnoreFields(v1alpha1.DevWorkspaceRouting{}, "ObjectMeta", "TypeMeta", "Status"),
}
var routeDiffOpts = cmp.Options{
cmpopts.IgnoreFields(routeV1.Route{}, "TypeMeta", "ObjectMeta", "Status"),
cmpopts.IgnoreFields(routeV1.RouteSpec{}, "WildcardPolicy", "Host"),
cmpopts.IgnoreFields(routeV1.RouteTargetReference{}, "Weight"),
}
var ingressDiffOpts = cmp.Options{
cmpopts.IgnoreFields(networkingv1.Ingress{}, "TypeMeta", "ObjectMeta", "Status"),
cmpopts.IgnoreFields(networkingv1.HTTPIngressPath{}, "PathType"),
}

View File

@ -0,0 +1,150 @@
// Copyright (c) 2019-2021 Red Hat, Inc.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package sync
import (
"fmt"
"reflect"
"github.com/devfile/devworkspace-operator/apis/controller/v1alpha1"
"github.com/devfile/devworkspace-operator/pkg/config"
"github.com/go-logr/logr"
"github.com/google/go-cmp/cmp"
routev1 "github.com/openshift/api/route/v1"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
networkingv1 "k8s.io/api/networking/v1"
rbacv1 "k8s.io/api/rbac/v1"
k8sErrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/types"
crclient "sigs.k8s.io/controller-runtime/pkg/client"
)
// SyncObjectWithCluster synchronises the state of specObj to the cluster, creating or updating the cluster object
// as required. If specObj is in sync with the cluster, returns the object as it exists on the cluster. Returns a
// NotInSyncError if an update is required, UnrecoverableSyncError if object provided is invalid, or generic error
// if an unexpected error is encountered
func SyncObjectWithCluster(specObj crclient.Object, api ClusterAPI) (crclient.Object, error) {
objType := reflect.TypeOf(specObj).Elem()
clusterObj := reflect.New(objType).Interface().(crclient.Object)
err := api.Client.Get(api.Ctx, types.NamespacedName{Name: specObj.GetName(), Namespace: specObj.GetNamespace()}, clusterObj)
if err != nil {
if k8sErrors.IsNotFound(err) {
return nil, createObjectGeneric(specObj, api)
}
return nil, err
}
if !isMutableObject(specObj) { // TODO: we could still update labels here, or treat a need to update as a fatal error
return clusterObj, nil
}
diffFunc := diffFuncs[objType]
if diffFunc == nil {
return nil, &UnrecoverableSyncError{fmt.Errorf("attempting to sync unrecognized object %s", objType)}
}
shouldDelete, shouldUpdate := diffFunc(specObj, clusterObj)
if shouldDelete {
printDiff(specObj, clusterObj, api.Logger)
err := api.Client.Delete(api.Ctx, specObj)
if err != nil {
return nil, err
}
api.Logger.Info("Deleted object", "kind", objType.String(), "name", specObj.GetName())
return nil, NewNotInSync(specObj, DeletedObjectReason)
}
if shouldUpdate {
printDiff(specObj, clusterObj, api.Logger)
return nil, updateObjectGeneric(specObj, clusterObj, api)
}
return clusterObj, nil
}
func createObjectGeneric(specObj crclient.Object, api ClusterAPI) error {
err := api.Client.Create(api.Ctx, specObj)
switch {
case err == nil:
api.Logger.Info("Created object", "kind", reflect.TypeOf(specObj).Elem().String(), "name", specObj.GetName())
return NewNotInSync(specObj, CreatedObjectReason)
case k8sErrors.IsAlreadyExists(err):
// Need to try to update the object to address an edge case where removing a labelselector
// results in the object not being tracked by the controller's cache.
return updateObjectGeneric(specObj, nil, api)
case k8sErrors.IsInvalid(err):
return &UnrecoverableSyncError{err}
default:
return err
}
}
func updateObjectGeneric(specObj, clusterObj crclient.Object, api ClusterAPI) error {
updateFunc := getUpdateFunc(specObj)
updatedObj, err := updateFunc(specObj, clusterObj)
if err != nil {
if err := api.Client.Delete(api.Ctx, specObj); err != nil {
return err
}
api.Logger.Info("Deleted object", "kind", reflect.TypeOf(specObj).Elem().String(), "name", specObj.GetName())
return NewNotInSync(specObj, DeletedObjectReason)
}
err = api.Client.Update(api.Ctx, updatedObj)
switch {
case err == nil:
api.Logger.Info("Updated object", "kind", reflect.TypeOf(specObj).Elem().String(), "name", specObj.GetName())
return NewNotInSync(specObj, UpdatedObjectReason)
case k8sErrors.IsConflict(err), k8sErrors.IsNotFound(err):
// Need to catch IsNotFound here because we attempt to update when creation fails with AlreadyExists
return NewNotInSync(specObj, NeedRetryReason)
case k8sErrors.IsInvalid(err):
return &UnrecoverableSyncError{err}
default:
return err
}
}
func isMutableObject(obj crclient.Object) bool {
switch obj.(type) {
case *corev1.PersistentVolumeClaim:
return false
default:
return true
}
}
func printDiff(specObj, clusterObj crclient.Object, log logr.Logger) {
if config.ExperimentalFeaturesEnabled() {
var diffOpts cmp.Options
switch specObj.(type) {
case *rbacv1.Role:
diffOpts = roleDiffOpts
case *rbacv1.RoleBinding:
diffOpts = rolebindingDiffOpts
case *appsv1.Deployment:
diffOpts = deploymentDiffOpts
case *corev1.ConfigMap:
diffOpts = configmapDiffOpts
case *v1alpha1.DevWorkspaceRouting:
diffOpts = routingDiffOpts
case *networkingv1.Ingress:
diffOpts = ingressDiffOpts
case *routev1.Route:
diffOpts = routeDiffOpts
default:
diffOpts = nil
}
log.Info(fmt.Sprintf("Diff: %s", cmp.Diff(specObj, clusterObj, diffOpts)))
}
}

View File

@ -0,0 +1,61 @@
// Copyright (c) 2019-2021 Red Hat, Inc.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package sync
import (
"errors"
"reflect"
corev1 "k8s.io/api/core/v1"
crclient "sigs.k8s.io/controller-runtime/pkg/client"
)
// updateFunc returns the object that should be applied to the client.Update function
// when updating an object on the cluster. Typically, this will just be defaultUpdateFunc,
// which returns the spec obejct unmodified. However, some objects, such as Services, require
// fields to be copied over from the cluster object, e.g. .spec.clusterIP. If an updated object
// cannot be resolved, an error should be returned to signal that the object in question should
// be deleted instead.
//
// The 'cluster' argument may be specified as nil in the case where a cluster version of the
// spec object is inaccessible (not cached) and has to be handled specifically.
type updateFunc func(spec, cluster crclient.Object) (crclient.Object, error)
func defaultUpdateFunc(spec, cluster crclient.Object) (crclient.Object, error) {
if cluster != nil {
spec.SetResourceVersion(cluster.GetResourceVersion())
}
return spec, nil
}
func serviceUpdateFunc(spec, cluster crclient.Object) (crclient.Object, error) {
if cluster == nil {
return nil, errors.New("updating a service requires the cluster instance")
}
specService := spec.DeepCopyObject().(*corev1.Service)
clusterService := cluster.(*corev1.Service)
specService.ResourceVersion = clusterService.ResourceVersion
specService.Spec.ClusterIP = clusterService.Spec.ClusterIP
return specService, nil
}
func getUpdateFunc(obj crclient.Object) updateFunc {
objType := reflect.TypeOf(obj).Elem()
switch objType {
case reflect.TypeOf(corev1.Service{}):
return serviceUpdateFunc
default:
return defaultUpdateFunc
}
}

0
vendor/github.com/json-iterator/go/build.sh generated vendored Executable file → Normal file
View File

0
vendor/github.com/json-iterator/go/test.sh generated vendored Executable file → Normal file
View File

0
vendor/github.com/modern-go/concurrent/test.sh generated vendored Executable file → Normal file
View File

0
vendor/github.com/modern-go/reflect2/test.sh generated vendored Executable file → Normal file
View File

0
vendor/go.uber.org/zap/checklicense.sh generated vendored Executable file → Normal file
View File

0
vendor/golang.org/x/sys/plan9/mkall.sh generated vendored Executable file → Normal file
View File

0
vendor/golang.org/x/sys/plan9/mkerrors.sh generated vendored Executable file → Normal file
View File

0
vendor/golang.org/x/sys/plan9/mksysnum_plan9.sh generated vendored Executable file → Normal file
View File

0
vendor/golang.org/x/sys/unix/mkall.sh generated vendored Executable file → Normal file
View File

0
vendor/golang.org/x/sys/unix/mkerrors.sh generated vendored Executable file → Normal file
View File

0
vendor/google.golang.org/appengine/internal/regen.sh generated vendored Executable file → Normal file
View File

0
vendor/google.golang.org/appengine/travis_install.sh generated vendored Executable file → Normal file
View File

0
vendor/google.golang.org/appengine/travis_test.sh generated vendored Executable file → Normal file
View File

0
vendor/google.golang.org/grpc/codegen.sh generated vendored Executable file → Normal file
View File

0
vendor/google.golang.org/grpc/install_gae.sh generated vendored Executable file → Normal file
View File

0
vendor/google.golang.org/grpc/internal/binarylog/regenerate.sh generated vendored Executable file → Normal file
View File

0
vendor/google.golang.org/grpc/vet.sh generated vendored Executable file → Normal file
View File

8
vendor/modules.txt vendored
View File

@ -33,12 +33,12 @@ github.com/cespare/xxhash/v2
github.com/che-incubator/kubernetes-image-puller-operator/api/v1alpha1 github.com/che-incubator/kubernetes-image-puller-operator/api/v1alpha1
# github.com/davecgh/go-spew v1.1.1 => github.com/davecgh/go-spew v1.1.1 # github.com/davecgh/go-spew v1.1.1 => github.com/davecgh/go-spew v1.1.1
github.com/davecgh/go-spew/spew github.com/davecgh/go-spew/spew
# github.com/devfile/api/v2 v2.0.0-20210713124824-03e023e7078b # github.com/devfile/api/v2 v2.0.0-20210917193329-089a48011460
## explicit ## explicit
github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2 github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2
github.com/devfile/api/v2/pkg/attributes github.com/devfile/api/v2/pkg/attributes
github.com/devfile/api/v2/pkg/devfile github.com/devfile/api/v2/pkg/devfile
# github.com/devfile/devworkspace-operator v0.2.1-0.20211005102315-728dff7e987c # github.com/devfile/devworkspace-operator v0.2.1-0.20211213140302-4226bdb05e56
## explicit ## explicit
github.com/devfile/devworkspace-operator/apis/controller/v1alpha1 github.com/devfile/devworkspace-operator/apis/controller/v1alpha1
github.com/devfile/devworkspace-operator/controllers/controller/devworkspacerouting github.com/devfile/devworkspace-operator/controllers/controller/devworkspacerouting
@ -46,8 +46,10 @@ github.com/devfile/devworkspace-operator/controllers/controller/devworkspacerout
github.com/devfile/devworkspace-operator/internal/map github.com/devfile/devworkspace-operator/internal/map
github.com/devfile/devworkspace-operator/pkg/common github.com/devfile/devworkspace-operator/pkg/common
github.com/devfile/devworkspace-operator/pkg/config github.com/devfile/devworkspace-operator/pkg/config
github.com/devfile/devworkspace-operator/pkg/config/configmap
github.com/devfile/devworkspace-operator/pkg/constants github.com/devfile/devworkspace-operator/pkg/constants
github.com/devfile/devworkspace-operator/pkg/infrastructure github.com/devfile/devworkspace-operator/pkg/infrastructure
github.com/devfile/devworkspace-operator/pkg/provision/sync
# github.com/dgrijalva/jwt-go v3.2.0+incompatible => github.com/dgrijalva/jwt-go v3.2.0+incompatible # github.com/dgrijalva/jwt-go v3.2.0+incompatible => github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/dgrijalva/jwt-go github.com/dgrijalva/jwt-go
# github.com/evanphx/json-patch v4.11.0+incompatible => github.com/evanphx/json-patch v4.11.0+incompatible # github.com/evanphx/json-patch v4.11.0+incompatible => github.com/evanphx/json-patch v4.11.0+incompatible
@ -886,7 +888,7 @@ sigs.k8s.io/yaml
# github.com/huandu/xstrings => github.com/huandu/xstrings v1.2.0 # github.com/huandu/xstrings => github.com/huandu/xstrings v1.2.0
# github.com/imdario/mergo => github.com/imdario/mergo v0.3.5 # github.com/imdario/mergo => github.com/imdario/mergo v0.3.5
# github.com/inconshreveable/mousetrap => github.com/inconshreveable/mousetrap v1.0.0 # github.com/inconshreveable/mousetrap => github.com/inconshreveable/mousetrap v1.0.0
# github.com/irifrance/gini => github.com/irifrance/gini v1.0.1 # github.com/irifrance/gini => github.com/go-air/gini v1.0.1
# github.com/itchyny/astgen-go => github.com/itchyny/astgen-go v0.0.0-20200519013840-cf3ea398f645 # github.com/itchyny/astgen-go => github.com/itchyny/astgen-go v0.0.0-20200519013840-cf3ea398f645
# github.com/itchyny/go-flags => github.com/itchyny/go-flags v1.5.0 # github.com/itchyny/go-flags => github.com/itchyny/go-flags v1.5.0
# github.com/itchyny/gojq => github.com/itchyny/gojq v0.11.0 # github.com/itchyny/gojq => github.com/itchyny/gojq v0.11.0